0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

生後日数を (基準日 - 誕生日) / 86400000 で出すと、夏時間のある国では1日足りなくなる

0
Posted at

背景

自分のツールサイトに、生年月日から満年齢や生後日数を出すページを置いています。
https://hashitosystem.com/tools/agecalc/
英語版も同じコードで動いているのですが、ふと「アメリカの人が使ったらどうなるんだろう」と気になりました。
日本には夏時間が無いので、私は夏時間のバグを踏んだことがほぼありません(なので苦手です…)。
試してみたら、生後日数が1日ずれる組み合わせがあったので残しておきます。

ツールの中身

生後日数の計算は、下記の1行でした。
b は生年月日、r は基準日(何日目か知りたい日)で、どちらも入力欄の YYYY-MM-DD を new Date(年, 月-1, 日) で作った Date です。
つまり、閲覧している人のタイムゾーンでの「その日の0時」を表しています。

// b: 生年月日の0時(ローカル時刻), r: 基準日の0時(ローカル時刻)
var days = Math.floor((r - b) / 86400000);

Date どうしを引くとミリ秒の差になるので、1日のミリ秒(86400000 = 24×60×60×1000)で割って切り捨てる、という仕組みだ。
1日が必ず24時間なら、これで正しい日数になります。

夏時間が始まる日は23時間しかない

夏時間(DST, Daylight Saving Time)は、夏のあいだ時計を1時間進める制度だ。
アメリカ東部では、2026年は3月8日の午前2時に時計が3時へ飛ぶので、その日は23時間しかありません。
すると、3月8日をまたぐ「0時から0時まで」の差は、ちょうどの日数より1時間短くなります。

下記は、ツールと同じ計算をタイムゾーンを変えて Node.js で回したものです。
daysFloor がツールの式、daysUTC が後で書く直し方です。

// age-dst.js : 生後日数の計算をタイムゾーンごとに比べる
function parse(v) { const p = v.split('-'); return new Date(+p[0], +p[1] - 1, +p[2]); }
function daysFloor(b, r) { return Math.floor((r - b) / 86400000); }
function daysUTC(b, r) {
  const ub = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
  const ur = Date.UTC(r.getFullYear(), r.getMonth(), r.getDate());
  return (ur - ub) / 86400000;
}
const cases = [['2026-01-10', '2026-03-20'], ['2026-03-01', '2026-03-08'], ['2026-03-01', '2026-03-09'], ['2026-01-10', '2026-12-20']];
for (const [b, r] of cases) {
  const B = parse(b), R = parse(r);
  console.log(b, '->', r, 'floor=', daysFloor(B, R), 'utc=', daysUTC(B, R), 'raw=', (R - B) / 86400000);
}

Windows の PowerShell なら $env:TZ='America/New_York'; node age-dst.js、macOS や Linux なら TZ=America/New_York node age-dst.js で、タイムゾーンを切り替えて実行できます。
Node.js v24.11.1 での結果が下記です。

# TZ=Asia/Tokyo
2026-01-10 -> 2026-03-20 floor= 69 utc= 69 raw= 69
2026-03-01 -> 2026-03-08 floor= 7 utc= 7 raw= 7
2026-03-01 -> 2026-03-09 floor= 8 utc= 8 raw= 8
2026-01-10 -> 2026-12-20 floor= 344 utc= 344 raw= 344

# TZ=America/New_York
2026-01-10 -> 2026-03-20 floor= 68 utc= 69 raw= 68.95833333333333
2026-03-01 -> 2026-03-08 floor= 7 utc= 7 raw= 7
2026-03-01 -> 2026-03-09 floor= 7 utc= 8 raw= 7.958333333333333
2026-01-10 -> 2026-12-20 floor= 344 utc= 344 raw= 344

ニューヨーク時間では、1月10日から3月20日までが 68日 と出て、東京の 69日 と1日食い違っています。
raw の 68.958… が、1時間(1/24 日 ≒ 0.0417)足りない分です。
これを Math.floor で切り捨てるので、まるごと1日消えてしまう、ということですね。
3月1日から3月8日(切り替えの当日0時)までは 7日 で合っていて、3月9日になった途端に 7日 のまま止まるのも分かると思います。

夏時間が終わる側では表に出ない

面白いのは、1月10日から12月20日までだと 344日 で合っていることです。
11月の夏時間の終わりで1日が25時間になり、3月で失った1時間を取り戻すからです。
また、夏のあいだに生まれて冬に数える場合は、差が1時間「長く」なるだけなので、切り捨てても日数は変わりません。
なので、ずれるのは「夏時間が始まる日をまたいで、まだ終わっていない」期間だけ、という様子でした。
日本時間で動かしている限り絶対に出ないので、テストを日本で書いていると気づけない気がします…。

日付だけの差は UTC に置き直してから割る

直し方は、年月日を取り出して Date.UTC で UTC の0時に置き直してから引くことです。
UTC(協定世界時)には夏時間が無いので、1日は必ず 86400000 ミリ秒になります。
上の daysUTC がそれで、ニューヨーク時間でも 69日・8日 と正しく出ました。
割り算の結果は必ず整数になるので、Math.floor も要りません。

Math.round で丸めて逃げる手もあります。
ツールでも「次の誕生日まで」のほうは Math.round にしていて、こちらはずれていませんでした。
ただ、丸めは「差が半日未満なら吸収できる」というだけで、なぜ合うのかがコードから読めません。
日付どうしの差を出したいだけなら、時刻の要素を最初から持ち込まない Date.UTC のほうが意図がはっきりすると考えています。

このツールの英語版は、まだ Math.floor のままなので直します。
日付の差を (r - b) / 86400000 で出しているコードがあったら、TZ=America/New_York で一度動かして確かめてください。


本記事はAI補助で執筆した、個人開発の紹介記事です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?