Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
38
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Organization

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

この投稿では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" ← 日本時間の表記
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
38
Help us understand the problem. What are the problem?