「祝日を登録したのに反映されない」「9月と11月だけ、更新しようとすると画面が真っ白になる」「31日まである月では普通に動くので、なぜ起きるのか分からない」——kintoneのカスタマイズでは、こうした「特定の月だけ」失敗する不具合が起きることがあります。
原因を探していくと、日付の範囲検索で月末を「31日」に固定して書いていた、というケースがあります。kintoneは存在しない日付をクエリに書くと検索そのものを受け付けないため、31日が無い月だけ検索結果が返らず、その結果に依存する画面全体が動かなくなります。
この記事では、月末日を31日固定で書いた時に何が起きるか、直し方を2通り、そして再発を防ぐテストの入れ方を、コピペで動くコードでまとめます。
・日付条件の月末を「31日」の固定値で書くと、30日までの月と2月だけ検索が失敗する
・失敗するのは31日が無い月だけなので、31日まである月にテストすると気づけない
・直し方は「その月の実際の末日を計算する」か「翌月1日未満で書く」のどちらか
・境界の月(2月・うるう年・30日の月・12月)を単体テストに必ず入れる
目次を開く
症状:31日が無い月だけ検索が失敗する
次のような特徴が重なっていたら、この記事の原因を疑います。
- 特定の月(4・6・9・11月と2月)だけ、一覧や画面が読み込めない
- 31日まである月では、まったく問題が出ない
- 画面の表示に必要なマスタ(祝日・休業日のカレンダーなど)を、月単位で取得している
- 検索の結果が空になるのではなく、エラーで返ってきて画面全体が止まる
「データが無い」のではなく「検索が受け付けられていない」点が見分けどころです。
原因:存在しない日付をクエリに書いている
問題のコードは、月末日を固定の文字列で書いていた次の形です。
// NG: 月末を31日で固定している
const query = `holiday_date >= "${year}-${mm}-01" and holiday_date <= "${year}-${mm}-31"`; 9月に対して 2026-09-31 を渡すことになりますが、この日付は暦に存在しません。kintoneは日付の書式が不正なクエリとして検索を弾きます。今回の事例で返ってきたエラーコードは GAIA_ID01 でした。
| 月 | 末日 | -31 固定の結果 |
|---|---|---|
| 1・3・5・7・8・10・12月 | 31日 | 動く |
| 4・6・9・11月 | 30日 | 失敗する |
| 2月 | 28日(うるう年は29日) | 失敗する |
31日まである月では正常に動くので、本番で対象の月が来るまで発見が遅れます。
まず、自分のコードに同じ書き方が無いかを探します。誤検出もあるので、ヒットした箇所を目で確認してください。
grep -rnE -e '-31[^0-9]' src 直し方1:その月の実際の末日を計算する
JavaScript(TypeScript)では、「翌月の0日」が「当月の末日」になる性質を使えます。うるう年も自動で正しく計算されます。
// month は 1 始まり(9月なら 9)
export function monthRange(year: number, month: number) {
const last = new Date(year, month, 0).getDate(); // 翌月0日 = 当月末日
const mm = String(month).padStart(2, '0');
return {
from: `${year}-${mm}-01`,
to: `${year}-${mm}-${String(last).padStart(2, '0')}`,
};
} 使う側は次のとおりです。
const { from, to } = monthRange(2026, 9);
const query = `holiday_date >= "${from}" and holiday_date <= "${to}" order by holiday_date asc`;
const resp = await kintone.api(kintone.api.url('/k/v1/records', true), 'GET', {
app: kintone.app.getId(),
query,
}); toISOString() で日付文字列を作ると、UTCに変換されて日付が1日ずれることがあります。年月日を数値から自前で組み立てる書き方のほうが安全です。
直し方2:翌月1日未満で書く
末日の計算そのものを避ける方法もあります。「当月1日以上、翌月1日未満」と書けば、月ごとの日数を意識せずに済みます。
const pad = (n: number) => String(n).padStart(2, '0');
export function monthRangeExclusive(year: number, month: number) {
const next = new Date(year, month, 1); // 翌月1日(12月は翌年1月に繰り上がる)
return {
from: `${year}-${pad(month)}-01`,
until: `${next.getFullYear()}-${pad(next.getMonth() + 1)}-01`,
};
} const { from, until } = monthRangeExclusive(2026, 12);
const query = `holiday_date >= "${from}" and holiday_date < "${until}"`;
// holiday_date >= "2026-12-01" and holiday_date < "2027-01-01" 日時フィールドを対象にする場合は、この「未満」の書き方のほうが月末の時刻の取りこぼしを避けられます。手元のアプリで境界の日時を入れて確認してください。
再発防止:境界の月を単体テストに入れる
月末・月初を扱う検索条件は、31日まである月だけでテストすると必ず見逃します。次の月を最低限入れておきます。
import { describe, it, expect } from 'vitest';
describe('monthRange', () => {
it.each([
[2026, 1, '31'],
[2026, 2, '28'], // 平年の2月
[2028, 2, '29'], // うるう年の2月
[2100, 2, '28'], // 100で割り切れる年はうるう年ではない
[2026, 4, '30'],
[2026, 9, '30'],
[2026, 12, '31'],
])('%i年%i月の末日は%s日', (y, m, d) => {
expect(monthRange(y, m).to).toBe(`${y}-${String(m).padStart(2, '0')}-${d}`);
});
}); | テストに入れる月 | 確認できること |
|---|---|
| 2月(平年) | 28日で止まる |
| 2月(うるう年) | 29日まで取れる |
| 30日の月(4・6・9・11月) | -31 固定の書き方を検出できる |
| 12月 | 翌月1日方式で年またぎが正しい |
まとめ
- 月末を31日固定でクエリに書くと、31日が無い月だけ検索が受け付けられず、画面ごと止まる
- 月ごとの末日は
new Date(year, month, 0).getDate()で計算するか、「翌月1日未満」で書いて日数の計算を避ける -
-31の書き方はgrepで探せる - 2月・うるう年・30日の月・12月を単体テストに入れて、境界の不具合を先に潰す
よくある質問(FAQ)
Q. エラーコードが GAIA_ID01 ではない場合も同じ原因ですか?
エラーコードは事例によって変わることがあります。共通して確認したいのは「31日が無い月だけ失敗するか」と「クエリの日付が暦に存在するか」の2点です。実際に送っているクエリ文字列をログに出して、日付が存在するかを見てください。
Q. 日付フィールドで <= "2026-09-30" と < "2026-10-01" はどちらが良いですか?
日付フィールドだけなら、どちらでも同じ結果になります。日時フィールドも扱う場合や、末日の計算を書きたくない場合は「翌月1日未満」のほうが単純です。
Q. 31日まである月では動くのに、なぜテストで気づけなかったのですか?
テストの月が31日まである月に偏っていたためです。月末を扱うロジックは、30日の月とうるう年の2月をテストの必須項目にしておくと、同じ見逃しを防げます。
Q. 修正後、すでに読み込めなくなっている画面はどうなりますか?
クエリが受け付けられるようになれば、次の読み込みから通常どおり表示されます。データそのものが壊れているわけではないので、登録データの修復は基本的に不要です。
