- 技術 💻
日付処理の落とし穴8選|2038年問題・うるう秒・うるう年2100年
日付処理って、普段は空気みたいに動いているんですよね。でも、締め日、年末年始、月末、時差。日付のバグは、境界の日にしか出ません。
今回は、知っていると事故を1つ減らせるものを8つ集めました。「動いているのに、年末だけ壊れる」の正体たちです。1つでも知らなかったら儲けものです。異論・追加も大歓迎です。
① 日本にも、夏時間があった時代があります
「日本時間は昔からずっとUTC+9」——実は、違うんです。1948年から1951年まで、日本でもサマータイムが実施されていました。この期間の夏は、日本時間がUTC+10だったんですよね。
タイムゾーンデータベース(tzdata)にはこの歴史がちゃんと入っているので、当時の日時をAsia/Tokyoで扱うと、+10:00のオフセットが返ってきます。さらにさかのぼると、1887年以前は「+9:18:59」という中途半端なオフセットになります。東京の経度に基づく地方時が使われていた時代の記録なんです。
古いデータを扱っていて「オフセットがおかしい」と思ったら、バグではなく歴史かもしれません。
② 2038年1月19日に、時刻が壊れるシステムがあります
いわゆる2038年問題です。UNIX時間を符号付き32bitで持っているシステムは、2038年1月19日(日本時間の昼12時14分すぎ)に桁あふれを起こします。カウンタが負に転じて、1901年として解釈されるんですよね。
「そんな古い仕組み、もう無いでしょ」と思いきや、組み込み機器や古いDBのTIMESTAMP型、32bitでビルドされたままのライブラリに、いまも残っています。
そして見落としがちなのが、2038年当日ではなく、その前に発動すること。「20年後の満期日」を計算する金融システムなら、2018年の時点でもう踏んでいたわけです。未来日付を扱うシステムほど、前倒しでやってくる問題なんです。
③ 12月31日が「翌年の第1週」になる年があります
ISO 8601の週番号は、「その年の最初の木曜日を含む週」が第1週というルールです。なので年末年始の数日は、暦の年と「週の年」がズレます。たとえば2024年12月30日は、週番号の世界では「2025年の第1週の月曜日」なんですよね。
これが実害になるのが、日付フォーマットの指定ミスです。
// Java: yyyy と YYYY は別物
yyyy-MM-dd → 2024-12-30 // 暦の年
YYYY-MM-dd → 2025-12-30 // 週基準の年(意図と違う!)
大文字のYYYYは「週基準の年」。年末の数日だけ、年が1つ未来にズレたログや帳票が出るという、発見しづらいバグになります。「動いているのに年末だけおかしい」の定番犯人です。
④ うるう秒は、廃止が決まっています
1日が86,401秒ある日が、これまでに何度も実在しました。うるう秒です。地球の自転のゆらぎに合わせて「23時59分60秒」を挿入する仕組みですが、2035年までに廃止することが国際的に決議済みです。挿入のたびに世界のどこかでシステム障害が起きてきたからなんですよね。
さらに「ほぉー」なのがここで。UNIX時間は、うるう秒を最初から無視しています。1日はつねに86,400秒として数える設計です。だから大手クラウドは、うるう秒を数時間かけて少しずつ薄める「うるう秒スメア」で吸収してきました。
厳密な時刻の世界と、コンピュータの時刻の世界は、実は少しズレているんです。
⑤ タイムゾーンは、政治で変わります
タイムゾーンの定義は、自然法則ではなく各国の決定です。有名なのがサモアで、2011年、日付変更線の西側に移ることを決めました。その結果、2011年12月30日という日が、サモアには存在しません。12月29日の次が、12月31日でした。
夏時間の開始日を直前に変更する国もあります。だからタイムゾーンデータベース(tzdata)は、年に数回更新され続けているんですよね。「タイムゾーン処理はライブラリに任せて、tzdataを更新し続ける」。自前実装が推奨されない理由が、ここにあります。
⑥ 存在しない時刻と、2回ある時刻があります
夏時間のある地域では、時計が飛ぶ日と戻る日があります。春は2:00が3:00になるので、2時30分という時刻がその日には存在しません。秋は逆に、1時30分が2回あります。
これが効いてくるのが、ローカル時刻からUTCへの変換です。存在しない時刻を渡されたらどうするか、2回ある時刻のどちらと解釈するか。ライブラリごとに挙動が違うんですよね。
深夜2時台に仕込んだ定期ジョブが年に1回だけ動かない(または2回動く)——夏時間圏のシステムでは実際に起きる事故です。バッチの時刻を決めるときは、切替時間帯を避けるのが定石です。
⑦ 1月31日の「1ヶ月後」を、即答できますか
月の加算は、見た目より奥が深いんです。多くのライブラリでは、1月31日の1ヶ月後は「2月28日」に丸められます。ここまでは知られていますよね。問題はその先で。
1月31日 → +1ヶ月 → 2月28日 → +1ヶ月 → 3月28日
1月31日 → +2ヶ月 → 3月31日
「1ヶ月後の1ヶ月後」と「2ヶ月後」が、一致しないんです。途中で月末に丸めた情報が失われるからですね。
毎月の請求日や締め日を「前回の日付+1ヶ月」で転がしていくと、月末スタートの契約だけ日付が少しずつ前にズレていきます。基準日を保持して「開始日+nヶ月」で計算するのが安全な形です。
⑧ 「4で割れたらうるう年」は、2100年に壊れます
うるう年の正しいルールは3段構えです。4で割り切れたらうるう年。ただし100で割り切れたら平年。ただし400で割り切れたらうるう年。だから2000年はうるう年でしたが、2100年は「4で割れるのに平年」です。
% 4 == 0 だけの実装は、2000年にはたまたま正解してしまったんですよね。400年ルールのおかげで。なので当時のテストでは見つからず、2100年2月29日を作ってしまうコードが、いまも眠っている可能性があります。有効期限や償還日で50年先・80年先を扱うシステムは、他人事ではない話です。
番外編:手元で確かめられる小ネタ
11日間が消えた月を見る
cal 9 1752
UNIXのcalコマンドで1752年9月を表示すると、2日の次が14日です。英国圏がグレゴリオ暦へ切り替えたとき、暦のズレを一気に清算した跡なんですよね。
JavaScriptの日付パースの罠
new Date("2026-08-21") // UTCの0時として解釈
new Date("2026/08/21") // ローカルの0時として解釈
区切り文字が違うだけで、解釈が9時間ズレます。日本では「前日として表示される」バグの定番です。
UTCとGMTは、厳密には別物
GMTは天文観測ベース、UTCは原子時計ベース。日常では同じに扱われますが、定義が違うんです。会話の小ネタにどうぞ。
RJCでも、日付と戦っています
RJCが携わるのは、保険、金融、人材、決済、交通。生活の裏側を支えるシステムです。この領域は、締め日、満期日、有効期限、利用開始日——日付の計算がそのまま仕様の中心なんですよね。
だから今日のような話を、Techチームで共有しながら開発しています。境界値の知識は、そのままテスト観点になるんです。
よくいただくご質問
Q. まずどれから意識すればいいですか?
A. ⑦の月末加算かなと思います。業務システムでいちばん踏みやすくて、しかも気づきにくいんですよね。
Q. 日付処理の基本方針はありますか?
A. 「保存はUTC、表示でローカルに変換」が定石です。ただし例外があって、未来の予定(会議やイベント)はタイムゾーン名ごと保存する方が安全なんです。⑤のとおり、タイムゾーンの定義自体が将来変わるかもしれないので。
Q. テストでは何を見ればいいですか?
A. 月末(特に1/31と2月)、年末年始、うるう日、そして夏時間圏なら切替日。今日の8つが、そのまま境界値のリストになります。
Q. 未経験からでも、ここまで分かるようになりますか?
A. なります。日付のルール自体は誰でも確かめられるので、「境界の日を疑う」習慣がつけば一気に強くなります。
日付のバグは、書いた日には出ません
今日の8つを並べてみて思ったのが、これでした。実装した日にはテストが通って、平日はずっと正常に動く。そして年末や月末や、何年も先の境界の日に、静かに発動する。
だから日付処理は、「いま動いている」を信用しすぎないのが強さなのかなと思います。
みなさんが踏んだ日付・時刻のバグ、よかったら教えてください。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。