背景
自分のツールサイトに、生年月日から満年齢や生後日数を出すページを置いています。
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補助で執筆した、個人開発の紹介記事です。