[go: up one dir, main page]

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

Temporal.Now.timeZoneId() returns undefined on MacOS 14.0 (Sonoma) #264

Closed
khawarizmus opened this issue Oct 7, 2023 · 18 comments
Closed

Comments

@khawarizmus
Copy link
khawarizmus commented Oct 7, 2023

As the title suggests I am getting `undefined when running this code

Temporal.Now.timeZoneId() // undefined

Not sure what other information I need to provide however here is the output of npx envinfo --system --binaries --browsers

  System:
    OS: macOS 14.0
    CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
    Memory: 410.13 MB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 18.17.0 - ~/.nvm/versions/node/v18.17.0/bin/node
    Yarn: 1.22.19 - ~/.nvm/versions/node/v18.17.0/bin/yarn
    npm: 9.6.7 - ~/.nvm/versions/node/v18.17.0/bin/npm
    pnpm: 8.6.11 - ~/.nvm/versions/node/v18.17.0/bin/pnpm
  Browsers:
    Chrome: 117.0.5938.149
    Safari: 17.0
@khawarizmus
Copy link
Author

To add to that when I try to use Temporal.Now.zonedDateTimeISO() I get the following error

RangeError: Invalid time zone specified: undefined

@justingrant
Copy link
Contributor
justingrant commented Oct 7, 2023

This is a known bug in Chromium on MacOS 14.0: https://bugs.chromium.org/p/chromium/issues/detail?id=1487920

Other related Chromium issues:

It's not related to the Temporal polyfill. The underlying API that the polyfill uses is also affected. Example:

new Intl.DateTimeFormat().resolvedOptions().timeZone

@justingrant justingrant changed the title Temporal.Now.timeZoneId() returns undefined Temporal.Now.timeZoneId() returns undefined on MacOS 14.0 (Sonoma) Oct 7, 2023
@khawarizmus
Copy link
Author

Thank you for the swift reply @justingrant . Is there any sensible workaround this?

@justingrant
Copy link
Contributor

I updated my comment above to link to a more appropriate Chromium issue, and added some related links.

Is there any sensible workaround this?

If yours is a server app running in Node, you could I guess try running it in Bun, which uses Safari's JS engine JavaScriptCore which doesn't exhibit this problem.

For client apps, the only workaround seems to be using Firefox or Safari.

But I don't think there's any way to work around this in V8, so any V8-based host (Chrome, Node, etc.) will have the problem.

@justingrant
Copy link
Contributor

Here's the root cause issue to track the upstream regression is in the ICU library that V8 uses to handle time zones: https://unicode-org.atlassian.net/browse/ICU-22541

@khawarizmus
Copy link
Author

Version 118.0.5993.88 (Official Build) (x86_64) Of Chrome seems to have fixed this issue. However FireFox 118.0.2 (64-bit) seems to be returning ETC/GMT-8 instead of Asia/Kuala_Lumpur.

This is still problematic as the two timezones are not canonically equal.

the following test is failing:

describe("canonicalEquals should work as expected", () => {
  it("should return true for equal zones", () => {
    expect(canonicalEquals("Etc/GMT-8", "Asia/Kuala_Lumpur")).toBe(true); // <-- Fails here
    expect(canonicalEquals("Asia/Calcutta", "Asia/Kolkata")).toBe(true);
    expect(canonicalEquals("Pacific/Truk", "Pacific/Yap")).toBe(true);
    expect(canonicalEquals("Africa/Asmera", "Africa/Asmara")).toBe(true);
  });

  it("should return false for different zones", () => {
    expect(canonicalEquals("Africa/CasaBlanca", "Africa/Algiers")).toBe(false);
  });
});

where canonicalEquals is as follows taken from the proposal-canonical-tz:

import { Temporal } from "@js-temporal/polyfill";

const EPOCH = Temporal.Instant.fromEpochNanoseconds(0n);

export function canonicalEquals(zone1: string, zone2: string) {
  const zdt1 = EPOCH.toZonedDateTimeISO(zone1);
  const zdt2 = EPOCH.toZonedDateTimeISO(zone2);
  return zdt1.equals(zdt2);
}

@justingrant Is that an expected behaviour?

@khawarizmus
Copy link
Author

@ptomato @justingrant is it an expected behaviour that ETC/GMT-8 and Asia/Kuala_Lumpur are not canonically equal?

@ptomato
Copy link
Contributor
ptomato commented Oct 19, 2023

@justingrant would be the expert on that, but it does not sound to me like those should be canonically equal.

@gilmoreorless
Copy link
Contributor

Asia/Kuala_Lumpur has only been UTC+8 since 1982, so it can't be canonically equal to a fixed-offset zone.

@khawarizmus
Copy link
Author

Asia/Kuala_Lumpur has only been UTC+8 since 1982, so it can't be canonically equal to a fixed-offset zone.

I am not sure I understand what you are trying to say. So ETC/GMT-8 is an offset and not a timezone? and why aren't they canonically equal if we are counting from Epoch ?

@justingrant
Copy link
Contributor

This is a complicated topic that we're trying to make less complicated.

Time zones in ECMAScript are based on the IANA Time Zone Database, or "TZDB" for short. A TZDB time zone identifer can either be a "Zone" or a "Link" that resolves to a Zone.

For example, "Asia/Ulan_Bator" is a link that resolves to the Zone "Asia/Ulanbataar", because "Ulan Bator" (the English name of the capital of Mongolia) was renamed to Ulanbataar and TZDB followed suit.

In ECMAScript, time zones are considered equal (meaning Temporal.TimeZone.prototype.equals returns true) when the IDs resolve to the same Zone in TZDB. So "Asia/Saigon" and "Asia/Ho_Chi_Minh" are equal, while "GMT-7" and "Asia/Ho_Chi_Minh" are not equal because they resolve to a different Zone in TZDB.

What makes this a bit complicated is that TZDB is not a well-specified thing. Depending on the command-line options used to build TZDB, for example, "Europe/Amsterdam" could be a Zone or a Link to "Europe/Berlin", because "Europe/Amsterdam" and "Europe/Berlin" have had the same UTC offsets for every instant since 1/1/1970 UTC, even though they varied before that.

We're working on standardizing this behavior to more clearly define for ECMAScript when time zones are considered equal or not. See tc39/ecma402#825 for more details.

So to answer your question: "Etc/GMT-8" is not considered equal to "Asia/Kuala_Lumpur", because they are different Zone IDs in TZDB, so they are not equal in ECMAScript.

@justingrant
Copy link
Contributor

FireFox 118.0.2 (64-bit) seems to be returning ETC/GMT-8 instead of Asia/Kuala_Lumpur.

I'm not sure what browser engines are doing to work around this change in behavior in MacOS. But in general, browsers should be reporting the same time zone ID. If you're seeing "Asia/Kuala_Lumpur" in Chrome but "Etc/GMT-8" in Firefox, then that seems like a Firefox bug and you should probably report it. FYI @anba.

@anba
Copy link
anba commented Oct 20, 2023

Firefox is also using ICU, so we had the same time zone bug on Sonoma as Chrome had. We've integrated the ICU patch in https://bugzilla.mozilla.org/show_bug.cgi?id=1856428 for all release branches, so the next Firefox release (October 24, 2023) will work correctly again.


Etc/GMT-8 was returned in Firefox, because we have a fallback code path to handle the case when ICU isn't able to determine the system time zone. Basically when ICU reports back that it can't determine the system time zone, we try to give the user a better time zone than just UTC.

@justingrant
Copy link
Contributor

So to answer your question: "Etc/GMT-8" is not considered equal to "Asia/Kuala_Lumpur", because they are different Zone IDs in TZDB, so they are not equal in ECMAScript.

I realize that I didn't really explain why these are different Zone IDs. It's because "Etc/GMT-8" for every possible instant has the offset +08:00. However, "Asia/Kuala_Lumpur", while it currently uses +08:00, might possibly change that offset at some point in the future, based on the decisions made by the Malaysian government. So we (and the IANA Time Zone Database) consider those time zones unequal.

@khawarizmus
Copy link
Author

So to answer your question: "Etc/GMT-8" is not considered equal to "Asia/Kuala_Lumpur", because they are different Zone IDs in TZDB, so they are not equal in ECMAScript.

I realize that I didn't really explain why these are different Zone IDs. It's because "Etc/GMT-8" for every possible instant has the offset +08:00. However, "Asia/Kuala_Lumpur", while it currently uses +08:00, might possibly change that offset at some point in the future, based on the decisions made by the Malaysian government. So we (and the IANA Time Zone Database) consider those time zones unequal.

Thanks a lot for the detailed explanation. It makes sense to me when you explain it that way. but when i looked at the implementation of canonicalEquals I was under the impression that it's checking the number of milliseconds or microseconds since epoch. which then leads me to my question why are we using Epoch here?

@khawarizmus
Copy link
Author

Firefox is also using ICU, so we had the same time zone bug on Sonoma as Chrome had. We've integrated the ICU patch in bugzilla.mozilla.org/show_bug.cgi?id=1856428 for all release branches, so the next Firefox release (October 24, 2023) will work correctly again.

I can confirm that Firefox 119.0 is working as expected. I will be closing this issue but i would love to have an answer to the question above? basically why are we using Epoch for canonical comparisons?

@justingrant
Copy link
Contributor
justingrant commented Oct 25, 2023

why are we using Epoch for canonical comparisons?

The canonicalEquals example you linked to comes from https://github.com/tc39/proposal-canonical-tz#6-add-temporaltimezoneprototypeequals, which demonstrated that comparing time zones for equality was possible using the ZonedDateTime.prototype.equals method. A ZonedDateTime instance stores three pieces of data: an Instant (number of nanoseconds since epoch), a time zone, and a calendar. By using the same instant and calendar when creating two ZonedDateTime instances, you can use ZonedDateTime.prototype.equals to determine whether two time zones are equal.

Epoch could have been any other time, the key was that they are equal.

Modern code can use the new Temporal.TimeZone.prototype.equals method instead, for a more ergonomic comparison. Like this:

Temporal.TimeZone.from('Asia/Calcutta').equals('Asia/Kolkata'); // => true
Temporal.TimeZone.from('Asia/Calcutta').equals('Asia/Colombo'); // => false

@khawarizmus
Copy link
Author

Makes sense to me now. Thank you @justingrant

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

5 participants