LoginSignup
107
59

More than 3 years have passed since last update.

JavaScript: date-fnsでタイムゾーンを扱う

Last updated at Posted at 2020-07-29

この投稿では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秒をnumber0と規定し、その既定値からの相対的な秒数をラップして、日時処理の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関数は何をするかというと、zxのようなタイムゾーン書式に指定されたタイムゾーンを出すことを行ってくれます。

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オブジェクトを日本時間の日時文字列にフォーマットする場合は、次の手順を踏みます:

  1. 一旦、date-fns-tzのutcToZonedTime関数で、UTCなDateオブジェクトを日本時間にずらしたDateオブジェクトに変換する。
  2. 変換後の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" ← 日本時間の表記
107
59
3

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
107
59