5.9 日付と時刻、正規化、バリデーション

(1/1)
日付と時刻
プログラムで日付や時刻を扱う場面は多い。何時間後、何週間後といった計算は、日時の繰り上がり処理が面倒なので、すべてプログラムに任せたいところだ。JavaScript には、こうした日時計算を容易にする Dateオブジェクトが用意されているが、世界視点で見た場合、いくつか注意点がある。

目次

サンプル・プログラム

圧縮ファイルの内容
date2.htmlサンプル・プログラム(1)
normalizeDateTime.htmlサンプル・プログラム(2)

世界時と地方時

グリニッジ天文台
グリニッジ天文台
プログラムに入る前に、世界標準時地方時(ローカルタイム)の概念を押さえていこう。

海外旅行経験がある方は実感としてわかると思うが、世界各地の時刻には時差がある。そこで、経度0度(イギリスのグリニッジ天文台を通る経度)における時刻を世界標準時とする。
グリニッジ天文台で正午12時を迎えた時点で日本に目を移すと、そろそろ寝る時間である。これを12時というのは無理があるから、世界標準時+9時=21時としている。これが日本国内で共通に使われている時刻体系である日本標準時だ。
同様に、各国・各地域に合わせた時刻体系があり、これを地方時(ローカルタイム)と呼ぶ。
明石市立天文科学館
明石市立天文科学館
時差は、東西方向(経度方向)で働く。東西に広いアメリカ合衆国は、国内で6つの地方時をもつ。ロシアは世界最多で、11の地方時をもつ。中国も東西に広いのだが、北京時間に統一している。
さて、JavaScriptの場合、内部的には世界標準時(厳密には協定世界時 UTC)として日時情報を格納し、計算に用いる。そして、画面表示やファイル出力の時に、地方時に変換するメソッドを備えている。こうすることで、時差のある国・地域の間でのカレンダー計算も可能になる。

Dateオブジェクト

JavaScriptには、日付と時刻と一緒にして扱う Dateオブジェクトが用意されている。Dateオブジェクト1つにつき、1つの日時情報を格納することができる。

Dateオブジェクトの生成には、いくつかの方法がある。
まず、引数を何も指定しないで new Date() とすると、現在日時を格納したDateオブジェクトを生成する。このオブジェクトは生成した時点の日時を格納しており、そのままプログラムが動き続けても、時計のように日時が更新されていくわけではない。

次に、引数に年, 月, 日を指定して new Date(2024, 1, 29) とすると、2024年2月29日午前0時(地方時)を格納する。ここで、第2引数の月は0~11の整数を渡すことに留意されたい。つまり、1月なら0、2月なら1‥‥12月なら11を渡す。

3番目は、日付や時刻を文字列として渡す方法である。日時の表記には様々な形式があるが、うまく渡せるかどうかをサンプル・プログラムを使ってみてみよう。
Dateオブジェクトに日時を文字列として渡し、toLocaleStringメソッドを使って地方時を画面に表示するプログラムである。

サンプル・プログラム(1)の実行例

日付と時刻を表示する(その1)

  48:     let dtText = '';
  49:     let dt;
  50: 
  51:     //現在日時
  52:     document.getElementById('dt0A').innerHTML = '';
  53:     let dt0 = new Date();
  54:     document.getElementById('dt0B').innerHTML = dt0.toLocaleString();
  55:     //現在日時
  56:     document.getElementById('dt0C').innerHTML = '';
  57:     document.getElementById('dt0D').innerHTML = dt0.getFullYear() + '年' + (dt0.getMonth() + 1+ '月' + dt0.getDate() + '日 ' + dt0.getHours() + '時' + dt0.getMinutes() + '分';
  58:     //指定年月日
  59:     document.getElementById('dt1A').innerHTML = '2024, 1, 29';
  60:     let dt1 = new Date(2024, 1, 29);
  61:     document.getElementById('dt1B').innerHTML = dt1.toLocaleString();
  62:     //指定年月日 (ISO 8601形式)
  63:     dtText = '2024-02-29';
  64:     document.getElementById('dt2A').innerHTML = "'" + dtText + "'";
  65:     let dt2 = new Date(dtText);
  66:     document.getElementById('dt2B').innerHTML = dt2.toLocaleString();
  67:     //指定年月日 (スラッシュ区切り)
  68:     dtText = '2024/02/29';
  69:     document.getElementById('dt3A').innerHTML = "'" + dtText + "'";
  70:     let dt3 = new Date(dtText);
  71:     document.getElementById('dt3B').innerHTML = dt3.toLocaleString();
  72:     //指定年月日時分秒 (ISO 8601形式)
  73:     dtText = '2024-02-29T12:00:00Z';
  74:     document.getElementById('dt4A').innerHTML = "'" + dtText + "'";
  75:     let dt4 = new Date(dtText);
  76:     document.getElementById('dt4B').innerHTML = dt4.toLocaleString();
  77:     //指定年月日時分秒 (ISO 8601形式)
  78:     dtText = '2024-02-29T12:00:00+09:00';
  79:     document.getElementById('dt5A').innerHTML = "'" + dtText + "'";
  80:     let dt5 = new Date(dtText);
  81:     document.getElementById('dt5B').innerHTML = dt5.toLocaleString();
  82:     //指定年月日 (スラッシュ区切り)
  83:     dtText = '2024/02/29 12:00:00';
  84:     document.getElementById('dt6A').innerHTML = "'" + dtText + "'";
  85:     let dt6 = new Date(dtText);
  86:     document.getElementById('dt6B').innerHTML = dt6.toLocaleString();
  87:     //指定年月日 (ドット区切り)
  88:     dtText = '2024.02.29';
  89:     document.getElementById('dt7A').innerHTML = "'" + dtText + "'";
  90:     let dt7 = new Date(dtText);
  91:     document.getElementById('dt7B').innerHTML = dt7.toLocaleString();
  92:     //指定年月日 (英語)
  93:     dtText = 'February 29, 2024';
  94:     document.getElementById('dt8A').innerHTML = "'" + dtText + "'";
  95:     let dt8 = new Date(dtText);
  96:     document.getElementById('dt8B').innerHTML = dt8.toLocaleString();
  97:     //指定年月日 (日本語)
  98:     dtText = '2024年3月2日';
  99:     document.getElementById('dt9A').innerHTML = "'" + dtText + "'";
 100:     let dt9 = new Date(dtText);
 101:     document.getElementById('dt9B').innerHTML = dt9.toLocaleString();
 102: }

Dateオブジェクトに渡せる日時文字列は、国際標準規格である ISO 8601形式と定められている。
Windows版Chromeでは、'2024/02/29' や '2024.02.29'、'February 29, 2024' も正しい日時として扱われるが、たまたま扱えているだけで、すべてのJavaScript処理系(ブラウザ)で正しく扱えるとは限らない。

また、'2024-02-29' と '2024/02/29' の結果(地方時)が異なっている点に注意したい。
'2024-02-29' は ISO 8601形式 で2024年2月29日UTC0時を意味するので、地方時(日本標準時)はプラス9時で2024年2月29日9時となる。一方、'2024/02/29' は地方時0時と解釈される。
ただし、前述の通り、'2024/02/29' はたまたま扱えているだけなので、他のJavaScript処理系(ブラウザ)では異なる結果になるかもしれない。

いずれにしても、Dateオブジェクトに日時文字列を渡すときには、かならず ISO 8601形式にして渡すようにしよう。

ISO 8601形式による表記

ISO 8601 表記 日本語表記
2018 西暦2018年
2018-07 西暦2018年7月
2018-07-12 西暦2006年7月12日
2018-07-12T23:20Z 西暦2006年7月12日23時20分UTC
2018-07-21T23:20:52Z 西暦2006年7月21日23時20分52秒UTC
2018-07-21T23:20:50.32Z 西暦2006年7月21日23時20分50.32秒UTC
2018-07-19T16:10:40.22-08:00 西暦2006年7月19日16時10分40.22秒
(米太平洋標準時間;UTCから-8時間の時差)
2018-07-19T16:10:40.22+09:00 西暦2006年7月19日16時10分40.22秒
(日本標準時間;UTCから+9時間の時差)
2006-01-01T08:59:60+09:00 西暦2006年1月1日8時59分60秒
(日本標準時間;閏秒を示す)

サマータイム

daylight saving time = サマータイム
国や地域によっては、季節によって地方時を早めたり、逆に遅くすることがある。
たとえばイギリスでは、毎年3月の最終日曜日1時(UTC)から10月の最終日曜日1時(UTC)までの期間、UTCより1時間だけ早めるイギリス夏時間(BST;British Summer Time)を実施している。現在、サマータイムを導入している国は、ヨーロッパを中心に約70カ国ある。
イギリスでは、たとえば2018年3月25日(日)の1時になった瞬間、時計は2時を指す。そして、10月28日(日)の2時になった瞬間、時計は1時に戻る。
一方、アラスカでは、毎年4月の第一日曜日2時から10月の最終日曜日2時までをアラスカ夏時間(AKDT;Alaska Daylight Time)としている。
このように、国や地域によってサマータイムの期間が異なる

さらに、イギリスでは、第二次大戦中の1940年2月15日(木)2時(UTC)から1945年10月7日(日)2時(UTC)までの間、2時間のサマータイムを行っていた。
このように、サマータイムは1時間とは限らないこと、そして年代によっては期間や時間が異なる。ある時期にサマータイムを廃止した国もあるし、新規に導入した国もある。

わが国も、GHQの指導により、1948年4月28日に夏時刻法を公布し、サマータイムを導入した時期がある。実施期間は、1948年5月2日(日)0時~9月12日(日)1時、1949年4月3日(日)0時~1949年9月11日(日)1時、1950年5月7日(日)0時~1950年9月10日(日)1時、1951年5月6日(日)0時~1951年9月8日(土)1時という変則的なものだった。

このようにローカルな条件が多いため、ISO 8601 ではサマータイムを定義しておらず、時差で表現することを推奨している。
サマータイム表記
ISO 8601 表記 日本語表記
2018-03-25T01:30Z 西暦2018年3月25日01時30分UTC
2018-03-25T02:30+01:00 西暦2018年3月25日02時30分
(イギリス夏時間BST;UTCから+1時間の時差)

正規化処理

日付や時刻のように表記書式が統一されていないものを、たとえば ISO 8601形式といった標準書式に統一することを正規化処理と呼ぶ(正規表現とは意味が異なる)。

たとえば「2024年2月29日9時30分」という日時は、
2024/2/29 9:30
2024.02.29 09:30
2024年2月29日午前9時半
February 29 2024, 9.30am
などの様々な表記がある。これを ISO 8601形式 である '2024-02-29T09:30:00+09:00' に正規化するには一筋縄ではいかない。残念ながら、JavaScriptの標準関数、メソッドに正規化のための手段は用意されていない。

そこで、gooラボのクラウドサービス「時刻情報正規化API」を利用してみることにした。JavaScriptでクラウドサービスを利用する方法については、「第7章 WebAPIとjQuery」をご覧いただきたい。

サンプル・プログラム(2)の実行例

gooラボ 時刻情報正規化API(jQuery版)

定義域と値域

高校数学で定義域値域を習う。JavaScriptで言えば、引数として与えられるデータの範囲が定義域、戻り値が取り得る範囲が値域である。

Dateオブジェクトは、内部的に日時データを 1970年1月1日UTC0時をゼロとしたミリ秒の数値データとして格納している。数値データである以上は、「2.4 数値型の範囲と誤差」で説明したように、上限値と下限値がある。つまり、定義域がある。
JavaScriptのECMA-262規格によれば、1970年1月1日UTC0時から前後1億日を格納できると定義されている。西暦になおすと、紀元前271,821年4月20日から西暦275,760年9月13日まで Dateオブジェクトで扱うことができるということになる。
歴史上の出来事を扱う限り、Dateオブジェクトで十分そうだ。だが、ユーザーは何を入力するか分からない。悪意のある第三者が、定義域を逸脱したデータを無理矢理に入力し、プログラムの停止や、データの摂取や改竄を目論むこともある(インジェクション)。

そこで、日付・時刻をキーボードから入力したり、ファイルやインターネットのコンテンツを参照したり、他システムからデータ連携するときには、計算処理に入る前に、「1.6 入力、バリデーション、関数」で紹介したバリデーションを実施することをおすすめする。

参考サイト

(この項おわり)
header