DateTime操作の決定的なガイド
公開: 2022-03-11ソフトウェア開発者として、日付操作から逃げることはできません。 開発者が作成するほとんどすべてのアプリには、ユーザーから日付/時刻を取得し、データベースに保存して、ユーザーに表示する必要があるコンポーネントがあります。
日付とタイムゾーンを処理した経験についてプログラマーに尋ねると、おそらくいくつかの戦争の話を共有するでしょう。 日付と時刻のフィールドの処理は確かにロケット科学ではありませんが、退屈でエラーが発生しやすいことがよくあります。
このトピックに関する記事は何百もありますが、ほとんどの記事は学術的すぎて詳細に焦点を当てているか、パッチが多すぎて説明があまりない短いコードスニペットを提供しています。 このDateTime操作の詳細なガイドは、このトピックに関する大量の情報を参照しなくても、時間と日付に関連するプログラミングの概念とベストプラクティスを理解するのに役立ちます。
この記事では、日付と時刻のフィールドについて明確に考えるのに役立ち、日付と時刻の地獄を回避するのに役立ついくつかのベストプラクティスを提案します。 ここでは、日付と時刻の値を正しく操作するために必要ないくつかの重要な概念、DateTime値を保存してAPIを介して転送するのに便利な形式などについて説明します。
手始めに、本番コードの正しい答えは、ほとんどの場合、独自のライブラリを作成するのではなく、適切なライブラリを使用することです。 この記事で説明するDateTime計算の潜在的な問題は氷山の一角に過ぎませんが、ライブラリの有無にかかわらず、それでも知っておくと役立ちます。
DateTimeライブラリは、それらを正しく理解している場合に役立ちます
日付ライブラリは、あなたの生活を楽にするために多くの点で役立ちます。 これらは、日付の解析、日付の算術演算と論理演算、および日付の書式設定を大幅に簡素化します。 フロントエンドとバックエンドの両方で信頼できる日付ライブラリを見つけて、手間のかかる作業のほとんどを実行できます。
ただし、日付/時刻が実際にどのように機能するかを考えずに、日付ライブラリを使用することがよくあります。 日付/時刻は複雑な概念です。 誤った理解のために発生するバグは、日付ライブラリの助けを借りても、理解して修正するのが非常に難しい場合があります。 プログラマーは、基本を理解し、日付ライブラリがそれらを最大限に活用するために解決する問題を理解できる必要があります。
また、日付/時刻ライブラリはこれまでのところしか取得できません。 すべての日付ライブラリは、DateTimeを表す便利なデータ構造にアクセスできるようにすることで機能します。 REST APIを介してデータを送受信する場合、JSONにはDateTimeを表すネイティブデータ構造がないため、最終的に日付を文字列に変換する必要があります。その逆も同様です。 ここで概説した概念は、これらの日付から文字列および文字列から日付への変換を行うときに発生する可能性のある一般的な問題のいくつかを回避するのに役立ちます。
注:この記事で説明するプログラミング言語としてJavaScriptを使用しましたが、これらは、事実上すべてのプログラミング言語とその日付ライブラリに大部分が適用される一般的な概念です。 したがって、これまでにJavaScriptの行を書いたことがない場合でも、この記事でJavaScriptについての予備知識はほとんどないと思いますので、お気軽に読み続けてください。
時間の標準化
DateTimeは、非常に特定の時点です。 これについて考えてみましょう。 この記事を落書きすると、ラップトップの時計は7月21日午後1時29分になります。 これは私たちが「現地時間」と呼んでいるもので、私の周りの壁掛け時計や腕時計に見られる時間です。
少し待ってください。午後3時に近くのカフェで会うように友達に頼むと、ほぼその時間に彼女に会えると期待できます。 同様に、たとえば「1時間半で会いましょう」と言っても混乱はありません。 私たちは日常的に同じ都市やタイムゾーンに住む人々とこのように時間について話します。
別のシナリオを考えてみましょう。スウェーデンのウプサラに住む友人に、午後5時に話したいことを伝えたいと思います。 私は彼にメッセージを送ります。「ねえアントン、午後5時に話しましょう。」 「あなたの時間ですか、それとも私の時間ですか?」という応答がすぐに届きます。
アントンは、UTC + 01:00である中央ヨーロッパ時間帯に住んでいると私に言います。 私はUTC+05:45に住んでいます。 これは、私が住んでいる場所が午後5時の場合、午後5時です。私たちの。
また、タイムゾーン(中央ヨーロッパ時間)とタイムゾーンオフセット(UTC + 05:45)の違いに注意してください。 国は、政治的な理由から、夏時間のタイムゾーンオフセットを変更することもできます。 ほぼ毎年、少なくとも1つの国でルールが変更されています。つまり、これらのルールが組み込まれているコードはすべて最新の状態に保つ必要があります。アプリの各層で、コードベースがこれにどのように依存しているかを検討する価値があります。
これは、ほとんどの場合、フロントエンドのみがタイムゾーンを処理することをお勧めするもう1つの理由です。 そうでない場合、データベースエンジンが使用するルールがフロントエンドまたはバックエンドのルールと一致しないとどうなりますか?
ユーザーと広く受け入れられている標準に関連して、2つの異なるバージョンの時間を管理するというこの問題は困難です。精度が重要であり、1秒でも大きな違いを生む可能性があるプログラミングの世界ではさらに困難です。 これらの問題を解決するための最初のステップは、DateTimeをUTCで保存することです。
フォーマットの標準化
UTC時刻を保存するだけでよく、ユーザーのタイムゾーンを知っている限り、いつでもユーザーの時刻に変換できるため、時刻の標準化はすばらしいことです。 逆に、ユーザーの現地時間を知っていて、そのタイムゾーンを知っている場合は、それをUTCに変換できます。
ただし、日付と時刻はさまざまな形式で指定できます。 日付については、「7月30日」または「7月30日」または「7/30」(または住んでいる場所によっては30/7)と書くことができます。 とりあえず、「午後9時30分」または「2130」と書くことができます。
世界中の科学者が集まってこの問題に取り組み、短くて正確であるため、プログラマーが本当に好きな時間を記述する形式を決定しました。 これを「ISO日付形式」と呼びます。これは、ISO-8601拡張形式の簡略版であり、次のようになります。
00:00またはUTCの場合、代わりに「Z」を使用します。これは、UTCの別名であるズールー時間を意味します。
JavaScriptでの日付操作と算術
ベストプラクティスを開始する前に、JavaScriptを使用した日付操作について学習し、構文と一般的な概念を理解します。 JavaScriptを使用していますが、この情報をお気に入りのプログラミング言語に簡単に適合させることができます。
日付演算を使用して、ほとんどの開発者が遭遇する一般的な日付関連の問題を解決します。
私の目標は、文字列から日付オブジェクトを作成し、文字列からコンポーネントを抽出することを快適にすることです。 これは日付ライブラリが役立つものですが、舞台裏でどのように行われるかを理解することをお勧めします。
日付/時刻で手を汚すと、直面している問題について考え、ベストプラクティスを抽出し、先に進むことが容易になります。 ベストプラクティスにスキップしたい場合は、遠慮なく行ってください。ただし、少なくとも以下の日付演算セクションをざっと読むことを強くお勧めします。
JavaScriptの日付オブジェクト
プログラミング言語には、私たちの生活を楽にするための便利な構造が含まれています。 JavaScriptのDate
オブジェクトはそのようなものの1つです。 現在の日付と時刻を取得し、変数に日付を格納し、日付演算を実行し、ユーザーのロケールに基づいて日付をフォーマットするための便利な方法を提供します。
ブラウザの実装の違いと夏時間(DST)の誤った処理のため、ミッションクリティカルなアプリケーションのDateオブジェクトに依存することはお勧めできません。おそらく、Luxon、date-fns、dayjsなどのDateTimeライブラリを使用する必要があります。 (使用するものは何でも、かつて人気のあったMoment.js(コードに表示されるように単にmoment
と呼ばれることが多い)は廃止されたため、避けてください。)
ただし、教育目的では、Date()オブジェクトが提供するメソッドを使用して、JavaScriptがDateTimeを処理する方法を学習します。
現在の日付を取得する
const currentDate = new Date();
Dateコンストラクターに何も渡さない場合、返される日付オブジェクトには現在の日付と時刻が含まれます。
次に、次のようにフォーマットして、日付部分のみを抽出できます。
const currentDate = new Date(); const currentDayOfMonth = currentDate.getDate(); const currentMonth = currentDate.getMonth(); // Be careful! January is 0, not 1 const currentYear = currentDate.getFullYear(); const dateString = currentDayOfMonth + "-" + (currentMonth + 1) + "-" + currentYear; // "27-11-2020"
注:「1月は0」の落とし穴は一般的ですが、普遍的ではありません。 使用を開始する前に、任意の言語(または構成形式:たとえば、cronは特に1ベース)のドキュメントを再確認する価値があります。
現在のタイムスタンプを取得する
代わりに現在のタイムスタンプを取得する場合は、新しいDateオブジェクトを作成し、getTime()メソッドを使用できます。
const currentDate = new Date(); const timestamp = currentDate.getTime();
JavaScriptでは、タイムスタンプは1970年1月1日から経過したミリ秒数です。
<IE8をサポートする予定がない場合は、 Date.now()
を使用して、新しいDateオブジェクトを作成せずにタイムスタンプを直接取得できます。
日付の解析
文字列をJavaScriptの日付オブジェクトに変換するには、さまざまな方法があります。
Dateオブジェクトのコンストラクターは、さまざまな日付形式を受け入れます。
const date1 = new Date("Wed, 27 July 2016 13:30:00"); const date2 = new Date("Wed, 27 July 2016 07:45:00 UTC"); const date3 = new Date("27 July 2016 13:30:00 UTC+05:45");
JSは任意の日付の曜日を判別できるため、曜日を含める必要がないことに注意してください。
年、月、日、時間、分、秒を個別の引数として渡すこともできます。
const date = new Date(2016, 6, 27, 13, 30, 0);
もちろん、いつでもISO日付形式を使用できます。
const date = new Date("2016-07-27T07:45:00Z");
ただし、タイムゾーンを明示的に指定しないと、問題が発生する可能性があります。
const date1 = new Date("25 July 2016"); const date2 = new Date("July 25, 2016");
これらのいずれかにより、2016年7月25日00:00:00現地時間が表示されます。
ISO形式を使用する場合、時刻とタイムゾーンではなく日付のみを指定しても、タイムゾーンはUTCとして自動的に受け入れられます。
この意味は:
new Date("25 July 2016").getTime() !== new Date("2016-07-25").getTime() new Date("2016-07-25").getTime() === new Date("2016-07-25T00:00:00Z").getTime()
日付のフォーマット
幸い、最新のJavaScriptには、日付の書式設定を簡単な操作にする、標準のIntl
名前空間に組み込まれた便利な国際化関数がいくつかあります。
このためには、出力設定で初期化されたDate
とIntl.DateTimeFormat
の2つのオブジェクトが必要です。 アメリカ(M / D / YYYY)形式を使用するとすると、次のようになります。
const firstValentineOfTheDecade = new Date(2020, 1, 14); // 1 for February const enUSFormatter = new Intl.DateTimeFormat('en-US'); console.log(enUSFormatter.format(firstValentineOfTheDecade)); // 2/14/2020
代わりに、オランダ語(D / M / YYYY)形式が必要な場合は、異なるカルチャコードをDateTimeFormat
コンストラクターに渡すだけです。
const nlBEFormatter = new Intl.DateTimeFormat('nl-BE'); console.log(nlBEFormatter.format(firstValentineOfTheDecade)); // 14/2/2020
または、月の名前が綴られた、より長い形式のアメリカの形式:
const longEnUSFormatter = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); console.log(longEnUSFormatter.format(firstValentineOfTheDecade)); // February 14, 2020
さて、月の日に適切な順序形式、つまり「14」ではなく「14日」が必要な場合、残念ながら、これには少し回避策が必要です。これは、この記事の執筆時点でのday
の唯一の有効な値が"numeric"
または"2-digit"
。 FlavioCopesのバージョンのMathiasBynensのコードを借りて、 Intl
の別の部分を活用するために、 formatToParts()
を介して出力される日をカスタマイズできます。
const pluralRules = new Intl.PluralRules('en-US', { type: 'ordinal' }) const suffixes = { 'one': 'st', 'two': 'nd', 'few': 'rd', 'other': 'th' } const convertToOrdinal = (number) => `${number}${suffixes[pluralRules.select(number)]}` // At this point: // convertToOrdinal("1") === "1st" // convertToOrdinal("2") === "2nd" // etc. const extractValueAndCustomizeDayOfMonth = (part) => { if (part.type === "day") { return convertToOrdinal(part.value); } return part.value; }; console.log( longEnUSFormatter.formatToParts(firstValentineOfTheDecade) .map(extractValueAndCustomizeDayOfMonth) .join("") ); // February 14th, 2020
残念ながら、この記事の執筆時点では、 formatToParts
はInternet Explorer(IE)でまったくサポートされていませんが、他のすべてのデスクトップ、モバイル、およびバックエンド(つまり、Node.js)テクノロジはサポートしています。 IEをサポートする必要があり、序数が絶対に必要な場合は、以下の補足記事(またはより適切な日付ライブラリ)が答えを提供します。
バージョン11より前のIEのような古いブラウザをサポートする必要がある場合、PythonやPHPのstrftime
のような標準の日付フォーマット関数がなかったため、JavaScriptでの日付フォーマットはより困難です。
たとえば、PHPの場合、関数strftime("Today is %b %d %Y %X", mktime(5,10,0,12,30,99))
は、 Today is Dec 30 1999 05:10:00
30199905:10:00を提供します。
%
で始まる文字のさまざまな組み合わせを使用して、さまざまな形式で日付を取得できます。 (注意してください。すべての言語が各文字に同じ意味を割り当てるわけではありません。特に、「M」と「m」は数分と数か月間交換される場合があります。)
使用する形式が確実な場合は、上記で説明したJavaScript関数を使用して個々のビットを抽出し、自分で文字列を作成することをお勧めします。
var currentDate = new Date (); var date = currentDate.getDate(); var month = currentDate.getMonth(); var year = currentDate.getFullYear();
日付はMM/DD/YYYY形式で次のように取得できます。
var monthDateYear = (month+ 1 ) + "/" + date + "/" + year;
このソリューションの問題は、月と日が1桁である場合と、2桁である場合があるため、日付の長さが一貫しない可能性があることです。 これは、たとえば、日付が整列していないためにテーブル列に日付を表示している場合に問題になる可能性があります。
これに対処するには、先行0を追加する「パッド」機能を使用します。
function pad ( n ) { return n< 10 ? '0' +n : n; }
これで、次を使用してMM / DD/YYYY形式で正しい日付を取得できます。
var mmddyyyy = pad(month + 1 ) + "/" + pad(date) + "/" + year;
代わりにDD-MM-YYYYが必要な場合、プロセスは次のようになります。
var ddmmyyyy = pad(date) + "-" + pad(month + 1 ) + "-" + year;
アンティを上げて、日付を「月日、年」形式で印刷してみましょう。 月のインデックスを名前にマッピングする必要があります。
var monthNames = [ "January" , "February" , "March" , "April" , "May" , "June" , "July" , "August" , "September" , "October" , "November" , "December" ]; var dateWithFullMonthName = monthNames[month] + " " + pad(date) + ", " + year;
日付を2013年1月1日として表示するのが好きな人もいます。問題ありません。必要なのは、1を1、12を12、103を103などに返すヘルパー関数ordinal
だけで、残りは単純です。
var ordinalDate = ordinal(date) + " " + monthNames[month] + ", " + year;
日付オブジェクトから曜日を簡単に判別できるので、次のように追加しましょう。
var daysOfWeek = [ "Sun" , "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" ]; ordinalDateWithDayOfWeek = daysOfWeek[currentDate.getDay()] + ", " + ordinalDate;
ここでのより大きなポイントは、日付から数値を抽出すると、フォーマットは主に文字列に関連するということです。
日付形式の変更
日付を解析してフォーマットする方法がわかれば、日付をあるフォーマットから別のフォーマットに変更するには、2つを組み合わせるだけです。
たとえば、2013年7月21日の形式の日付があり、形式を2013年7月21日に変更したい場合は、次のように実行できます。
const myDate = new Date("Jul 21, 2013"); const dayOfMonth = myDate.getDate(); const month = myDate.getMonth(); const year = myDate.getFullYear(); function pad(n) { return n<10 ? '0'+n : n } const ddmmyyyy = pad(dayOfMonth) + "-" + pad(month + 1) + "-" + year; // "21-07-2013"
JavaScript日付オブジェクトのローカリゼーション関数の使用
上で説明した日付フォーマットメソッドはほとんどのアプリケーションで機能するはずですが、本当に日付のフォーマットをローカライズしたい場合は、 Date
オブジェクトのtoLocaleDateString()
メソッドを使用することをお勧めします。
const today = new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', });
… 26 Jul 2016
のようなものを提供してくれます。
ロケールを「en-US」に変更すると、代わりに「2016年7月26日」になります。 フォーマットがどのように変更されたかに注意してください。ただし、表示オプションは同じままです。これは非常に便利な機能です。 前のセクションで示したように、新しいIntl.DateTimeFormat
ベースの手法はこれと非常によく似ていますが、フォーマッタオブジェクトを再利用できるため、オプションを1回設定するだけで済みます。
toLocaleDateString()
を使用すると、コンピューターで出力が正常に見える場合でも、常にフォーマットオプションを渡すことをお勧めします。 これにより、月の名前が非常に長い予期しないロケールに侵入したり、月の名前が短いために見苦しく見えたりすることからUIを保護できます。
代わりに1か月の「7月」が必要な場合は、オプションの月のパラメーターを「long」に変更するだけです。 JavaScriptがすべてを処理してくれます。 en-USの場合、2016年7月26日を取得します。
注:ブラウザーでユーザーのロケールを自動的に使用する場合は、最初のパラメーターとして「undefined」を渡すことができます。

日付の数値バージョンを表示したいが、異なるロケールのMM / DD/YYYYとDD/MM / YYYYを混同したくない場合は、次の簡単な解決策をお勧めします。
const today = new Date().toLocaleDateString(undefined, { day: 'numeric', month: 'numeric', year: 'numeric', });
私のコンピューターでは、これは2016年7月26日を出力7/26/2016
。 月と日付が2桁であることを確認する場合は、オプションを変更するだけです。
const today = new Date().toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric', });
これは2016年7月26日を出力07/26/2016
。 まさに私たちが欲しかったもの!
他の関連機能を使用して、時刻と日付の両方の表示方法をローカライズすることもできます。
コード | 出力 | 説明 |
---|---|---|
| 「午前4時21分38秒」 | 時間のみのローカライズ版を表示 |
| 「04:21:38AM」 | 提供されたオプションに基づいてローカライズされた時間を表示する |
| 「2016年7月22日、午前4時21分38秒」 | ユーザーのロケールの日付と時刻を表示する |
| 「2016年7月22日、午前4時21分」 | 提供されたオプションに基づいてローカライズされた日付と時刻を表示する |
相対的な日付と時刻の計算
JavaScriptの日付に20日を追加する例を次に示します(つまり、既知の日付の20日後の日付を計算します)。
const myDate = new Date("July 20, 2016 15:00:00"); const nextDayOfMonth = myDate.getDate() + 20; myDate.setDate(nextDayOfMonth); const newDate = myDate.toLocaleString();
元の日付オブジェクトは7月20日から20日後の日付を表し、 newDate
にはその日付を表すローカライズされた文字列が含まれています。 私のブラウザでは、 newDate
に「2016年8月9日午後3時00分00秒」が含まれています。
1日よりも正確な差のある相対タイムスタンプを計算するには、 Date.getTime()
およびDate.setTime()
を使用して、特定のエポック(つまり、1970年1月1日)からのミリ秒数を表す整数を処理できます。たとえば、今から17時間後のことを知りたい場合は、次のようにします。
const msSinceEpoch = (new Date()).getTime(); const seventeenHoursLater = new Date(msSinceEpoch + 17 * 60 * 60 * 1000);
日付の比較
日付に関連する他のすべてと同様に、日付の比較には独自の落とし穴があります。
まず、日付オブジェクトを作成する必要があります。 幸い、<、>、<=、および>=はすべて機能します。 したがって、2014年7月19日と2014年7月18日を比較するのは次のように簡単です。
const date1 = new Date("July 19, 2014"); const date2 = new Date("July 28, 2014"); if(date1 > date2) { console.log("First date is more recent"); } else { console.log("Second date is more recent"); }
同じ日付を表す2つの日付オブジェクトは2つの異なる日付オブジェクトであり、等しくないため、等しいかどうかを確認するのは難しいです。 たとえば、「2014年7月20日」と「2014年7月20日」は同じ日付を表しますが、文字列の表現が異なるため、日付文字列を比較することはお勧めできません。 以下のスニペットは、最初のポイントを示しています。
const date1 = new Date("June 10, 2003"); const date2 = new Date(date1); const equalOrNot = date1 == date2 ? "equal" : "not equal"; console.log(equalOrNot);
これはnot equal
出力になります。
この特定のケースは、次のように日付(タイムスタンプ)に相当する整数を比較することで修正できます。
date1.getTime() == date2.getTime()
この例は多くの場所で見ましたが、通常は別の日付オブジェクトから日付オブジェクトを作成しないため、これは好きではありません。 ですから、この例は学術的な観点からのみ重要だと思います。 また、これには、両方のDateオブジェクトがまったく同じ秒を参照している必要がありますが、同じ日、時間、または分を参照しているかどうかだけを知りたい場合があります。
より実用的な例を見てみましょう。 ユーザーが入力した誕生日が、APIから取得した幸運な日付と同じであるかどうかを比較しようとしています。
const userEnteredString = "12/20/1989"; // MM/DD/YYYY format const dateStringFromAPI = "1989-12-20T00:00:00Z"; const dateFromUserEnteredString = new Date(userEnteredString) const dateFromAPIString = new Date(dateStringFromAPI); if (dateFromUserEnteredString.getTime() == dateFromAPIString.getTime()) { transferOneMillionDollarsToUserAccount(); } else { doNothing(); }
両方とも同じ日付を表していますが、残念ながら、ユーザーは数百万ドルを受け取ることはありません。
問題は次のとおりです。JavaScriptは、明示的に指定されていない限り、常にブラウザが提供するタイムゾーンであると想定します。
つまり、私にとって、 new Date ("12/20/1989")
は、1989-12-20T00:00:00 + 5:45または1989-12-19T18:15:00Zと同じではない日付を作成します。タイムスタンプの観点から1989-12-20T00:00:00Z。
既存の日付オブジェクトのタイムゾーンだけを変更することはできないため、ターゲットは新しい日付オブジェクトを作成することですが、ローカルタイムゾーンではなくUTCを使用します。
ユーザーのタイムゾーンを無視し、日付オブジェクトの作成時にUTCを使用します。 それを行うには2つの方法があります。
- ユーザーが入力した日付からISO形式の日付文字列を作成し、それを使用してDateオブジェクトを作成します。 有効なISO日付形式を使用して、UTCとローカルの意図を非常に明確にしながらDateオブジェクトを作成します。
const userEnteredDate = "12/20/1989"; const parts = userEnteredDate.split("/"); const userEnteredDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const userEnteredDateObj = new Date(userEnteredDateISO + "T00:00:00Z"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateObj.getTime() == dateFromAPI.getTime(); // true
これは、時刻がデフォルトで深夜(00:00:00Z)になるため、時刻を指定しない場合にも機能します。
const userEnteredDate = new Date("1989-12-20"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDate.getTime() == dateFromAPI.getTime(); // true
注意:日付コンストラクターにYYYY-MM-DDの正しいISO日付形式の文字列が渡されると、UTCが自動的に想定されます。
- JavaScriptは、日付のUTCタイムスタンプを取得するために使用できる適切なDate.UTC()関数を提供します。 日付からコンポーネントを抽出し、それらを関数に渡します。
const userEnteredDate = new Date("12/20/1989"); const userEnteredDateTimeStamp = Date.UTC(userEnteredDate.getFullYear(), userEnteredDate.getMonth(), userEnteredDate.getDate(), 0, 0, 0); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateTimeStamp == dateFromAPI.getTime(); // true ...
2つの日付の違いを見つける
遭遇する一般的なシナリオは、2つの日付の違いを見つけることです。
2つのユースケースについて説明します。
2つの日付の間の日数を見つける
両方の日付をUTCタイムスタンプに変換し、ミリ秒単位で差を見つけ、同等の日を見つけます。
const dateFromAPI = "2016-02-10T00:00:00Z"; const now = new Date(); const datefromAPITimeStamp = (new Date(dateFromAPI)).getTime(); const nowTimeStamp = now.getTime(); const microSecondsDiff = Math.abs(datefromAPITimeStamp - nowTimeStamp); // Math.round is used instead of Math.floor to account for certain DST cases // Number of milliseconds per day = // 24 hrs/day * 60 minutes/hour * 60 seconds/minute * 1000 ms/second const daysDiff = Math.round(microSecondsDiff / (1000 * 60 * 60 * 24)); console.log(daysDiff);
生年月日からユーザーの年齢を見つける
const birthDateFromAPI = "12/10/1989";
注:非標準の形式があります。 APIドキュメントを読んで、これが10月12日を意味するのか12月10日を意味するのかを判断します。それに応じてISO形式に変更します。
const parts = birthDateFromAPI.split("/"); const birthDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const birthDate = new Date(birthDateISO); const today = new Date(); let age = today.getFullYear() - birthDate.getFullYear(); if(today.getMonth() < birthDate.getMonth()) { age--; } if(today.getMonth() == birthDate.getMonth() && today.getDate() < birthDate.getDate()) { age--; }
このコードを書くにはもっと簡潔な方法があることは知っていますが、ロジックが非常に明確であるため、この方法で書くのが好きです。
日付地獄を避けるための提案
日付の計算に慣れてきたので、従うべきベストプラクティスとそれに従う理由を理解できるようになりました。
ユーザーからDateTimeを取得する
ユーザーから日付と時刻を取得している場合は、おそらくローカルのDateTimeを探しています。 日付演算のセクションで、 Date
コンストラクターがさまざまな方法で日付を受け入れることができることを確認しました。
混乱を避けるために、有効な解析可能な形式の日付が既にある場合でも、常にnew Date(year, month, day, hours, minutes, seconds, milliseconds)
形式を使用して日付を作成することをお勧めします。 チーム内のすべてのプログラマーがこの単純なルールに従う場合、 Date
コンストラクターを使用できる限り明示的であるため、コードを長期的に維持するのは非常に簡単です。
クールな部分は、最後の4つのパラメーターがゼロの場合にそれらのパラメーターを省略できるバリエーションを使用できることです。 つまり、 new Date(2012, 10, 12)
new Date(2012, 10, 12, 0, 0, 0, 0)
同じです。これは、未指定のパラメーターがデフォルトでゼロになるためです。
たとえば、日付2012-10-12と時刻12:30を提供する日付と時刻のピッカーを使用している場合、次のようにパーツを抽出して新しいDateオブジェクトを作成できます。
const dateFromPicker = "2012-10-12"; const timeFromPicker = "12:30"; const dateParts = dateFromPicker.split("-"); const timeParts = timeFromPicker.split(":"); const localDate = new Date(dateParts[0], dateParts[1]-1, dateParts[2], timeParts[0], timeParts[1]);
ISO日付形式でない限り、文字列から日付を作成しないようにしてください。 代わりに、Date(年、月、日付、時間、分、秒、マイクロ秒)メソッドを使用してください。
日付のみを取得する
日付(たとえば、ユーザーの生年月日)のみを取得する場合は、形式を有効なISO日付形式に変換して、UTCに変換したときに日付が前後にシフトする可能性のあるタイムゾーン情報を削除することをお勧めします。 例えば:
const dateFromPicker = "12/20/2012"; const dateParts = dateFromPicker.split("/"); const ISODate = dateParts[2] + "-" + dateParts[0] + "-" + dateParts[1]; const birthDate = new Date(ISODate).toISOString();
忘れてしまった場合、有効なISO日付形式(YYYY-MM-DD)で入力してDate
オブジェクトを作成すると、ブラウザのタイムゾーンではなく、デフォルトでUTCになります。
日付の保存
DateTimeは常にUTCで保存してください。 常にISO日付文字列またはタイムスタンプをバックエンドに送信します。
何世代にもわたるコンピュータープログラマーは、ユーザーに正しい現地時間を見せようとする苦い経験の後で、この単純な真実に気づきました。 バックエンドに現地時間を保存することは悪い考えです。フロントエンドで現地時間への変換をブラウザに処理させることをお勧めします。
また、「1989年7月20日12:10PM」のようなDateTime文字列をバックエンドに送信してはならないことは明らかです。 タイムゾーンも送信しても、他のプログラマーが自分の意図を理解し、日付を正しく解析して保存するための労力が増えています。
DateオブジェクトのtoISOString()
またはtoJSON()
メソッドを使用して、ローカルのDateTimeをUTCに変換します。
const dateFromUI = "12-13-2012"; const timeFromUI = "10:20"; const dateParts = dateFromUI.split("-"); const timeParts = timeFromUI.split(":"); const date = new Date(dateParts[2], dateParts[0]-1, dateParts[1], timeParts[0], timeParts[1]); const dateISO = date.toISOString(); $.post("http://example.com/", {date: dateISO}, ...)
日付と時刻の表示
- RESTAPIからタイムスタンプまたはISO形式の日付を取得します。
-
Date
オブジェクトを作成します。 -
toLocaleString()
またはtoLocaleDateString()
およびtoLocaleTimeString()
メソッド、または日付ライブラリを使用して、現地時間を表示します。
const dateFromAPI = "2016-01-02T12:30:00Z"; const localDate = new Date(dateFromAPI); const localDateString = localDate.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric', }); const localTimeString = localDate.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit', });
現地時間をいつ保存する必要がありますか?
「イベントが発生したタイムゾーンを知ることが重要な場合があります。単一のタイムゾーンに変換すると、その情報が取り返しのつかないほど消去されます。
「マーケティングプロモーションを行っていて、ランチタイムにどの顧客が注文したかを知りたい場合、GMT正午に行われたように見える注文は、実際にニューヨークで朝食をとって行われたときはあまり役に立ちません。」
このような状況に遭遇した場合は、現地時間も節約する方が賢明です。 いつものように、ISO形式で日付を作成したいのですが、最初にタイムゾーンオフセットを見つける必要があります。
DateオブジェクトのgetTimeZoneOffset()
関数は、特定の現地時間に追加されたときに同等のUTC時間を与える分数を示します。 タイムゾーンオフセットであることがより明確になるため、(+-)hh:mm形式に変換することをお勧めします。
const now = new Date(); const tz = now.gettime zoneOffset();
私のタイムゾーン+05:45の場合、-345が返されます。これは反対の符号であるだけでなく、-345のような数値はバックエンド開発者にとって完全に困惑している可能性があります。 したがって、これを+05:45に変換します。
const sign = tz > 0 ? "-" : "+"; const hours = pad(Math.floor(Math.abs(tz)/60)); const minutes = pad(Math.abs(tz)%60); const tzOffset = sign + hours + ":" + minutes;
次に、残りの値を取得し、ローカルのDateTimeを表す有効なISO文字列を作成します。
const localDateTime = now.getFullYear() + "-" + pad(now.getMonth()+1) + "-" + pad(now.getDate()) + "T" + pad(now.getHours()) + ":" + pad(now.getMinutes()) + ":" + pad(now.getSeconds());
必要に応じて、UTCとローカルの日付をオブジェクトでラップできます。
const eventDate = { utc: now.toISOString(), local: localDateTime, tzOffset: tzOffset, }
これで、バックエンドで、イベントが現地時間の正午より前に発生したかどうかを確認したい場合は、日付を解析してgetHours()
関数を使用するだけで済みます。
const localDateString = eventDate.local; const localDate = new Date(localDateString); if(localDate.getHours() < 12) { console.log("Event happened before noon local time"); }
ここではtzOffset
を使用しませんでしたが、将来デバッグ目的で必要になる可能性があるため、引き続き保存します。 実際には、タイムゾーンオフセットとUTC時間のみを送信できます。 ただし、最終的にはデータベースに日付を保存する必要があり、現地時間を個別に保存すると、計算を実行して現地の日付を取得するのではなく、フィールドに基づいて直接クエリを実行できるため、現地時間も保存するのが好きです。
ローカルタイムゾーンが保存されている場合でも、特定のタイムゾーンの日付を表示したい場合があります。 たとえば、イベントの時間は、仮想の場合は現在のユーザーのタイムゾーンで、そうでない場合は物理的に発生するタイムゾーンでより意味がある場合があります。 いずれにせよ、明示的なタイムゾーン名でフォーマットするための確立されたソリューションを事前に検討する価値があります。
サーバーとデータベースの構成
常にUTCタイムゾーンを使用するようにサーバーとデータベースを構成します。 (UTCとGMTは同じものではないことに注意してください。たとえば、GMTは夏の間にBSTに切り替えることを意味する場合がありますが、UTCは決してそうしません。)
特に意図しない場合、タイムゾーンの変換がどれほど苦痛になるかはすでに見てきました。 常にUTCDateTimeを送信し、サーバーをUTCタイムゾーンに設定すると、作業が楽になります。 バックエンドコードは、タイムゾーン変換を行う必要がないため、はるかにシンプルでクリーンになります。 DateTime data coming in from servers across the world can be compared and sorted effortlessly.
Code in the back end should be able to assume the time zone of the server to be UTC (but should still have a check in place to be sure). A simple configuration check saves having to think about and code for conversions every time new DateTime code is written.
It's Time for Better Date Handling
Date manipulation is a hard problem. The concepts behind the practical examples in this article apply beyond JavaScript, and are just the beginning when it comes to properly handling DateTime data and calculations. Plus, every helper library will come with its own set of nuances—which is even true of the eventual official standard support{target=”_blank”} for these types of operations.
The bottom line is: Use ISO on the back end, and leave the front end to format things properly for the user. Professional programmers will be aware of some of the nuances, and will (all the more decidedly) use well-supported DateTime libraries on both the back end and the front end. Built-in functions on the database side are another story, but hopefully this article gives enough background to make wiser decisions in that context, too.