14
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

JS/TSでタイムゾーン情報付きの`Date.toISOString()`を作成する

Last updated at Posted at 2021-01-30

問題点

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}`;
};
  1. ECMAスクリプトの仕様書に曰く、"The time zone is always UTC, denoted by the suffix Z. "だそうで。

  2. e.g. 普段JSTの下で生活している我々

14
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?