問題点
JavaScript/TypeScriptで日時情報を扱う場合、よくDate
が用いられます。
そしてDate
を一定の書式(ここではISO 8601拡張形式)で文字列に変換するには、Date.toISOString()
を用います。
しかしながら、このDate.toISOString()
は「日時を常にUTCで吐き出す」という仕様1になっており、例えば
console.log(new Date('2021-01-30T00:00:00+09:00').toISOString());
を実行しても
2021-01-29T21:00:00Z
のように、元々のタイムゾーン情報が失われてしまいます。これはUTC以外のタイムゾーン環境下2においては下記のような点で問題を抱えています。
- 出力された時刻が直感と一致せず、いちいち脳内やプログラムで変換しなければならない
- タイミングによっては日付がずれてしまうこともあり、何かと不便
- etc...
そこで、Date.toISOString()
と同じフォーマット(ISO 8601拡張形式)でタイムゾーン情報付きの文字列を出力させるためのメモです。
解決法
タイムゾーン情報付きのISO 8601拡張形式で出力する関数を自分で書くことで解決します。
Date
には日時を任意のフォーマットで文字列に変換する関数がないため、下記のような泥臭い方法になっています。
TypeScript
function toISOStringWithTimezone(date: Date): string {
const pad = function (str: string): string {
return ('0' + str).slice(-2);
}
const year = (date.getFullYear()).toString();
const month = pad((date.getMonth() + 1).toString());
const day = pad(date.getDate().toString());
const hour = pad(date.getHours().toString());
const min = pad(date.getMinutes().toString());
const sec = pad(date.getSeconds().toString());
const tz = -date.getTimezoneOffset();
const sign = tz >= 0 ? '+' : '-';
const tzHour = pad((tz / 60).toString());
const tzMin = pad((tz % 60).toString());
return `${year}-${month}-${day}T${hour}:${min}:${sec}${sign}${tzHour}:${tzMin}`;
}
JavaScript
function toISOStringWithTimezone(date) {
const pad = function (str) {
return ('0' + str).slice(-2);
};
const year = (date.getFullYear()).toString();
const month = pad((date.getMonth() + 1).toString());
const day = pad(date.getDate().toString());
const hour = pad(date.getHours().toString());
const min = pad(date.getMinutes().toString());
const sec = pad(date.getSeconds().toString());
const tz = -date.getTimezoneOffset();
const sign = tz >= 0 ? '+' : '-';
const tzHour = pad((tz / 60).toString());
const tzMin = pad((tz % 60).toString());
return `${year}-${month}-${day}T${hour}:${min}:${sec}${sign}${tzHour}:${tzMin}`;
}
実行結果
console.log(toISOStringWithTimezone(new Date('2021-01-30T00:00:00+09:00')));
を実行すると
2021-01-30T00:00:00+09:00
と、きちんとタイムゾーン情報を保持したまま出力できました。
非推奨のおまけ
下記のようにDate.prototype.toISOString
をオーバライドする方法をとることで、Date.toISOString()
の結果にタイムゾーン情報を含めることが可能です。
しかし「元の関数の定義から乖離してしまうという」という点で保守性の低下やバグの原因になりかねないので、この方法を取るか否か慎重に検討すべきでしょう。
TypeScript
Date.prototype.toISOString = function(): string {
const pad = function (str: string): string {
return ('0' + str).slice(-2);
}
const year = (this.getFullYear()).toString();
const month = pad((this.getMonth() + 1).toString());
const day = pad(this.getDate().toString());
const hour = pad(this.getHours().toString());
const min = pad(this.getMinutes().toString());
const sec = pad(this.getSeconds().toString());
const tz = -this.getTimezoneOffset();
const sign = tz >= 0 ? '+' : '-';
const tzHour = pad((tz / 60).toString());
const tzMin = pad((tz % 60).toString());
return `${year}-${month}-${day}T${hour}:${min}:${sec}${sign}${tzHour}:${tzMin}`;
};
JavaScript
Date.prototype.toISOString = function () {
const pad = function (str) {
return ('0' + str).slice(-2);
};
const year = (this.getFullYear()).toString();
const month = pad((this.getMonth() + 1).toString());
const day = pad(this.getDate().toString());
const hour = pad(this.getHours().toString());
const min = pad(this.getMinutes().toString());
const sec = pad(this.getSeconds().toString());
const tz = -this.getTimezoneOffset();
const sign = tz >= 0 ? '+' : '-';
const tzHour = pad((tz / 60).toString());
const tzMin = pad((tz % 60).toString());
return `${year}-${month}-${day}T${hour}:${min}:${sec}${sign}${tzHour}:${tzMin}`;
};