Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a plugin with the time zone support #323

Closed
prantlf opened this issue Sep 3, 2018 · 22 comments
Closed

Add a plugin with the time zone support #323

prantlf opened this issue Sep 3, 2018 · 22 comments

Comments

@prantlf
Copy link
Contributor

prantlf commented Sep 3, 2018

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.

@agwells
Copy link

agwells commented Oct 1, 2018

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.

@prantlf
Copy link
Contributor Author

prantlf commented Oct 7, 2018

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)?

@agwells
Copy link

agwells commented Oct 7, 2018

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.

@ghost
Copy link

ghost commented Oct 9, 2018

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

@prantlf
Copy link
Contributor Author

prantlf commented Oct 9, 2018

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 :-)

@prantlf
Copy link
Contributor Author

prantlf commented Oct 9, 2018

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.

@Globegitter
Copy link

Globegitter commented Nov 29, 2018

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.

@ianldgs
Copy link

ianldgs commented Jan 8, 2020

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.

@jpike88
Copy link

jpike88 commented Feb 9, 2020

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

@vpalos
Copy link

vpalos commented Mar 1, 2020

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).

@ianldgs
Copy link

ianldgs commented Mar 2, 2020

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.

@Herz3h
Copy link

Herz3h commented Jul 22, 2020

Any news on this ?

@gongAll
Copy link

gongAll commented Jul 27, 2020

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.

@iamkun
Copy link
Owner

iamkun commented Aug 4, 2020

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

@iamkun iamkun closed this as completed Aug 4, 2020
@Herz3h
Copy link

Herz3h commented Aug 12, 2020

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) ?

@iamkun
Copy link
Owner

iamkun commented Aug 12, 2020

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

Note you have to dayjs.extend(timezone) first

@Herz3h
Copy link

Herz3h commented Aug 12, 2020

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

@iamkun
Copy link
Owner

iamkun commented Aug 12, 2020

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
Copy link

Herz3h commented Aug 12, 2020

@iamkun
Copy link
Owner

iamkun commented Aug 13, 2020

@Herz3h Check my comment here, please. #323 (comment)

@tonprince
Copy link

Is there any dayjs support in Google App Scripts library including timezone plugin?

@sellalone
Copy link

sellalone commented Jan 3, 2023

i have extended dayjs with timezone plugin like this

import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';

dayjs.extend(utc);
dayjs.extend(timezone);

dayjs.tz.setDefault('America/New_York');

But for some reason format method is returning wrong answer

From dayjs.tz('2023-01-02T09:00:00-05:00').format();

Expected 2023-01-02T09:00:00-05:00

But instead got 2023-01-02T14:00:00-05:00

or
if i use

dayjs('2023-01-03').tz().format('YYYY-MM-DD');

Expected 2023-01-03

But instead got 2023-01-02

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests