date-fns-tz
Version:
Time zone support for date-fns v2 with the browser Intl API
181 lines (126 loc) • 8.38 kB
Markdown
# date-fns-tz
Time zone support for [date-fns](https://date-fns.org/) v2.0.0.
Dependency free IANA time zone support is implemented via the
[Intl API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) to keep
actual time zone data out of code bundles. Modern browsers all support the
[necessary features](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat#Browser_compatibility),
and for those that don't a [polyfill](https://github.com/yahoo/date-time-format-timezone) can be used.
If you do not wish to use a polyfill the time zone option can still be used, but only with
time zone offsets such as '-0200' or '+04:00' and not IANA time zone names.
*This work was initially proposed in PR [date-fns/#707](https://github.com/date-fns/date-fns/pull/707), but won't
be considered until `date-fns` version 2 has been released. It is my hope that these features will eventually
make it into `date-fns` or at least contribute to the conversation and that this project will be deprecated.*
## Table of Contents
- [Overview](#overview)
- [Time Zone Helpers](#time-zone-helpers)
- [`zonedTimeToUtc`](#zonedtimetoutc) - Get the UTC date/time from a date representing local time in a given time zone
- [`utcToZonedTime`](#utctozonedtime) - Get a date/time representing local time in a given time zone from the UTC date
- [Time Zone Formatting](#time-zone-formatting)
- [`format`](#format) - Extends `date-fns/format` with full time zone support
- [`toDate`](#todate) - Can be used to create a zoned Date from a string containing an offset or IANA time zone
## Overview
Working with UTC or ISO date strings is easy, and so is working with JS dates when all times
are displayed in a user's local time in the browser. The difficulty comes when working with another
time zone's local time, other than the current system's, like on a Node server or when showing the time
of an event in a specific time zone, like an event in LA at 8pm PST regardless of where a user resides.
In this case there are two relevant pieces of information:
- a fixed moment in time in the form of a timestamp, UTC or ISO date string, and
- the time zone descriptor, usually an offset or IANA time zone name (e.g. `America/New_York`).
Libraries like Moment and Luxon, which provide their own date time classes, manage these timestamp and time
zone values internally. Sine `date-fns` always returns a plain JS Date, which implicitly has the current
system's time zone, helper functions are provided for handling common time zone related use cases.
## Time Zone Helpers
To discuss the usage of the time zone helpers let's assume we're writing a system where administrators set
up events which will be start at a specific time in the venue's local time, and this local time should be
shown when accessing the site from anywhere in the world.
### `zonedTimeToUtc`
**Get a date with the correct UTC time for the date/time in a specific time zone**
```js
zonedTimeToUtc(date: Date|Number|String, timeZone: String): Date
```
Say a user is asked to input the date/time and time zone of an event. A date/time picker will typically
return a Date instance with the chosen date, in the user's local time zone, and a select input might
provide the actual IANA time zone name.
In order to work with this info effectively it is necessary to find the equivalent UTC time:
```javascript
import { zonedTimeToUtc } from 'date-fns-tz'
const date = getDatePickerValue() // e.g. 2014-06-25 10:00:00 (picked in any time zone)
const timeZone = getTimeZoneValue() // e.g. America/Los_Angeles
const utcDate = zonedTimeToUtc(date, timeZone) // In June 10am in Los Angeles is 5pm UTC
postToServer(utcDate.toISOString(), timeZone) // post 2014-06-25T17:00:00.000Z, America/Los_Angeles
```
### `utcToZonedTime`
**Get a date/time in the local time of any time zone from UTC time**
```js
utcToZonedTime(date: Date|Number|String, timeZone: String): Date
```
Say the server provided a UTC date/time and a time zone which should be used as initial values for the above form.
The date/time picker will take a Date input which will be in the user's local time zone, but the date value
must be that of the target time zone.
```javascript
import { utcToZonedTime } from 'date-fns-tz'
const { isoDate, timeZone } = fetchInitialValues() // 2014-06-25T10:00:00.000Z, America/New_York
const date = utcToZonedTime(isoDate, timeZone) // In June 10am UTC is 6am in New York (-04:00)
renderDatePicker(date) // 2014-06-25 06:00:00 (in the system time zone)
renderTimeZoneSelect(timeZone) // America/New_York
```
## Time Zone Formatting
### `format`
The `format` function exported from this library extends `date-fns/format` with full time zone support:
- The `z..zzz` Unicode tokens: *short specific non-location format*
- The `zzzz` Unicode token: *long specific non-location format*
- Using the name of any IANA time zone or offset by specifying a `timeZone` option;
when using this option the `x..xxxxx`, `X..XXXXX` and `O..OOO` tokens will also use the provided
time zone rather than the system time zone.
```javascript
import { format, utcToZonedTime } from 'date-fns-tz'
const date = new Date('2014-10-25T10:46:20Z')
const nyTimeZone = 'America/New_York'
const parisTimeZone = 'Europe/Paris'
const nyDate = utcToZonedTime(date, nyTimeZone)
const parisDate = utcToZonedTime(date, parisTimeZone)
format(nyDate, 'yyyy-MM-dd HH:mm:ssXXX', { timeZone: 'America/New_York' }) // 2014-10-25 06:46:20-04:00
format(nyDate, 'yyyy-MM-dd HH:mm:ss zzz', { timeZone: 'America/New_York' }) // 2014-10-25 06:46:20 EST
format(parisDate, 'yyyy-MM-dd HH:mm:ss zzz', { timeZone: 'Europe/Paris' }) // 2014-10-25 10:46:20 GMT+2
// The time zone name is generated by the Intl API which works best when a locale is also provided
import enGB from 'date-fns/locale/en-GB'
enGB.code = 'en-GB'
format(parisDate, 'yyyy-MM-dd HH:mm:ss zzz', { timeZone: 'Europe/Paris', locale: enGB })
// 2014-10-25 10:46:20 CEST
format(parisDate, 'yyyy-MM-dd HH:mm:ss zzzz', { timeZone: 'Europe/Paris', locale: enGB })
// 2014-10-25 10:46:20 Central European Summer Time
```
**Caveat:** Note that when using a locale the language code of the locale should be added to the import
somewhere in the project so `format` can identify the locale. Once this library is absorbed into `date-fns`
this can be added to each locale natively.
### `toDate`
The `toDate` function can be used to create a zoned Date from a string containing an offset or IANA
time zone, or by providing the `timeZone` option.
```javascript
import { toDate, format } from 'date-fns-tz'
// Offsets in the date string work as usual and take precedence
const parisDate = toDate('2014-10-25T13:46:20+02:00')
format(parisDate, 'yyyy-MM-dd HH:mm:ssZ', { timeZone: 'Europe/Paris' }) // 2014-10-25 13:46:20+02:00
// Since toDate simply clones a Date instance timeZone option is effectively ignored in this case
const date = new Date('2014-10-25T13:46:20Z')
const clonedDate = toDate(date, { timeZone: 'Europe/Paris' })
assert(date.valueOf() === clonedDate.valueOf())
// When there is no offset in the date string the timeZone property is used
const bangkokDate = toDate('2014-10-25T13:46:20', { timeZone: 'Asia/Bangkok' })
format(bangkokDate, 'yyyy-MM-dd HH:mm:ssZ', { timeZone: 'Asia/Bangkok' }) // 2014-10-25 13:46:20+07:00
const nyDate = toDate('2014-10-25T13:46:20 America/New_York')
format(nyDate, 'yyyy-MM-dd HH:mm:ssZ', { timeZone: 'America/New_York' }) // 2014-10-25 13:46:20-04:00
```
**Note:** Since the Intl API does not provide a way to parse long or short time zone names the `parse`
function cannot be supported using this approach.
## Credit
The idea of using the Intl API for time zone support was inspired by the [Luxon](https://github.com/moment/luxon)
library.
The initial port of the idea into date-fns was done by [@benmccan](https://github.com/benmccann) in
[date-fns/#676](https://github.com/date-fns/date-fns/pull/676).
## Alternatives
The [`date-fns-timezone`](https://github.com/prantlf/date-fns-timezone) library provides similar functionality
for `date-fns` version 1 by bundling time zone data. This does have the advantage of making parsing time zone
long and short names possible.
## License
MIT © Marnus Weststrate