【ITニュース解説】A Day Is Not 86400 Seconds: The DST Bug in Your Date Math
2026年09月24日に「Dev.to」が公開したITニュース「A Day Is Not 86400 Seconds: The DST Bug in Your Date Math」について初心者にもわかりやすく解説しています。
ITニュース概要
「1日=86400秒」という認識は危険だ。サマータイム(DST)があるため、暦上の1日は86400秒ではないことがある。安易な秒数加算による日付計算は、DST切り替え日に日付がずれるバグを生む。タイムゾーンを考慮し、暦日単位で計算するメソッドを使おう。絶対時刻の扱いやテストの工夫も重要だ。
ITニュース解説
システム開発において、日付や時刻の計算は非常に頻繁に行われる基本的な処理の一つだ。しかし、「1日後」を計算する際に、単純に24時間、つまり86,400秒を足すという方法が、実は大きな落とし穴になることがある。「夏時間」(Daylight Saving Time、略してDST)という制度が、この日付計算に予期せぬバグを引き起こす原因となるのだ。
このバグは、年にたった2回しか現れないため、見過ごされやすく、いざ発生すると原因の特定が難しい。典型的な例として、ある時刻からちょうど24時間後を計算するコードを考えてみよう。例えば、JavaScriptでnew Date(Date.now() + 86400 * 1000);という記述は、現在の時刻(ミリ秒)に24時間分のミリ秒を加算して「明日、同時刻」を求めようとするものだ。このコードは、ほとんどの日では問題なく動作する。しかし、夏時間が始まる日と終わる日という年に2回だけ、計算結果が1時間ずれてしまうことがある。さらに、このずれた時刻を「日付」として丸めてしまうと、本来計算したかった日付とは異なる、丸一日ずれた結果になることも珍しくない。
なぜこのようなずれが生じるのだろうか。その鍵は、「絶対的な瞬間」と「カレンダー上の期間」の違いにある。コンピュータの世界では、時刻はしばしば「Unixタイムスタンプ」や「エポック秒」と呼ばれる形式で扱われる。これは、1970年1月1日午前0時0分0秒UTC(協定世界時)からの経過秒数を指す絶対的な値であり、地球上のどの場所で見ても、その瞬間自体は同じだ。例えば、1758700800というタイムスタンプは、日本でもアメリカでも、またヨーロッパでも、まったく同じ瞬間を示す。秒数を用いた算術計算は、この絶対的な瞬間に対しては常に正確に行われる。しかし、「1日後」という概念は、実は絶対的な期間ではない。これは「カレンダー上の操作」であり、その長さは、その日その瞬間に適用されているタイムゾーンのルールに大きく依存するのだ。
具体的に見てみよう。夏時間が始まる日、時計が1時間「進む」地域では、その日は実質的に23時間しかない。つまり、秒数にすると82,800秒となる。逆に、夏時間が終わる日、時計が1時間「戻る」地域では、その日は実質的に25時間あることになり、秒数にすると90,000秒となる。このように、私たちが普段「1日」と認識している期間が、夏時間の切り替え日には24時間(86,400秒)からずれてしまうのだ。したがって、単純に86,400秒を足して「1日後」を計算すると、夏時間の境界をまたぐ日に、本来意図した時刻より1時間早く着いたり、あるいは前の日の23時に戻ってしまったりする結果となる。
この問題は、特定のプログラミング言語に限らず、様々な言語やシステムで発生しうる。例えば、JavaではInstant.now().plus(Duration.ofDays(1));というコードは、内部で24時間、つまり86,400秒を加算するため、DSTの境界で問題を発生させる可能性がある。これに対し、ZonedDateTime.now(zone).plusDays(1);というコードは、指定されたタイムゾーンのカレンダールールに従って「1日」を加算するため、DSTを正しく処理できる。JavaScriptの場合も同様に、new Date(Date.now() + 86400 * 1000);は秒数での加算だが、d.setDate(d.getDate() + 1);のように日付オブジェクトのメソッドを使ってカレンダー日を加算する方が安全だ。Go言語では、t.Add(24 * time.Hour)が期間を加算するのに対し、t.AddDate(0, 0, 1)はカレンダー日を加算する。SQLにおいても、date_add(d, interval 1 day)はタイムゾーンを考慮するが、d + interval 86400 secondは単純な秒数加算になる。Pythonのdt + timedelta(days=1)は、日時オブジェクトがタイムゾーン情報を「持たない(naive)」場合は壁時計の時間として計算されるが、タイムゾーン情報を「持つ(aware)」場合でもDST対応のゾーンでは予期せぬ挙動をすることがあるため注意が必要だ。
では、この厄介なDSTバグをどのように回避すれば良いのだろうか。最も重要なのは、時刻データを「絶対的な瞬間」として保存することだ。具体的には、エポック秒やUTC(協定世界時)のISO 8601形式で時刻を記録する。これらの形式は、タイムゾーンや夏時間の影響を受けない普遍的な時刻を表すため、データの一貫性を保つことができる。次に、「この日付は何日のことか?」といったカレンダー上の判断を行う際は、必ずユーザーのタイムゾーンを正確に考慮する必要がある。ここでいうタイムゾーンとは、単なる「現在のオフセット」(UTCからの時間差)ではなく、夏時間ルールを含むその地域の時間ルール全体を指す。例えば、「昨日の行」といったデータを取得する場合、現在時刻から単純に86,400秒を引くのではなく、ユーザーのタイムゾーンにおいて「昨日」がいつからいつまでだったのかをカレンダー上の日境界として計算し、それを絶対的な瞬間に変換して利用するべきだ。
また、システム開発におけるテストの段階で、DSTの切り替わり日を想定したテストケースを必ず含めることが重要である。年に2回しか発生しないため、テストが忘れられがちだが、このテストを怠ると本番環境で突然バグが露呈し、深刻な問題に発展する可能性がある。もし実際にDST関連のバグに遭遇し、ログに記録された生のエポック値やタイムスタンプを分析する必要が生じた場合は、タイムスタンプ変換ツールを活用すると良いだろう。これらのツールは、ローカル時間とUTCを並べて表示し、さらにミリ秒単位か秒単位かを自動で検出してくれるため、デバッグ作業を効率的に進める手助けとなる。
このように、日付と時刻の扱いは一見単純に見えて、タイムゾーンや夏時間といった複雑な要素が絡み合う奥深いテーマである。システムエンジニアを目指す上では、常にこれらの要素を意識し、安易な秒数計算に頼らず、カレンダーのルールに基づいた適切な日付操作を心がけることが、堅牢なシステムを構築するための第一歩となる。