js-temporal / temporal-polyfill

Polyfill for Temporal (under construction)
ISC License
529 stars 28 forks source link

Issue Related DST #232

Closed susanta96 closed 1 year ago

susanta96 commented 1 year ago

Hi everyone, I was trying to convert the date coming from timezone 'Europe/Berlin'. I tried to convert time to IST(Not only based on where ever client is ). But some how Temporal Object is getting offset of +1 , But after 26th of March, 2023 , DST has started so, it should be +2.

const tempDate = Temporal.ZonedDateTime.from({
  timeZone: 'Europe/Berlin',
  // offset: '+02:00',
  year: year,
  month: month,
  day: day,
  hour: hour,
  minute: min,
});

const formattedTime = intlFormat(tempDate.toInstant().epochMilliseconds, {
  hour: '2-digit',
  minute: '2-digit',
  hour12: false,
  localeMatcher: 'lookup',
}, {
  locale: navigator.language
})

I tried this intlFormat is from date-fns. This under the hood using Intl DateTimeFormat. But the outcome is still not perfect . (9:15 AM should be in IST 12:45 ) But its coming 13:45 . If anyone have any idea, please share . Also, why Europe/Berlin timezoneDate returns offset of +1.

justingrant commented 1 year ago

Hi @susanta96 - We're happy to help you with this issue, but we'll need a self-contained code sample to diagnose it. Could you provide a code sample that:

Please also supply what value you expected, and what actual value was returned.

Also, please let us know what environment you're executing this code in, e.g. Node, Chrome, Firefox, Safari, etc. And the version of the environment.

Thanks!

susanta96 commented 1 year ago

Hi @justingrant Thanks for quick reply.

Here is the env: Node v16.17 Browser - Brave (Chromium v111) latest version

Here is the code to convert date string to output the local Time with 24hr format.

The input is like this - "2023-02-24T09:15:01.000+0000" (CET although its not look like it.) Output I expect - "12:45" but got "13:45". (IST)

function formatTime(date: string): string {
  const year = Number(date.split('-')[0]);
  const month = Number(date.split('-')[1]);
  const day = Number(date.split('T')[0].split('-')[2]);
  const hour = Number(date.split('T')[1].split(':')[0]);
  const min = Number(date.split('T')[1].split(':')[1]);
  const tempDate = Temporal.ZonedDateTime.from({
    timeZone: 'Europe/Berlin',
    // offset: '+02:00',
    year: year,
    month: month,
    day: day,
    hour: hour,
    minute: min,
  });
  const formatIntl = Intl.DateTimeFormat(navigator.language, {
    hour: '2-digit',
    minute: '2-digit',
    hour12: false,
  }).format(tempDate.toInstant().epochMilliseconds);
  return formatIntl;
}

Need some solution to make this work for DST .

susanta96 commented 1 year ago

Sorry @justingrant Temporal API is actually properly working, it was not giving me the +2 (UTC) , because the input date & month was not appropriate for DST. it was giving +1 (UTC).

Thanks tough. 👍

justingrant commented 1 year ago

Hi @susanta96, glad you were able to figure it out. While you're here, you can also also use Temporal to parse the inputs instead of parsing by hand, and you can use toLocaleString instead of Intl.DateTimeFormat.prototype.format.

Combining the advice above, your function above can be rewritten much more simply:

function formatTime(timestamp) {
  // Get the date and time from the input, ignoring the +0000 offset. 
  // The timestamp actually refers to a local date/time in CET and the +0000 is fake.
  const pdt = Temporal.PlainDateTime.from(timestamp);

  // Project this timezone-less date/time into CET
  const zdtCET = pdt.toZonedDateTime('Europe/Berlin');

  // Convert to the user's local time zone
  const zdtLocal = zdtCET.withTimeZone(Temporal.Now.timeZone());

  // Finally, format its hours/minutes in 24-hour time display
  return zdtLocal.toLocaleString(navigator.language, { hour: '2-digit', minute: '2-digit', hour12: false });
}

Finally, are you sure that your timestamp string is in CET? If so, why does it have an +0000 offset when you get it?

justingrant commented 1 year ago

One more thing: as you're probably aware, the Temporal API is not intended for production use because the Temporal API is not finalized yet. Specifically, there are a few upcoming changes that will affect the code above:

So don't be surprised if your code breaks with the next release of this polyfill.

Thankfully these are the last major API changes expected in Temporal before it's officially added to the JavaScript standard.

susanta96 commented 1 year ago

Thanks @justingrant for the heads up for the API changes in the future. I know this is relatively new. But This is something, that we actually need.

Also, you are correct I am also thinking this date string is actually not CET (+0000). This shouldn't be there then. (We're discussing with Backend Team).

However, I will change it to toLocaleString instead of Intl.DateTimeFormat .

Once again, Thank you for you valuable feedback.

justingrant commented 1 year ago

If it's an actual ISO 8601 timestamp (meaning the the offset matches what the local time really is at that offset), then your function gets even simpler.

function formatTime(timestamp) {
  return Temporal.Instant.from(timestamp) // parse an ISO 8601 timestamp
    .toZonedDateTimeISO(Temporal.Now.timeZone()) // convert to the user's time zone
    .toLocaleString(navigator.language, { hour: '2-digit', minute: '2-digit', hourCycle: 'h23' });
}
formatTime("2023-02-24T09:15:01.000+0000")
// => '01:15' (here in America/Los_Angeles)

Note that I used the hourCycle option not hour12 because hour12 produces h24 results like 24:15 in many locales including en-US which is seldom what's expected. Feel free to use hour12 instead if that works better for you.

If you have control over the inputs, then I'd strongly recommend not using a string with a '+0000' at the end unless the string really represents a local time that's zero hours offset from UTC. Otherwise you're abusing ISO-8601 and some other reader of that data might not know that it's a fake offset.

susanta96 commented 1 year ago

Correct 💯. Actually my function got really simple , as after discussing we got to know, db has actual CET timing, but from the backend when they're returning it's UTC actually. Just need to convert it to particular zone timing.