dmtrKovalenko / date-io

Abstraction over common javascript date management libraries
MIT License
726 stars 90 forks source link

Support for Timezones #609

Open dantman opened 2 years ago

dantman commented 2 years ago

date-io currently doesn't handle timezones at all. Or it stays blind to timezones and assumes you never pass it anything but a local time, because passing it an externally zoned date gives inconsistent results.

It does at least appear consistent for .date() created dates. However the date-io users are libraries so it's no guarantee that the users using those libraries create their dates with the date-io .date() function. Most likely they choose a library, create dates with that library, and pass those dates to a library that happens to use a date-io adapter.

Current behavior

Luxon uses the DateTime's timezone when calculating startOfDay and formats ISO timestamps according to the DateTime's zone.

const {DateTime} = require('luxon')
const LuxonUtils = require('@date-io/luxon')
const ioLuxon = new LuxonUtils();
ioLuxon.toISO(ioLuxon.startOfDay(DateTime.local()));
// '2022-02-25T00:00:00.000-08:00'
ioLuxon.toISO(ioLuxon.startOfDay(DateTime.utc()));
// '2022-02-25T00:00:00.000Z'
ioLuxon.toISO(ioLuxon.startOfDay(ioLuxon.date('2022-02-25T00:00:00.000Z')));
// '2022-02-24T00:00:00.000-08:00'

Moment uses the DateTime'z timezone when calculating startOfDay, but formats ISO timestamps always in UTC.

const MomentUtils = require('@date-io/moment')
const moment = require('moment')
const ioMoment = new MomentUtils()
ioMoment.toISO(ioMoment.startOfDay(moment()));
// '2022-02-25T08:00:00.000Z'
ioMoment.toISO(ioMoment.startOfDay(moment.utc()));
// '2022-02-25T00:00:00.000Z'
ioMoment.toISO(ioMoment.startOfDay(ioMoment.date('2022-02-25T00:00:00.000Z')));
// '2022-02-24T08:00:00.000Z'

date-fns uses the local timezone when calculating startOfDay and always formats timestamps in the local time. Since despite date-fns-tz existing, date-fns appears to not actually support calculations knowledgable about other timezones.

const {zonedTimeToUtc} = require('date-fns-tz')
const DateFnsUtils = require('@date-io/date-fns')
const ioDateFns = new DateFnsUtils();
ioDateFns.toISO(ioDateFns.startOfDay(new Date));
// '2022-02-25T00:00:00-08:00'
ioDateFns.toISO(ioDateFns.startOfDay(zonedTimeToUtc(new Date)));
// '2022-02-25T00:00:00-08:00'
ioDateFns.toISO(ioDateFns.startOfDay(ioDateFns.date('2022-02-25T00:00:00.000Z')));
// '2022-02-24T00:00:00-08:00'

However date-io doesn't handle them at all. Or rather it stays blind to the timezone leaving different results

Expected behaviour

I think we should add a timeZone option to the adapter options and aim for relative consistency between libraries.

sidharthancr commented 1 year ago

@dmtrKovalenko any update on this. this is badly needed when we are working across timezones!

dmtrKovalenko commented 1 year ago

Sorry for the input on my side.

I feel that this must be done as an extension to the date-io interfaces and not the core features. Maybe even 🤔 core exported extension? I'll think about this but I am not sure what is the use case? In my workflows for adapters of date-io you will take the user specified timezone and return the object in same timezone, and only end user controls which timezone to set..

cleemansen commented 5 months ago

I'll think about this but I am not sure what is the use case?

We are developing an application for scientists. This application is used all over the world. Regarding date/time in the whole application there is one rule: all dates/times are in UTC. Therefore, users must be able to enter a date/time in the UTC zone - regardless of their current time zone when entering it. This rule is intended to clear up any confusion about dates/times among users.

dmtrKovalenko commented 5 months ago

I’d recommend picking up dayjs timezones and provide preconfigured timezoned data to date-io.

it is not always possible to abstract time zones between the libraries we support.

kor3k commented 2 months ago

maybe instead of making it "timezone aware", just introduce one method for setting the zone.

// simplified example with js-joda/ZonedDateTime
setTimezone (Temporal: value, String: zone, Boolean: preserveInstant = true): Temporal {
  if (preserveInstant) {
      return value.withZoneSameInstant(zone)
  } else {
      return value.withZoneSameLocal(zone)
  }
}

the preserveInstant arg is for controlling whether to keep the instant (timestamp) and change the "reading", or keep "reading" and change the instant (convert zone vs override zone).

implementations which do not support zones can just return value.