この投稿ではJavaScriptの日時ライブラリdate-fnsでタイムゾーンを扱う方法を説明します。
基本知識
date-fnsはJavaScriptのDateを扱うヘルパー関数のセットなので、そもそもDate
についてよく知っておく必要があります。
JavaScriptのDate
にはタイムゾーンを表すデータが無い
JavaScriptのDate
オブジェクトはタイムゾーンを表すデータを持ちません。
実行環境のタイムゾーン設定を変更したとしても、new Date()
はUTC時刻になります:
$ TZ=Asia/Tokyo node -e 'console.log(new Date())'
2020-07-29T00:27:44.573Z
$ TZ=UTC node -e 'console.log(new Date())'
2020-07-29T00:27:50.167Z
Date
のコンストラクタはISO 8601の表記をパースできますが、タイムゾーン指定子(+09:00
など)をつけたとしても、UTC時刻に直されます:
console.log(new Date('2000-01-01T00:00:00+09:00'))
//=> 1999-12-31T15:00:00.000Z
Date
はUTC時刻の1970年1月1日0時0分0秒をnumber
の0
と規定し、その既定値からの相対的な秒数をラップして、日時処理のAPIを生やしたような素朴なオブジェクトなので、タイムゾーンを表すデータを持たないのも理解できるかと思います。
date-fnsでのタイムゾーンの扱い
date-fnsとdate-fns-tz
date-fnsの姉妹パッケージにdate-fns-tzがあります。date-fns-tzはタイムゾーンを扱うためのパッケージですが、これが必要となるケースと不要なケースがあります。個別に見ていきます。
ISO 8601の表記をパースする
タイムゾーン指定子がある場合
タイムゾーン指定子を持ったISO 8601形式の日時のパースは、date-fnsのparseISO
でできます。date-fns-tzは不要です。パースされた日時はUTCになります:
import { parseISO } from 'date-fns'
const utcDate: Date = parseISO('2000-01-01T00:00:00+09:00')
console.log(utcDate)
//=> 1999-12-31T15:00:00.000Z
タイムゾーンに関して言うと、Date()
コンストラクタと同じ挙動になります:
const utcDate1: Date = parseISO('2000-01-01T00:00:00+09:00')
const utcDate2: Date = new Date('2000-01-01T00:00:00+09:00')
console.log(utcDate1.getTime() === utcDate2.getTime())
//=> true
タイムゾーン指定子が無い場合
タイムゾーン指定子を持たないISO 8601表記を、タイムゾーンを指定しながらパースする場合は、date-fns-tzのzonedTimeToUtc
を使います。関数の名が示すとおり、戻り値のDate
オブジェクトはUTC時刻になります。
import { zonedTimeToUtc } from 'date-fns-tz'
const utcDate: Date = zonedTimeToUtc('2000-01-01T00:00:00', 'Asia/Tokyo')
//=> 1999-12-31T15:00:00.000Z
もし、この場合に、parseISO
を使ってしまうと、この"2000-01-01T00:00:00"
はUTCのタイムゾーンを指すものと解釈されるので注意が必要です:
const utcDate: Date = parseISO('2000-01-01T00:00:00')
//=> 2000-01-01T00:00:00.000Z
Date
オブジェクトをフォーマットする
date-fnsのformat()
関数とは別に、date-fns-tzにはタイムゾーンを指定できるformat()
関数がありますが、この取扱には注意が必要です。
date-fns-tzのformat
はタイムゾーン指定による時刻変更はしない
format
はタイムゾーン指定による時刻変更はしません。
import { format } from 'date-fns-tz'
const utcDate = new Date('2000-01-01T00:00:00')
//=> 2000-01-01T00:00:00.000Z
format(utcDate, 'yyyy-MM-dd HH:mm:ss', { timeZone: 'Asia/Tokyo' })
//=> "2000-01-01 00:00:00"
ご覧のとおり、UTCの0時は日本時間で9時のはずですが、フォーマットされた時刻は0時のままです。timeZone
を"UTC"
にすると全く同じ文字列にフォーマットされることから、タイムゾーン指定による時間帯の変更はこの関数では行われないことが分かります:
format(utcDate, 'yyyy-MM-dd HH:mm:ss', { timeZone: 'UTC' })
//=> "2000-01-01 00:00:00"
format(utcDate, 'yyyy-MM-dd HH:mm:ss', { timeZone: 'Asia/Tokyo' })
//=> "2000-01-01 00:00:00"
ではdate-fns-tzのformat
関数は何をするかというと、z
やx
のようなタイムゾーン書式に指定されたタイムゾーンを出すことを行ってくれます。
format(utcDate, 'yyyy-MM-dd HH:mm:ss xxx', { timeZone: 'UTC' })
//=> "2000-01-01 00:00:00 +00:00"
format(utcDate, 'yyyy-MM-dd HH:mm:ss xxx', { timeZone: 'Asia/Tokyo' })
//=> "2000-01-01 00:00:00 +09:00"
したがって、タイムゾーンを書式化する必要がない場合は、date-fns-tzのformat
関数を使う理由はありません。
UTCなDate
をAsia/Tokyoの日時にフォーマットする
UTCなDate
オブジェクトを日本時間の日時文字列にフォーマットする場合は、次の手順を踏みます:
- 一旦、date-fns-tzの
utcToZonedTime
関数で、UTCなDate
オブジェクトを日本時間にずらしたDate
オブジェクトに変換する。 - 変換後の
Date
オブジェクトをformat
関数でフォーマットする
import { format } from 'date-fns'
import { utcToZonedTime } from 'date-fns-tz'
const utcDate = new Date('2000-01-01T00:00:00')
//=> 2000-01-01T00:00:00.000Z
const jstDate = utcToZonedTime(utcDate, 'Asia/Tokyo')
//=> 2000-01-01T09:00:00.000Z
const jstString = format(jstDate, 'yyyy-MM-dd HH:mm:ss')
//=> "2000-01-01 09:00:00"
このformat
関数はdate-fnsのものであることに注意してください。もし、タイムゾーン表記をフォーマット後の文字列に含める場合は、date-fns-tzのほうのformat
関数をtimeZone
オプションを指定しつつ使う必要があります。誤って、date-fnsのformat
関数を使うとUTCのタイムゾーン表記になってしまいます:
import { format } from 'date-fns'
import { format as formatTZ } from 'date-fns-tz'
format(jstDate, 'yyyy-MM-dd HH:mm:ss xxx')
//=> "2000-01-01 09:00:00 +00:00" ← UTCの表記
formatTZ(jstDate, 'yyyy-MM-dd HH:mm:ss xxx', { timeZone: 'Asia/Tokyo' })
//=> "2000-01-01 09:00:00 +09:00" ← 日本時間の表記