Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

date-fns-tz

marnusw11.7mMIT3.2.0TypeScript support: included

Time zone support for date-fns v3 with the Intl API

date-fns, timezone, time zone, date, time, parse, format, immutable

readme

date-fns-tz

Time zone support for date-fns v3.0.0 using the Intl API. By using the browser API no time zone data needs to be included in code bundles. Modern browsers and Node.js all support the necessary features, and for those that don't a polyfill can be used.

If you do not wish to use a polyfill the time zones can still be specified as offsets such as '-0200' or '+04:00', but not IANA time zone names.

Note: date-fns is a peer dependency of this library.

If you find this library useful, why not

Buy Me A Coffee

ESM and CommonJS

This library supports CommonJS and native ESM imports. The exports field in package.json defines the correct entry point depending on project type, so the same import path is used for both. Make sure to set the type property in your project's package.json to either module, for ESM, or commonjs.

Even when using ESM some CommonJS imports from date-fns will be used until they support ESM natively as well date-fns#1781. This is because an ESM project cannot use ESM imports from a library that doesn't specify {"type": "module"}.

Table of Contents

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, one 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. Since 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.

Date and time zone formatting

formatInTimeZone

This function takes a Date instance in the system's local time or an ISO8601 string, and an IANA time zone name or offset string. It then formats this date in the target time zone regardless of the system's local time zone.

It supports the same format tokens as date-fns/format, and adds full support for:

  • The z..zzz Unicode tokens: short specific non-location format, e.g. EST
  • The zzzz Unicode token: long specific non-location format, e.g. Eastern Standard Time

Unlike date-fns/format, the z..zzzz, x..xxxxx, X..XXXXX and O..OOO tokens will all print the formatted value of the provided time zone rather than the system time zone.

An invalid date or time zone input will result in an Invalid Date passed to date-fns/format, which will throw a RangeError.

For most use cases this is the only function from this library you will need.

import { formatInTimeZone } from 'date-fns-tz'

const date = new Date('2014-10-25T10:46:20Z')

formatInTimeZone(date, 'America/New_York', 'yyyy-MM-dd HH:mm:ssXXX') // 2014-10-25 06:46:20-04:00
formatInTimeZone(date, 'America/New_York', 'yyyy-MM-dd HH:mm:ss zzz') // 2014-10-25 06:46:20 EST
formatInTimeZone(date, 'Europe/Paris', 'yyyy-MM-dd HH:mm:ss zzz') // 2014-10-25 12: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'

formatInTimeZone(parisDate, 'Europe/Paris', 'yyyy-MM-dd HH:mm:ss zzz', { locale: enGB })
// 2014-10-25 10:46:20 CEST

formatInTimeZone(parisDate, 'Europe/Paris', 'yyyy-MM-dd HH:mm:ss zzzz', { locale: enGB })
// 2014-10-25 10:46:20 Central European Summer Time

Time zone offset helpers

These functions are useful when you are not formatting a date yourself, but passing it to third-party code such as a date picker library alongside an input for selecting a time zone.

To discuss the usage of the time zone helpers let's assume we're writing a system where administrators set up events which will 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.

fromZonedTime

Given a date and any time zone, returns the equivalent Date in the current system time zone and the equivalent UTC time internally. An invalid date string or time zone will result in an Invalid Date.

fromZonedTime(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:

import { fromZonedTime } 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 = fromZonedTime(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

toZonedTime

Returns a Date which will format as the local time of any time zone from a specific UTC time or date in the current system time zone. An invalid date string or time zone will result in an Invalid Date.

toZonedTime(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.

import { toZonedTime } from 'date-fns-tz'

const { isoDate, timeZone } = fetchInitialValues() // 2014-06-25T10:00:00.000Z, America/New_York

const date = toZonedTime(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

getTimezoneOffset

Returns the offset in milliseconds between the time zone and UTC time.

getTimezoneOffset(timeZone: String, date: Date|Number): number

Returns the time zone offset from UTC time in milliseconds for IANA time zones as well as other time zone offset string formats.

For time zones where daylight savings time is applicable a Date should be passed on the second parameter to ensure the offset correctly accounts for DST at that time of year. When omitted, the current date is used.

For invalid time zones, NaN is returned.

import { getTimezoneOffset } from 'date-fns-tz'

const result = getTimezoneOffset('-07:00')
//=> -18000000 (-7 * 60 * 60 * 1000)
const result = getTimezoneOffset('Africa/Johannesburg')
//=> 7200000 (2 * 60 * 60 * 1000)
const result = getTimezoneOffset('America/New_York', new Date(2016, 0, 1))
//=> -18000000 (-5 * 60 * 60 * 1000)
const result = getTimezoneOffset('America/New_York', new Date(2016, 6, 1))
//=> -14400000 (-4 * 60 * 60 * 1000)

Low-level formatting helpers

format

The format function exported from this library is used under the hood by formatInTimeZone and extends date-fns/format with full time zone support for:

  • The z..zzz Unicode tokens: short specific non-location format
  • The zzzz Unicode token: long specific non-location format

When using those tokens with date-fns/format it falls back to the GMT time zone format, and always uses the current system's local time zone. For example zzz in New York will always return GMT-4 instead of the desired EST, and zzz in Paris GMT+2 instead of CEST, making the time zone tokens somewhat irrelevant. This extended format function returns the proper specific non-location format, e.g. EST or Eastern Standard Time, and that of the target time zone (if provided, see below) rather than the system time zone.

Since a JavaScript Date instance cannot convey the time zone information to the format function it is necessary to pass the timeZone value as an option on the third argument of format.

Similar to date-fns/format, when an invalid date is used a RangeError is thrown. When an invalid time zone is provided and included in the output, i.e. with time zone tokens in the format string, it will also throw a RangeError.

To format a date showing time for a specific time zone other than the system time zone, the format function can be combined with toZonedTime. This is what formatInTimeZone does internally. To clarify, the format function will never change the underlying date, it must be changed to a zoned time before passing it to format.

In most cases there is no need to use format rather than formatInTimeZone. The only time this makes sense is when toZonedTime has been applied to a date once, and you want to format it multiple times to different outputs.

import { format, toZonedTime } from 'date-fns-tz'

const date = new Date('2014-10-25T10:46:20Z')

const nyDate = toZonedTime(date, 'America/New_York')
const parisDate = toZonedTime(date, 'Europe/Paris')

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'

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

toDate

The toDate function can be used to parse a Date from a string containing a date and time representing time in any time zone by providing an IANA time zone name on the timeZone option.

An invalid date string or time zone will result in an Invalid Date.

import { toDate, format } from 'date-fns-tz'

// Offsets in the date string work as usual and take precedence
const parsedDate = toDate('2014-10-25T13:46:20+04:00')
const parisDate = toZonedTime(parsedDate, 'Europe/Paris')
format(parisDate, 'yyyy-MM-dd HH:mm:ssxxx', { timeZone: 'Europe/Paris' }) // 2014-10-25 11:46:20+02:00

// Since toDate simply clones a Date instance, the 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 parsedDate = toDate('2014-10-25T13:46:20', { timeZone: 'Asia/Bangkok' })
const bangkokDate = toZonedTime(parsedDate, 'Asia/Bangkok')
format(bangkokDate, 'yyyy-MM-dd HH:mm:ssxxx', { timeZone: 'Asia/Bangkok' }) // 2014-10-25 13:46:20+07:00

Usage with Android

This library works with React Native on iOS, and on Android with Hermes which supports Intl by default.

In Android projects that do not use Hermes, make this change in android/app/build.gradle:

- def jscFlavor = 'org.webkit:android-jsc:+'
+ def jscFlavor = 'org.webkit:android-jsc-intl:+'

Usage with Node.js

Node.js supports the Intl API and ships with full ICU data included in the binary since v13, so this library works by default.

Credit

The idea of using the Intl API for time zone support was inspired by the Luxon library.

The initial port of the idea into date-fns was done by @benmccan in date-fns/#676.

License

MIT © Marnus Weststrate

changelog

v3.2.0 (27 September 2024)

  • [DEPS] `date-fns@4.0.0` supported (#301)
  • [PERFORMANCE] Make testDateFormatted static to avoid re-initialising it on every call (#297) @hurali97
  • [ENHANCEMENT] Compatibility with date-fns setDefaultOptions (#286) @yangchristina
  • [DOCS] Fixed table of contents links (#284)

v3.1.3 (17 April 2024)

  • [BUGFIX] Make getTimeZoneOffset's 2nd arg optional

v3.1.2 (16 April 2024)

  • [BUGFIX] Fix formatInTimeZone use of timeZone string (#280)

v3.1.1 (16 April 2024)

  • [BUGFIX] getTimeZoneOffset's 2nd arg should be optional (#281)

v3.1.0 (15 April 2024)

  • [REFACTOR] Full rewrite in TypeScript (#278) Thank you, @brettwillis
  • [BREAKING CHANGE] OptionsWithTZ is now either FormatOptionsWithTZ or ToDateOptionsWithTZ, matching date-fns

v3.0.1 (10 April 2024)

  • [BUGFIX] Fix named export typings (#277)

v3.0.0 (6 April 2024)

  • [UPGRADE] Support date-fns v3 (#265) Thank you, @christopherklint97
  • [BUGFIX] Correct import of date-fns@v3 format and use Babel Webpack loader for mjs files (#271)
  • [BREAKING CHANGE] date-fns v2 is no longer supported
  • [BREAKING CHANGE] Renamed utcToZonedTime to toZonedTime to make the name less confusing, just search & replace
  • [BREAKING CHANGE] Renamed zonedTimeToUtc to fromZonedTime to make the name less confusing, just search & replace
  • [BREAKING CHANGE] All functions are now exported using named exports, this requires changing direct imports from import formatInTimeZone from 'date-fns-tz/formatInTimeZone' to import { formatInTimeZone } from 'date-fns-tz/formatInTimeZone'
  • [BREAKING CHANGE] Functions now don’t check the number of passed arguments, delegating this task to type checkers (This isn't fully implemented yet, but it should be the assumption moving forward, as it is in date-fns)
  • [BREAKING CHANGE] Arguments are not explicitly converted to the target types. Instead, they are passed as is, delegating this task to type checkers (This isn't fully implemented yet, but it should be the assumption moving forward, as it is in date-fns)
  • [BREAKING CHANGE] IE is no longer supported since date-fns no longer supports it
  • [BREAKING CHANGE] Removed flow support since date-fns also removed it

v2.0.1 (9 March 2024)

  • [DOCS] Fix incorrect output comment (#270)
  • [DEPS] Limit to date-fns 2.x (#262, #267)
  • [REFACTOR] tzTokenizeDate: Remove weird unneeded regex (#254)
  • [BUGFIX] Improving correctness of the formatInTimeZone close to the DST threshold (#247)
  • [ENHANCEMENT] Use hourCycle for browsers that support hour cycle formatting (#231)
  • [BUGFIX] Fix tzParseTimezone to parse 00:30 timezones properly (#229)

v2.0.0 (30 January 2023)

  • [BREAKING CHANGE] Optimize configuration for ESM exports (entry points for cjs / esm / typescript) (#212)

Upgrade guide: Both CJS and ESM imports now use the default import paths. CJS will continue working unchanged; to fix ESM imports:

Before:

import { format } from 'date-fns-tz/esm'
import utcToZonedTime from 'date-fns-tz/esm/utcToZonedTime'

After:

import { format } from 'date-fns-tz'
import utcToZonedTime from 'date-fns-tz/utcToZonedTime'

v1.3.8 (30 January 2023)

  • [TESTS] Tests documenting zonedTimeToUtc daylight saving jumps (#220)
  • [TYPES] Update format types to match date-fns (#199)
  • [BUGFIX] Fixed undefined timezone error (#214)

v1.3.7 (31 August 2022)

  • [BUGFIX] Fixed getting the time zone name in partsTimeZone out of formatToParts (#196)
  • [BUGFIX] tzTokenizeDate uses month: numeric as a possible fix for #190

v1.3.6 (13 July 2022)

  • [BUGFIX] Remove the use of const (#192)

v1.3.5 (18 June 2022)

  • [BUGFIX] Fixed utcToZonedTime not working with year < 100; thanks @healqq (#185)

v1.3.4 (28 April 2022)

  • [BUGFIX] Add new to DateTimeFormat invocation to fix mocking it in tests (#181)
  • [DOCS] Add notes on usage with Android

v1.3.3 (1 April 2022)

  • [BUGFIX] Fix broken ESM import (#177)

v1.3.2 (31 March 2022)

  • [BUGFIX] Support tz offsets up to +/-23:59 (#171)
  • [BUGFIX] Fixed zonedTimeToUtc not working with year < 100 (#87, #170)

v1.3.1 (25 March 2022)

  • [BUGFIX] ESM export of package.json (#173)
  • [BUGFIX] Round tz offset correctly to format old dates (#169, #168)

v1.3.0 (24 February 2022)

  • [ENHANCEMENT] Native ESM support via exports configuration in package.json (#133, #150)
  • [DOCS] Clarify the use of ESM vs CommonJS import paths
  • [DOCS] Clarify when format throws a RangeError and fix the test for it
  • [ENHANCEMENT] More extensive validation of numeric time zone offsets
  • [BUGFIX] Fixed zonedTimeToUtc throwing RangeError instead of returning an Invalid Date (#151)
  • [BUGFIX] Fixed format not throwing RangeError for invalid time zones with offset tokens (#152)

v1.2.2 (21 December 2021)

  • [BUGFIX] Fix formatInTimeZone types and fp arguments

v1.2.1 (21 December 2021)

  • [DOCS] Fixed a broken link (#148)

v1.2.0 (18 December 2021)

  • [ENHANCEMENT] Add formatInTimeZone
  • [DOCS] Various improvements and corrections
  • [BUGFIX] Fixed zonedTimeToUtc parsing of date strings with time zone specifiers
  • [ENHANCEMENT] Functions that return dates will return Invalid Date for bad date / time zone inputs, and format functions throw a RangeError
  • [BUGFIX] Fix format returning wrong time zone offset close to DST. (#138)

v1.1.7 (17 December 2021)

  • [PERFORMANCE] Improve performance when validating the same timezones many times through caching; thanks @billthornton (#135)
  • [TESTS] Added a test for #33 which now passes
  • [BUGFIX] Fix format handling of quoted text next to a time zone token (#136)

v1.1.6 (27 July 2021)

  • [BUGFIX] Added an error object to the catch of isValidTimezoneIANAString for older JS (#131)

v1.1.5 (27 July 2021)

  • [BUGFIX] Fixed parsing IANA strings (#129)

v1.1.4 (13 April 2021)

  • [UPGRADE] Use Yarn berry
  • [BUGFIX] Restore IE11 support (#112)

v1.1.3 (27 February 2021)

  • [BUGFIX] Support IANA timezones with a hyphen in the name! (#110)

v1.1.2 (20 February 2021)

  • [BUGFIX] Fix time zone offset around DST; thanks @bsvetlik & @yharaskrik! (#108, #99, #93)

v1.1.1 (30 January 2021)

  • [DOCS] Add getTimezoneOffset to TOC

v1.1.0 (30 January 2021)

  • [ENHANCEMENT] Add the getTimezoneOffset function (#100).
  • [UPGRADE] Upgrade dependencies

v1.0.12 (9 October 2020)

  • [BUGFIX] Improve the #46 fix for toDate using the UTC timestamp for determining offset (#86).

v1.0.11 (5 October 2020)

  • [BUGFIX] Fix uctToZonedTime during timezone changes (#80).
  • [BUGFIX] Fix toDate using the UTC timestamp for determining offset (#46).

v1.0.10 (10 February 2020)

  • [BUGFIX] Fix day wrapping when the result is midnight in new browsers supporting DTF hourCycle (#38, #41, #43).

v1.0.9 (28 December 2019)

  • [DOCS] Improve the documentation, particularly on the usage of format.

v1.0.8 (12 October 2019)

  • [BUGFIX] Not losing milliseconds when converting time zones. (#25)
  • [BUGFIX] Fix missing _lib/convertToFP/index.js. (#20)
  • [UPGRADE] Upgrade dependencies and match the date-fns@2 build config.

v1.0.6 (2 February 2019)

  • [BUGFIX] Rewrite import paths in esm version to use esm version of date-fns. (#8 @pkaske)

v1.0.3 (21 January 2019)

  • [BUGFIX] Removed const, let, template string and arrow function syntax which is not transpiled in the ems build to fix for IE11.

v1.0.0 (12 January 2019)

  • [ENHANCEMENT] Typescript typings.

v0.1.1 (13 November 2018)

Initial release.