Open1

nodejsでSystemのTimezoneがUTCのDateを扱うときのあれこれ

tkowtkow

yyyymmdd変換

何が困るか

システムのTZがUTCの時に、現在の時刻を(2022年9月16日00:00:00)としてJSTの日付(yyyymmdd)を取り出そうとして以下のようなコードを書くとする。

const now = new Date(2022, 8 , 15 , 15, 0, 0) // new Date()の時刻を具体的に仮定。UTCでは、2022年9月15日15:00を取得したことになる
console.log(now.getFullYear() * 10000 + (now.getMonth() + 1) * 100 + now.getDate())
// 結果: 20220915

結果を見てもらえれば分かるが、本来20220916を日付として表示したいのだが、JSTにおける時間はUTCにおける15:00~24:00の間に日付が1日ずれてしまう。

解決策

サーバにUTCがTZとして設定されている場合

process.env.TZ = 'Asia/Tokyo'

で解決するべきかどうかは熟考したほうがよい。

理由

  1. ホスティングサービスや特定のRuntime上で変更がサポートされないことがある(Jest, windows環境等)
  2. nodejsのバージョン依存で問題が起きるケースがある

可能性は低いもののnodejsおよび使用している環境で破壊的な変更が生じる可能性があり、知らない間にプロダクション環境で日付のTZが無効になったことにより、重大な障害やデータの不整合を引き起こす可能性があり大きな危険をはらんでいる。(ただし、Dateをあまりに多用している場合にはTimezoneを設定することでコードベースを変更しなくてもいいためリスクを鑑みた上で設定するのも悪くはない。)

よって、汎用的にUTCで設定されているDateオブジェクトからJSTを計算して、UTCのDateのオブジェクトとして再初期化してあげることによって、JSTのDateオブジェクトを擬似的に再現するとよいだろう。(参考

export function convertAsiaTokyoLocaleDate(org: Date): Date {
    // WARNING: UTCに強制的にJST時間を読み込ませて擬似的にJST時間として扱わせる方法。そのため、UTCおよび、JST環境以外で実行するとバグるので注意。
    const japanLocaleString = org.toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' });
    return new Date(japanLocaleString);
}

これで先ほどのコードのnowの部分をconvertAsiaTokyoLocaleDate(now)で扱うことによって適切なyyyymmddを計算できる。
この方法であっても自分が利用しているタイムゾーンを把握している必要があるが、JSTの時刻をJSTのDateオブジェクトの初期化に利用しても同じ時刻で初期化されるだけなので比較的リスクは少ない。

違うロケールを経由する場合

変換先のロケールを基準にoffsetの差分をとって時刻を入力する。特殊な環境でない限りユースケースはないだろうからコードは省略。可能な限りUTCを基準にすべき。
時差を元に何かしたいアプリケーションの場合は、こちらは汎用的に使えるロジックなのでこちらを採用するとよい。ここまで必要ならライブラリをつかったほうがよいだろう。