📆

やっとできたよ。JS 標準Date型でタイムゾーン対応の dateToString() stringToDate()

2021/06/15に公開

やっとできました。

しかし、タイムゾーンの変化する「地域」の概念はいれられず。
というか、どうやんのタイムゾーン地域データベースとかって.....

前からの続き

前といっても Qiita だけれども。

こちらの記事で、dateToString を作ってました。

https://qiita.com/standard-software/items/fe8dfcbeb3bde37bd2f4

こちらの記事で、stringToDate を作ってました。

https://qiita.com/standard-software/items/299a9d403238a93cf351

で、今回はソースコードは長過ぎるので切り出しも面倒なために npm ライブラリ側だけです。

何がかわったか。

前の記事のコードと比べて

  • タイムゾーン設定ができるようになった。
  • 月の文字列 Jan とか Feb とか対応
  • 曜日出力も対応。曜日が違う文字列は除外する。(Invalid Date が返る)
  • フォーマットにフォーマットしない文字列をクウォートで囲むことができるように。

CDN JSBin で動作確認

ブラウザ上で動作確認できます。

https://jsbin.com/posusoh/edit?html,js,console

const {
  dateToString, dateToStringUTC,
  stringToDate, stringToDateUTC,
} = parts.date;

const equalDate = (a, b) => {
  return a.getTime() === b.getTime();
}

let dt1, dt2;
dt1 = new Date(2021, 5, 15, 9, 1, 2, 3);
dt2 = new Date(Date.UTC(2021, 5, 15, 0, 1, 2, 3));
console.log(equalDate(dt1, dt2)); // true

console.log(dateToString(dt1, 'YYYY/MM/DD HH:mm:ss.SSS'));
// "2021/06/15 09:01:02.003"

console.log(dateToStringUTC(dt1, 'YYYY/MM/DD HH:mm:ss.SSS'));
// "2021/06/15 00:01:02.003"

console.log(dateToString(dt1, 'YYYY/MM/DD HH:mm:ss.SSS', -600));
// "2021/06/15 10:01:02.003"

console.log(dateToString(dt1, 'YYYY/MM/DD HH:mm:ss.SSS', 0));
// "2021/06/15 00:01:02.003"

console.log(dateToString(dt1, 'YYYY/MM/DD HH:mm:ss.SSS', +600));
// "2021/06/14 14:01:02.003"

console.log(dateToString(dt1, 'YYYY/MM/DD HH:mm:ss.SSS Z'));
// "2021/06/15 09:01:02.003 +09:00"

console.log(dateToStringUTC(dt1, 'YYYY/MM/DD HH:mm:ss.SSS Z'));
// "2021/06/15 00:01:02.003 Z"

console.log(dateToString(dt1, 'YYYY/MM/DD HH:mm:ss.SSS Z', -600));
// "2021/06/15 10:01:02.003 +10:00"

console.log(dateToString(dt1, 'YYYY/MM/DD HH:mm:ss.SSS Z', 0));
// "2021/06/15 00:01:02.003 +00:00"

console.log(dateToString(dt1, 'YYYY/MM/DD HH:mm:ss.SSS Z', +600));
// "2021/06/14 14:01:02.003 -10:00"

console.log(equalDate(dt1, stringToDate("2021/06/15 09:01:02.003", 'YYYY/MM/DD HH:mm:ss.SSS') )); // true
console.log(equalDate(dt1, stringToDateUTC("2021/06/15 00:01:02.003", 'YYYY/MM/DD HH:mm:ss.SSS') )); // true
console.log(equalDate(dt1, stringToDate("2021/06/15 10:01:02.003", 'YYYY/MM/DD HH:mm:ss.SSS', -600) )); // true
console.log(equalDate(dt1, stringToDate("2021/06/15 00:01:02.003", 'YYYY/MM/DD HH:mm:ss.SSS', 0) )); // true
console.log(equalDate(dt1, stringToDate("2021/06/14 14:01:02.003", 'YYYY/MM/DD HH:mm:ss.SSS', +600) )); // true
console.log(equalDate(dt1, stringToDate("2021/06/15 09:01:02.003 +09:00", 'YYYY/MM/DD HH:mm:ss.SSS Z') )); // true
console.log(equalDate(dt1, stringToDateUTC("2021/06/15 00:01:02.003 Z", 'YYYY/MM/DD HH:mm:ss.SSS Z') )); // true
console.log(equalDate(dt1, stringToDate("2021/06/15 10:01:02.003 +10:00", 'YYYY/MM/DD HH:mm:ss.SSS Z', -600) )); // true
console.log(equalDate(dt1, stringToDate("2021/06/15 00:01:02.003 +00:00", 'YYYY/MM/DD HH:mm:ss.SSS Z', 0) )); // true
console.log(equalDate(dt1, stringToDate("2021/06/14 14:01:02.003 -10:00", 'YYYY/MM/DD HH:mm:ss.SSS Z', +600) )); // true

console.log(equalDate(dt1, stringToDate("2021/06/15 10:01:02.003 +10:00", 'YYYY/MM/DD HH:mm:ss.SSS Z', -540) )); // true
console.log(equalDate(dt1, stringToDate("2021/06/14 14:01:02.003 -10:00", 'YYYY/MM/DD HH:mm:ss.SSS Z', +540) )); // true
// 文字列でタイムゾーン指定している場合、数値指定は無視される

console.log(dt1.toString());
// "Tue Jun 15 2021 09:01:02 GMT+0900 (日本標準時)"

console.log(dateToString(dt1, 'ddd MMM DD YYYY HH:mm:ss "GMT"ZZ'));
// "Tue Jun 15 2021 09:01:02 GMT+0900"
// 曜日や月文字列の場合も対応
// 出力文字に書式文字が入る場合はクウォート(シングルかダブルかどちらか)で囲う

console.log(equalDate(
  new Date(2021, 5, 15, 9, 1, 2, 0),
  stringToDate("Tue Jun 15 2021 09:01:02 GMT+0900", 'ddd MMM DD YYYY HH:mm:ss "GMT"ZZ')
)); // true

書式文字列

このあたりコードよむとわかるようになっています。Moment互換と少し改良したDefaultルールを設定可能。
独自拡張も可能。(ExcleVBA互換とか.NET互換とかできるんじゃないかな。気が向いたらやろうかな。)

partsjs/__stringToDateRule.js at v10.6.0 · standard-software/partsjs
https://github.com/standard-software/partsjs/blob/v10.6.0/source/date/__stringToDateRule.js#L171

partsjs/__dateToStringRule.js at v10.6.0 · standard-software/partsjs
https://github.com/standard-software/partsjs/blob/v10.6.0/source/date/__dateToStringRule.js#L211

タイムゾーンについての注意

dateToString や stringToDate は、タイムゾーンオフセットの分を設定しない時はローカルのタイムゾーンオフセット、(new Date()).getTimezoneOffset() で得られる値「とか」で動くので、ご注意ください。

「とか」という表現なのは、夏時間がある現地があるからです。今日のgetTimezoneOffsetと、設定したい日のgetTimezoneOffsetが夏時間などでずれていたりするので、そういう場合はtimezoeOffset指定にいろいろ工夫が必要。未指定の場合は現地に従う(内部ではnew Date 使ってる)ので、現地で使う限りは問題ないはず。

上記のコードも日本以外のタイムゾーンのときは9時間ずれではなくなるので動かないのですが、その現地にあわせたテストコードにする必要あり。
タイムゾーン指定した場合はローカルタイムゾーンに依存しないコードになるので動きます。

Parts.js の内部テストコードは、結構な数の地域設定に切り替えて動かしたので大丈夫だとは思います。GoogleSpread上(タイムゾーンが違う様子)でも動いています。

苦労

夏時間とか含めた、タイムゾーン周りの実装と動作確認と、PCのタイムゾーン設定を切り替えてテスト動作確認をしなきゃいけなかったのが激辛でした。
どこを自前で計算して、どこをJavaScript標準にまかせるとかも悩ましい

jest なら、タイムゾーン設定を変えてテストを動かせるらしいのでそういうのをテストコードに組み込みたい。
夏時間とか考えるとややこしいけど、jestって対応しているのかな。

感想

個人的には これで、もう Momentとかあれやらこれやらの日付ライブラリに頼らなくても自前でまかなえるかなという感じ。

それにしても、日付処理がこんなに大変だとは全くしりませんでした。
システムの日付は、日付型とかgetTimeの数値(UNIXタイムスタンプ)とかUTC文字列で保存しておくのがよいと思います。

YYYY/MM/DD とかの形式で文字列保存とかしちゃだめ。
当たり前か。

Discussion