【Day.js】CustomParseFormatのstrict parsingでハマったので原因を調べてみた

【Day.js】CustomParseFormatのstrict parsingでハマったので原因を調べてみた

Clock Icon2025.02.27

はじめに

JavaScriptの日付処理ライブラリDay.jsには様々なプラグインが用意されています。その中の一つにCustomParseFormatというプラグインがあり、カスタム形式を指定して日付文字列を解析できます。今回、このプラグインを使う中で思わぬ箇所でつまづいたため、その仕組みや挙動を調査しました。本記事では、その内容を記録します。

困っていたこと

import dayjs from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat";
dayjs.extend(customParseFormat);

console.log(dayjs("2025-01-01T09:00:00.000+09:00", "YYYY-MM-DDTHH:mm:ss.SSSZ", true).isValid());

上記コードがローカル環境ではtrueを返すのにも関わらず、API Gateway経由で実行されるLambda関数上ではfalseが返ってきました。

なぜこのような挙動になるのか

フォーマット指定子の確認

Day.jsのCustomParseFormatプラグインで使用できるフォーマット指定子は以下のドキュメントに記載されている通りです。このドキュメントを見る限り、フォーマットは合っています。

String + Format · Day.js

日付オブジェクトの確認

isValid()を除いたコードの実行結果をログに出力してみます。

dayjs("2025-01-01T09:00:00.000+09:00", "YYYY-MM-DDTHH:mm:ss.SSSZ", true)

日付オブジェクトの中の$dInvalid Dateという文字列が入っていることがわかります。

M {
  '$L': 'en',
  '$u': undefined,
  '$d': Invalid Date,
  '$y': 2025,
  '$M': 0,
  '$D': 1,
  '$W': 3,
  '$H': 0,
  '$m': 0,
  '$s': 0,
  '$ms': 0,
  '$x': {},
  '$isDayjsObject': true
}

ちなみに、有効な文字列だと判断された場合は以下のようになります。

M {
  '$L': 'en',
  '$u': undefined,
  '$d': 2025-01-01T00:00:00.000Z,
  '$y': 2025,
  '$M': 0,
  '$D': 1,
  '$W': 3,
  '$H': 0,
  '$m': 0,
  '$s': 0,
  '$ms': 0,
  '$x': {},
  '$isDayjsObject': true
}

strict parsingの無効化

第三引数でstrict parsingを有効にするかどうか設定できます。これをfalseに設定すると、ローカル環境でもAWS環境でも結果はtrueとなりました。

dayjs("2025-01-01T09:00:00.000+09:00", "YYYY-MM-DDTHH:mm:ss.SSSZ", false).isValid()

CustomParseFormat · Day.js

CustomParseFormatの処理の確認

CustomParseFormatではstrict parsingが無効の場合、大まかに以下のような処理が行われます。

  • フォーマット文字列(YYYY-MM-DDTHH:mm:ss.SSSZ)を元に、パーサーを作成する
  • パーサーを元に、日付文字列(2025-01-01T09:00:00.000+09:00)をパースする

上述した日付オブジェクトの中の$dに、正常にパースできればパース後の日付が、失敗すればInvalid Valueが格納されます。

strict parsingが有効の場合、さらに追加で以下のような処理が行われます。

  • 日付オブジェクトとフォーマット文字列を元に、日付文字列を再構築
  • 現在のロケールとUTCとの差分を元にタイムゾーン情報を日付文字列に追加

出来上がった日付文字列が元の日付文字列と異なる場合、$dInvalid Valueが格納されます。

LambdaのデフォルトタイムゾーンはUTCのため、strict parsingを有効化した状態だと、UTCとの差分はゼロになります。そのため再構築された日付文字列に含まれるタイムゾーン情報は+00:00です。元の日付文字列に含まれるタイムゾーン情報は+09:00なので、ここで差異が発生してInvalid Valueとなります。

なお、現在のロケールとUTCとの差分を計算する際、JavaScript組み込みのDate.prototype.getTimezoneOffset()を使用しているため、Day.jsでのタイムゾーン指定は関係ありません。

Date.prototype.getTimezoneOffset() - JavaScript | MDN

対処法

  • Lambdaの環境変数でタイムゾーンを設定する

strict parsingを有効化したままチェックができます。注意点として、チェックする日付が+09:00+04:00など異なるタイムゾーンが入り混じる可能性がある場合は機能しません。また、同じLambda上の他のモジュールに意図しない副作用が発生する可能性があります。さらに、GitHub ActionsなどLambda以外の実行環境についても設定が必要です。

  • strict parsingを無効化する

タイムゾーンの影響を受けずに日付文字列が特定のフォーマットに適合するかどうか検証できます。ただし、new Date()に適合する値であれば通るので、予期しない形式のデータを受け入れる可能性があります。例えば、以下の日付文字列はtrueとなります。(HHが99)

dayjs('2025-01-01T99:00:00.000+09:00', 'YYYY-MM-DDTHH:mm:ss.SSSZ', false).isValid()
  • Day.js以外の手段でチェックを行う

他の日付ライブラリや正規表現を使ってチェックを行います。実装方法によってはコストがかかる場合があります。

  • データフォーマットの再検討

データベースにはUTC形式の日付を格納し、表示時にローカライズすることは、日付処理における環境依存の問題を避けるために有効な対処法です。Amazon Qにクラウド環境での日付処理のベストプラクティスを尋ねたところ、UTCを使用することを推奨していました。

Use UTC: Always store and process dates in Coordinated Universal Time (UTC) to avoid timezone-related issues. This is especially important when dealing with distributed systems or global applications.

おわりに

クラウド環境はローカル環境とタイムゾーンが異なることが多く、日付処理に関して想定外の挙動になることがよくあります。ローカル環境でのテストだけでなく、本番想定の環境で動作確認を行うこと、また日付処理の設計に関して綿密に検討することが望ましいと考えられます。

参考

https://github.com/iamkun/dayjs/blob/dev/src/plugin/customParseFormat/index.js

https://github.com/iamkun/dayjs/blob/dev/src/index.js#L262

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.