JavaScriptでライブラリなしの日付の計算!標準機能で正確に処理

[PR]

JavaScript

JavaScriptで日付を扱うとき、ライブラリを使わずに「日付の計算」が必要になる場面は案外多いです。日付の差分を取ったり、月末を求めたり、うるう年を考慮したり――これらを標準機能で正確に実装できれば、コードが軽く保守もしやすくなります。この記事では「JavaScript 日付 計算 ライブラリ なし」というキーワードを軸に、標準機能だけで日付計算を正しく扱う方法をわかりやすく解説します。

目次

JavaScript 日付 計算 ライブラリ なしで基本を理解する

標準機能で日付計算をするときの基礎知識を押さえておけば、後の応用がスムーズになります。JavaScriptにはDateオブジェクトという組み込みの型があります。Dateはミリ秒という単位で時間を内部表現しており、1970年1月1日からの経過時間で処理されます。これにより差分を取る・加算・減算などが可能です。とはいえ、月の長さの違いやうるう年・タイムゾーンなど、思わぬ落とし穴があるので基本を理解することが非常に重要です。ここではDateオブジェクトの生成・加算・フォーマットなどの基本操作について解説します。

Dateオブジェクトの生成方法

Dateオブジェクトは引数なしで現在日時を生成できます。また、年・月・日・時間などを指定するコンストラクタ形式や、ミリ秒単位のタイムスタンプ指定など複数の方式があります。月は0から11で指定される点に注意が必要です。月末の計算などで、月は0起点なので混乱しやすいため、明示的に年と月と日にちを指定する形式を使うと誤りが減ります。

差分の取得と日付の加算減算

二つのDateオブジェクトの差分はミリ秒単位で得られます。それを日数や時間に換算することで期間を扱うことができます。日付を加算・減算するには、setDateやsetMonthなどを使ってもよいです。たとえば「ある日付から30日後」を求めるには、DateのgetDateで日にちを取得し、それに30を足してsetDateで設定する方法があります。このとき月をまたぐと自動的に月が繰り上がる仕様を利用できます。

日付フォーマットと文字列パース

Dateオブジェクトから年月日時分秒を取得するメソッドが多数あります。getFullYear・getMonth・getDate・getHoursなどを組み合わせて好きな書式に整形できます。一方で文字列からDateを生成する際のパースはブラウザや環境によって挙動が異なりうるため、ISO形式(YYYY-MM-DDまたはYYYY-MM-DDTHH:mm:ss)など標準形式を使うことが安全です。不正な形式を渡すとInvalid Dateになることがあるので入力チェックも必要です。

うるう年と月の長さを正確に扱う方法

日付計算で間違いやすいポイントの代表が「うるう年」と「各月の長さ」です。2月が28日か29日か、日数の変動はプログラムの間違いを招きます。それらを正しく扱うためには、うるう年かどうかを判定する関数や、月末を動的に取得するロジックが必要です。標準機能だけでこれらを実現する方法を紹介します。

うるう年の判定ルール

うるう年は西暦年で以下のルールに従います。まず4で割り切れる年は基本的にうるう年ですが、100で割り切れる年はうるう年ではなく、ただし400で割り切れる年は再びうるう年とする例外があります。この三段階の条件により、うるう年が正確に判定できます。JavaScriptではこのロジックを年を整数として取り扱い、「年%4」「年%100」「年%400」の組み合わせで判定できます。

月末を動的に取得する方法

ある月の最終日を取得するには、「次の月の1日の前日」を求める方法が一般的です。たとえば、年と月を指定して、新しいDateを次の月の1日として、日を0日に指定すれば、その月の最後の日付が得られます。これにより2月の28日か29日、4月の30日などを自動的に考慮できます。この手法は標準Dateを使った正確な計算方法です。

日数が異なる月をまたぐ加算・減算の注意点

もし1月31日に1か月を加算するとき、標準的な加算方法だけでは期待通りの結果にならないことがあります。DateのsetMonthはオーバーフロー処理をするため、例えば1月31日に1を加えると3月3日になることがあります。このようなケースでは、先に月末をチェックし、目標月の月末と比較して日付を調整するロジックを入れるとずれを防げます。

タイムゾーン・夏時間・UTCの落とし穴

標準機能で日付を計算している場合、それぞれの実行環境のタイムゾーンや夏時間(DST)が計算結果に影響を与えることがあります。特に日付の加算や差分を計算する際、UTCとローカル時刻の変換を理解しておかないと意図しない結果になることがあります。標準DateにはUTCメソッドがあり、UTC基準で操作を行うことでタイムゾーンや夏時間の影響を排除できます。ここではUTC操作やIntl APIの利用など、安全に日付を扱うための方法を解説します。

Date内部のUTCとローカル時刻の違い

Dateオブジェクトは内部ではUTC基準で時刻を保持していますが、getHoursなどを使うと実行環境のローカルタイムで値を返します。このため「2つの日付の差分」を取るとき、ローカル時間のオフセットが意図せず影響することがあります。計算を正確にするには、getUTCFullYear・getUTCMonthなどUTC系メソッドを利用することが推奨されます。

夏時間(DST)のずれを避ける工夫

夏時間の開始や終了日は国や地域によって変わるため、その年の夏時間変換を考慮する必要があります。たとえば1時間が伸びたり短くなる日付をまたいで時間を加算する場合、setHoursなどの操作で意図しない時間ずれが生じることがあります。これを避けるには、日付単位での加算にはsetDate、時間単位の加算にはsetHoursではなくUTCベースの計算を使うと影響が最小限になります。

Intl APIと標準機能の組み合わせ

標準のDateオブジェクトだけでは表示形式やロケール/タイムゾーンを制御しきれない場合があります。そのようなときにはIntl.DateTimeFormatなどの国際化用APIを利用することで、安全に日時をフォーマットできます。たとえば特定のタイムゾーンで日付を表示したい場合、ロケールとタイムゾーンを指定してIntlフォーマッタを生成し、それを使用することで標準機能だけで期待通りの表示を得られます。

応用ケース:期間計算・年齢・月数を標準機能で算出する

実務上よく使われる応用的な日付計算として「期間差」「年齢」「月数」などがあります。これらは単純にミリ秒差を日数に変換するだけでなく、カレンダーのルールを守る必要があります。標準機能と論理を組み合わせて精度の高い結果を得る方法について具体的に見ていきます。

2つの日付の差分で「日数・時間・分」を得る方法

まずDateオブジェクトを2つ用意し、それぞれのgetTime(または値としてキャスト)でミリ秒を取得します。その差を求め、1000で秒、さらに60で分、24で時間、さらに日数に換算します。時間や分を余り計算で取り出す処理を併用することで「3日と4時間5分」などのフォーマットが得られます。標準機能だけで十分に表現可能です。

年齢計算の注意点と実装例

年齢を計算する場合、単に年の差を取るだけでは誕生日を越えているかどうかを判断できません。たとえば2026年6月10日現在で1990年7月15日生まれの人はまだ35歳で、1990年5月1日生まれなら36歳になります。年差を取り、現在の月日が誕生日の月日より小さいかどうかで年齢を1つ少なくするロジックが必要です。

月数・期間を正しくカウントするテクニック

月数を計算することは年齢を月単位で出すときや支払回数の期間計算で重要です。月数だけを計算するには、年差×12+月差という式を使い、さらに日にち差がある場合は1か月を調整します。日にちが目標月より前ならその月数を1引く、といったロジックが必要です。標準機能のgetMonth・getDateなどで比較できます。

標準機能のみでよくある誤りとその回避策

標準機能だけで日付計算をする場合、多くの開発者が罠にはまります。たとえば「1日ずれてしまった」「月末がずれてしまった」「夏時間で時間が狂っている」などです。ここでは代表的な誤りパターンと、それを回避する具体的な対策を紹介します。これを知っていれば、ライブラリなしで十分に堅牢な日付処理が書けるようになります。

オフバイワンの日数カウントエラー

開始日と終了日を含むかどうか、あるいは境界をどう扱うかで1日の違いが出ることがあります。たとえば期間を「2026年6月1日〜6月10日」としたとき、含む日数は10日間ですが、差分計算(終了−開始)では9日となります。どちらを求めたいかを明確にし、それに合わせて+1をするかしないかを決めて実装します。

Date.parseと曖昧な文字列入力の問題

DateコンストラクタやDate.parseで日付文字列を扱うと、環境によって解釈が異なることがあります。たとえば「2026/06/10」形式が異なるロケールで読み替えられることがあります。これを避けるにはISOフォーマットを使うか、年月日を個別に取得してnew Date(year, month−1, day)形式で生成するようにします。

月末や1月31日など極端な日付のケース

先に述べたように、月末や1月31日など、次月にその日が存在しない日を加算するとき、Dateの標準動作では自動的に繰り越しが発生します。これが期待とは違う結果になる場合があります。期待どおりの月末に揃えたいなら、加算前に目標月の最終日を取得し、元の日付がその値を超えていたら調整するロジックを入れます。

コード例:標準機能だけで使える実践ユーティリティ集

実際に使える関数をいくつか提示します。ライブラリを使わずとも標準機能だけで日付処理を多くこなせるようになります。必要に応じてコピーして使えるコードを中心に、可読性と正確性を重視しています。

うるう年チェック関数

うるう年かどうかを判定する関数を次のように書けます。
function isLeapYear(year) {
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}

この関数は100年単位・400年単位の例外も考慮しています。過去・未来の西暦年にも対応でき、日数計算やバリデーションに安心して使えます。

月末を取得する関数

function getLastDayOfMonth(year, month) {
return new Date(year, month + 1, 0).getDate();
}

ここで month は 0 から 11 の範囲です。この関数を使えばどの月でも最終日の数を取得できます。2月のうるう年・平年の差も自動的に処理されます。

2つの日付から「日数・時間・分」の差を返す関数

function diffDetailed(date1, date2) {
const ms = Math.abs(date2.getTime() - date1.getTime());
const days = Math.floor(ms / (1000 * 60 * 60 * 24));
const hours = Math.floor((ms % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
return { days, hours, minutes };
}

このように日数のみならず時間・分も取り出せます。差分を扱うユーティリティとして汎用性が高いです。

深掘り:最新機能と将来に向けた注意点

標準機能だけで日付計算を行う方法は確立していますが、JavaScriptでは将来性のある新しいAPIも登場しています。また、既存機能の制限を理解しておくことは長期的なメンテナンスで非常に重要です。ここでは標準の限界と最新の選択肢、将来対応へのヒントを紹介します。

Dateオブジェクトの仕様制限</

Dateオブジェクトは内部でミリ秒で時間を管理していますが、これにはうるう秒の処理は含まれていないため、天文学的な正確性が必要な用途には不向きです。また極めて古い過去の年や未来の年を扱うとき、浮動小数点精度の問題が発生する可能性があります。さらに、getMonth等の月が0から始まる仕様や、曖昧な文字列入力の扱いの違いなどに注意が必要です。

Temporal APIの台頭

新しい日付・時間APIとしてTemporalが提案されています。これは標準で日付・時間の計算やタイムゾーン・ロケール・表現形式の多くの問題を解決することを目的にしています。標準機能のみを使いたい場合でも、この新APIへの移行を視野に入れて実装をしておくことが望ましいです。将来の互換性を見据えるなら、標準的な方法で作られたユーティリティがTemporalにも適応しやすい構造にすることが良いでしょう。

パフォーマンスと可読性のトレードオフ

日付操作は頻繁に行うことがあり得るため、パフォーマンスが問題になることがあります。しかし過度に最適化するよりも、まずは正確でメンテナンス性のあるコードを書くことが重要です。可読性が損なわれるとバグが入りやすくなるため、関数を分けて目的を明確にすること、そしてテストを行うことが成功の鍵です。

まとめ

標準的なJavaScriptの機能だけであっても、日付の生成・加算・差分・フォーマット・うるう年・月末の扱い・タイムゾーンなどを正確に処理するユーティリティを作ることは十分可能です。重要なのは、仕様の「月が0からカウントされる」「UTCとローカルの違い」「夏時間の変動」「曖昧な文字列パース」を理解して、誤りを防ぐロジックを組むことです。ライブラリなしで軽く、保守しやすく、高信頼なコードを書くための知識を本記事でつかんでいただければ幸いです。

特集記事

コメント

この記事へのトラックバックはありません。

TOP
CLOSE