Dayjs: Add a plugin with the time zone support

Created on 3 Sep 2018  路  20Comments  路  Source: iamkun/dayjs

Update: Day.js Time Zone Plugin https://day.js.org/docs/en/timezone/timezone










Business Case

A typical application, which displayed dates and expects dates entered by the users handles the time zone:

  1. Consistently. The user should not be confused by different components working in different time zones.
  2. Either using the local time zone from the web browser (operating system).
  3. Or using the time zone specified in the user settings of the web application.

Interface Synopsis

A minimal support could include the following two scenarios, typical for applications displaying and editing dates:

  1. From the storage to the presentation. Format a UTC date to show it converted to the time zone chosen by the user.
  2. From the presentation to the storage. Parse a date entered by the user in their chosen time zone and convert it to UTC.

The API for these scenarios could use the constructor and the format method:

const timeZone = 'Europe/Berlin' // Canonical time zone name

// String without a time zone from the date picker
const enteredDate = '2018-09-02 23:41:22'
const storedDate = dayjs(enteredDate, { timeZone }).toISOString()
// Will contain "2018-09-02T21:41:22Z"

// String in UTC or anything that Day.js accepts
const storedDate = '2018-09-02T21:41:22Z'
const userFormat = 'D.M.YYYY H:mm:ss [GMT]Z (z)' // Standard Day.js format
const displayedDate = dayjs(storedDate).format(userFormat, { timeZone })
// Will contain "2.9.2018 23:41:22 GMT+02:00 (CEST)"

Implementation Proposal

Day.js uses an embedded Date object. That is why this library is so lightweight, but it limits its functionality. The Date object supports only local time zone and UTC. It does not work in other time zones. It is possible to continue leveraging this principle. without making more complicated by replacing internal Date object with something else. All other methods continue working as expected.

dayjs(input: any, { timeZone: boolean }?)

Day.js already supports an optional options object in the constructor and the parse method. The time zone parameter in the constructor is meant only for converting the parsed input string correctly to UTC. The embedded Date object will be initialised with UTC and offer the local time zone representation as usual. The original time zone offset will not be remembered. It is usually not important, because dates should be rendered consistently in user's time zone; not in various time zones, which their string sources referred to.

format(format: string, { timeZone: boolean }?)

The options object is optionally recognised by the overridden format method. The time zone parameter will extract the date parts (year, month, ...) from the embedded this.$d object in UTC and convert them to the specified time zone, before producing the output string.

This can be delivered as a new plugin. Not everyone need the full time zone handling and if the implementation includes the time zone data, it will not be small.

Most helpful comment

Yikes this has put me off migrating dayjs from moment. I'm surprised there isn't an official plugin already.

If there is no easy equivalent of this code, it's a problem:

import moment from 'moment-timezone';
moment.tz.setDefault(UserService.user.timezone); // global setting of timezone, moment will from now on respect this

All 20 comments

Here's another slightly different use-case. I'm working on a project that needs to display a series of measurements (things like a lake's water level) recorded at different sites spread across a few different timezones. When creating a site, I make a note of which timezone it's located in. Then, when I'm showing a table of measurements to the user, I want to show the measurements' times of recording in the site's timezone, because then the user can think about them in relation to the local time of day. (i.e., does the lake level rise highest an noon, or in the evening?)

It sounds like your proposal above should be able to handle it. It's basically the same as your business case 3, except that the timezone is not part of the user's settings.

Yes, @agwells, if you do not store the dates in UTC, but in the original time zone together with the time zone name, you can print the date in the original zone later. For example:

const measurements = [
  { value: 123.45, date: '2018-10-07 10:30', timeZone: 'Europe/Prague' },
  { value: 198.75, date: '2018-10-07 16:00', timeZone: 'Europe/London' }
]

// Print dates in the original time zone
measurements.forEach(({ value, date, timeZone }) => {
  const localDate = dayjs(date, { timeZone })
  const formattedDate = localDate.format('MM/DD/YYYY h:mm A [GMT]Z (z)', { timeZone })
  console.log(`${value} at ${formattedDate}`)
})

// Print dates in the single chosen time zone
const displayTimeZone = 'Europe/Lisbon'
measurements.forEach(({ value, date, timeZone: originalTimeZone }) => {
  const localDate = dayjs(date, { timeZone: originalTimeZone })
  const formattedDate = localDate.format('MM/DD/YYYY h:mm A [GMT]Z (z)', { timeZone: displayTimeZone })
  console.log(`${value} at ${formattedDate}`)
})

Do you store the time zone information as the canonical time zone name ("Europe/Berlin", for example), or as the UTC offset at the particular day ("GMT+02:00 CEST", for example)?

We do this:

  1. Store and manipulate all dates internally in UTC time.
  2. When parsing a date from user input, transform it from the measurement site's timezone to UTC.
  3. When formatting a date to display to the user, transform from UTC to the measurement site's timezone.
  4. Store the timezone in a separate field from the date, in the "Europe/Lisbon" format
  5. Display the timezone with its abbreviation and/or UTC offset, "GMT +02:00 CEST"

So it looks like that's actually a simpler use-case than in your example code. :) Something like this...

const measurements = [
  { value: 123.45, date: '2018-10-07T08:30:00Z', timeZone: 'Europe/Prague' },
  { value: 198.75, date: '2018-10-07T16:00:00Z', timeZone: 'Europe/London' }
];

// Print dates in the original time zone
measurements.forEach(({ value, date, timeZone: originalTimeZone }) => {
  const utcDate = dayjs(date, {timeZone: 'UTC'})
  const formattedDate = utcDate.format('MM/DD/YYYY h:mm A [GMT]Z (z)', { timeZone: originalTimeZone })
  console.log(`${value} at ${formattedDate}`)
})

... and more or less the inverse operation, when parsing user input to convert it into UTC.

I do this:

dayjs(new Date().toLocaleString("en-US", {timeZone: "America/New_York"})).format('h:mA')


dayjs(new Date().toLocaleString("en-US", {timeZone: "America/Los_Angeles"})).format('h:mA')

demo -> https://plnkr.co/edit/KjrKnwvxBktn3GJmDwDw

Refer: https://medium.com/@bmarti44/10-helpful-tips-i-wish-i-knew-before-i-started-using-angular-2-c25bcaa6fe2f

Yes @agwells, it will work fine.

Just a little note - if you always include the "Z" in your stored date strings, you won't need to specify the time zone explicitly as "UTC", when parsing the date strings to dayjs. If there is a time zone information available in the source date string, it will be converted to the internal Date object in the days instance correctly.

const utcDate = dayjs(date)

You will save a couple of characters and gain an immeasurable performance improvement :-)

The code formatting a date an an arbitrary time zone is cunning, @xxyuk :-) Providing, that the Node.js or the browser supports time zones.

dayjs(new Date().toLocaleString("en-US", {timeZone: "America/New_York"})).format('h:mA');

Just make sure, that you use the dayjs instance only for the formatting. The best would be throwing it away afterwards, as your code does it. If you used the dayjs instance tot other purposes, like UTC conversions, computing differences to other dates or manipulating the date, it would yield wrong results. New York time, when parsed to Date in other time zone, will be subject to other DST changes, when converting to UTC.

We were also using toLocaleString and passing in the timeZone to be able to format a string for a specified timeZone but our problem is IE11 only supports UTC for that value. That is why we looked into dayjs support for that use-case, so it would be great to have a plugin for that working all the way down to IE11.

I've created a small ridiculous plugin to use locally in the project that I currently work, inspired on date-fns-tz/Intl API. If anyone wants to check it out and maybe give some feedback, it is really simple and could be a way to solve timezone problem with dayjs.
PS: it doesn't intend to be compatible with moment.

Yikes this has put me off migrating dayjs from moment. I'm surprised there isn't an official plugin already.

If there is no easy equivalent of this code, it's a problem:

import moment from 'moment-timezone';
moment.tz.setDefault(UserService.user.timezone); // global setting of timezone, moment will from now on respect this

I've created a small ridiculous plugin to use locally in the project that I currently work, inspired on date-fns-tz/Intl API. If anyone wants to check it out and maybe give some feedback, it is really simple and could be a way to solve timezone problem with dayjs.
PS: it doesn't intend to be compatible with moment.

This looks like a possible alternative for those that need a solution now! But note that this will not help to render the time-zone correctly via the format() call.

I'd add that Intl time-zone support can be ensured on older browsers using a polyfill (https://github.com/formatjs/date-time-format-timezone).

But note that this will not help to render the time-zone correctly via the format() call.

You sound about right! In my case, all I need to do are operations like adding/subtracting but considering the timezone.

Any news on this ?

The fact that this isn't closed almost 2 years later is a bummer... Was really considering migrating to dayjs from moment, but this is a must.

Is there a use example ? I tried:

dayjs('mydatehere').tz('Europe/Paris').format('DD/MM/YYYY') and it says format is not a function.

It seems that from source code, tz() returns an offset, should this be used with a function like dayjs().add(offset) ?

@Herz3h Please check here, https://day.js.org/docs/en/timezone/timezone

Note you have to dayjs.extend(timezone) first

I did this:

dayjs.extend(timezone)

and used code in previous post. I get a number as a return value instead of a dayjs() instance

Timezone plugin needs UTC plugin

var dayjs = require("dayjs")
var utc = require("dayjs/plugin/utc")
var timezone = require("dayjs/plugin/timezone")
dayjs.extend(timezone)
dayjs.extend(utc)
dayjs().tz('Europe/Paris').format('DD/MM/YYYY')

@Herz3h Check my comment here, please. https://github.com/iamkun/dayjs/issues/323#issuecomment-672991485

Was this page helpful?
0 / 5 - 0 ratings

Related issues

antony picture antony  路  5Comments

idasbiste picture idasbiste  路  4Comments

LauRocky picture LauRocky  路  5Comments

sohailalam2 picture sohailalam2  路  4Comments

oliv9286 picture oliv9286  路  4Comments