dayjsでの西暦1000年より前の挙動について調べた

2 min read読了の目安(約2100字

環境

状況

  1. 西暦1〜99年が、1901年〜1999年に勝手に変換され入力される
  2. 西暦100〜999年の変換でエラーになる (Firefoxのみ)

原因

1について

  1. 2桁数値は西暦1901〜1999年に変換されるというJavaScriptの仕様
  2. dayjs('0099-01-01') としたとき、内部では new Date(Date.UTC('0099', 0, 1, 0, 0, 0, '000')) のように変換されている

この2つのあわせ技によって西暦1〜99年は1901〜1999年に変換されてしまう。

解法

無理やりやるとすれば、

const date = '0099-01-01'
dayjs(date).year(date.substr(0, 4))

とかか。
ただし、 MM-DD-YYYY などの YYYY の位置が可変の環境を想定して対応するのは面倒か。

追記: 2021-04-14

そういえばECMA Script的にどうなってるんだろうと思って調べたところ、以下の箇所が該当箇所の模様。

21.4.2.1 Date

ここの 5.j.ii で定義されている模様。

2について

Firefoxのみの問題。Chromeでは問題なし。

const dayjs = require('dayjs');
const utc = require('dayjs/plugin/utc');
const timezone = require('dayjs/plugin/timezone');
dayjs.extend(utc);
dayjs.extend(timezone);

dayjs('0999-01-01').startOf('hour');                  // OK
dayjs('0999-01-01').tz('Asia/Tokyo').startOf('hour'); // NG

APIとのやり取りの都合上、クライアントの時刻をUTCに変換した日時を送信する必要があったため、 dayjs での変換を噛ませていた。
通常であれば問題がない dayjs().startOf('xxx') が、 timezone プラグインを使用したときにエラーを吐くようになっていた。

dayjsのソースを追っていくと startOf('xxx')toDate('s') を呼び出しており、その toDate('s')dayjs(this.format('YYYY-MM-DD HH:mm:ss:SSS')).toDate() を行っていた。
format() の内部では JavaScript の Date().getFullYear() を呼び出しており、 Date().getFullYear()数値で年を返す。そのため、文字列 '0999' は数値 999 として解釈され、 format() の結果としては '999-01-01' が返る。しかし、Firefoxは '999-01-01' という値を有効な日付として解釈できないため、 Invalid Date になってしまう。何を言っているんだお前は。

refs.

解法

こんな感じで行けるか?(未確認)

const isoString = dayjs('0999-01-01').tz('Asia/Tokyo').toISOString();
dayjs(isoString).startOf('hour').utc().format('YYYY-MM-DDTHH:mm:ss.SSSZ');