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

Normative: Prevent loss of TimeZone information #2479

Closed
wants to merge 2 commits into from

Conversation

Aditi-1400
Copy link
Collaborator

Modified FormatDateTimePattern to include TimeZone information.
Fixes: #2013

@codecov
Copy link

codecov bot commented Jan 17, 2023

Codecov Report

Merging #2479 (4b78177) into main (f973e18) will increase coverage by 0.02%.
The diff coverage is n/a.

❗ Current head 4b78177 differs from pull request most recent head 756bce1. Consider uploading reports for the commit 756bce1 to get more accurate results

@@            Coverage Diff             @@
##             main    #2479      +/-   ##
==========================================
+ Coverage   95.47%   95.50%   +0.02%     
==========================================
  Files          20       20              
  Lines       10929    10931       +2     
  Branches     2034     2035       +1     
==========================================
+ Hits        10435    10440       +5     
+ Misses        432      430       -2     
+ Partials       62       61       -1     
Impacted Files Coverage Δ
polyfill/lib/calendar.mjs 84.41% <0.00%> (+0.03%) ⬆️
polyfill/lib/ecmascript.mjs 98.28% <0.00%> (+0.05%) ⬆️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

Copy link
Collaborator

@ptomato ptomato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good start, but there's more needed, I think. First, we need to make a similar change in PartitionDateTimeRangePattern.

The x.[[timeZone]] has to come from somewhere, so we need to make sure that HandleDateTimeValue provides that field in the Record that it returns. I'd guess it should be set to undefined in every case except HandleDateTimeTemporalZonedDateTime. (That also suggests to me that we shouldn't check whether it is present in FormatDateTimePattern but instead check whether it is undefined.)

spec/intl.html Outdated Show resolved Hide resolved
spec/intl.html Outdated Show resolved Hide resolved
@Aditi-1400
Copy link
Collaborator Author

Good start, but there's more needed, I think. First, we need to make a similar change in PartitionDateTimeRangePattern.

The x.[[timeZone]] has to come from somewhere, so we need to make sure that HandleDateTimeValue provides that field in the Record that it returns. I'd guess it should be set to undefined in every case except HandleDateTimeTemporalZonedDateTime. (That also suggests to me that we shouldn't check whether it is present in FormatDateTimePattern but instead check whether it is undefined.)

Since, HandleDateTimeValue directly returns the records from function, I’ve made the changes to the respective functions, because that’s consistent with HandleDateTimeTemporalZonedDateTime but it might be better to add [[timeZone]]:undefined to HandleDateTimeValue, rather than returning it from other methods, what do you think?

Copy link
Collaborator

@ptomato ptomato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think PartitionDateTimeRangePattern is not quite there, everything else looks good.

In formatRange and formatRangeToParts, the time zones need to be both either present or undefined (this is already guaranteed by SameTemporalType, near the beginning of the operation) but after that we need to check that they are the same time zone, and throw a RangeError if not. After that, we can just use one variable timeZone which is either the time zone of x and y or the time zone of dateTimeFormat if x and y weren't ZonedDateTime instances. We should do this before calculating tm1 and tm2 because they should use timeZone, not dateTimeFormat.[[TimeZone]].

spec/intl.html Outdated Show resolved Hide resolved
spec/intl.html Outdated Show resolved Hide resolved
@ptomato
Copy link
Collaborator

ptomato commented Jan 20, 2023

Since, HandleDateTimeValue directly returns the records from function, I’ve made the changes to the respective functions, because that’s consistent with HandleDateTimeTemporalZonedDateTime but it might be better to add [[timeZone]]:undefined to HandleDateTimeValue, rather than returning it from other methods, what do you think?

I think the way you did it is fine, but probably an equivalent way would also be fine. The current way seems concise, I'd imagine that HandleDateTimeValue would get longer if we added the time zone field after each Handle... call.

Copy link
Collaborator

@ptomato ptomato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thanks!

I did notice one more place where I think we need to make a change from dateTimeFormat.[[TimeZone]] to timeZone: step 16.e.ii of FormatDateTimePattern (the one starting "Else if p is equal to "timeZoneName", then"). There's also 16.f.viii but when I was looking at it, I actually came to believe that that is dead code because it's already covered by 16.e.ii. I'll remove it in an editorial PR which I'm working on.

Other than that, I think this is ready to merge.

spec/intl.html Outdated Show resolved Hide resolved
@@ -436,7 +438,7 @@ <h1>FormatDateTimePattern ( _dateTimeFormat_, <ins>_pattern_</ins>, _patternPart
1. Append a new Record { [[Type]]: _p_, [[Value]]: _fv_ } as the last element of the list _result_.
1. Else if _p_ is equal to *"timeZoneName"*, then
1. Let _f_ be _dateTimeFormat_.[[TimeZoneName]].
1. Let _v_ be _dateTimeFormat_.[[TimeZone]].
1. Let _v_ be <del>_dateTimeFormat_.[[TimeZone]]</del><ins>_timeZone_</ins>.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this incorrect for cases where timeZone is undefined?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes! I'll fix this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we go with the suggestion from my previous comment, then timeZone can't be undefined here anymore.

spec/intl.html Show resolved Hide resolved
spec/intl.html Show resolved Hide resolved
spec/intl.html Show resolved Hide resolved
@@ -502,7 +504,7 @@ <h1>PartitionDateTimePattern ( _dateTimeFormat_, _x_ )</h1>
<emu-alg>
1. <ins>Let _x_ be ? HandleDateTimeValue(_dateTimeFormat_, _x_).</ins>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you forgot to add [[timeZone]]: *undefined* to the Record returned by HandleDateTimeOthers.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops!

@FrankYFTang
Copy link
Contributor

I have a major concern about this change and like other members from TG2 to consider this.
Up to this point (before Temporal), Intl.DateTimeFormat format Date() which is basically an instance in time. And the choice of timeZone is decided and remember inside Intl.DateTimeFormat constructor so the same Intl.DateTimeFormat always format the same timezone if the timeZone is configure to be outputted. With this change, this mean if I set the Intl.DateTimeFormat to timeZone A, it will format all Date and other Temporal object to timeZone A but for the Temporal.ZonedDateTime it will format that to timeZone B from Temporal.ZonedDateTime . This significantly change the design contract of Intl.DateTimeFormat .

@justingrant
Copy link
Collaborator

I've found it helpful to think about this PR as addressing three related but not identical questions:

  1. Should optionless ZonedDateTime.p.toLocaleString() use the ZDT's time zone or the system time zone?
  2. Should optionless new Intl.DateTimeFormat().format(zdt) use the ZDT's time zone or the system time zone?
  3. How should conflicts be handled (in either API) when a timeZone option is provided and it conflicts with the ZDT's timeZone property?

It may be easier to handle these questions in the order above, because I suspect that it will be easier to get consensus on (1) than (2), and because resolving (3) may be hard until we decide on (2).

@FrankYFTang @sffc (or anyone else) - do you object to (1) using the ZDT's time zone? If not, then it'd be great to consider (1) settled and scope the discussion to only (2) and (3).

This significantly change the design contract of Intl.DateTimeFormat

Not sure if it affects this discussion, but *all* Temporal objects change the design contract of Intl.DateTimeFormat, in the sense that the type of the Temporal object being formatted will change the behavior of one or more DTF options:

  • Formatting a PlainYearMonth will cause all options related to times and day-of-month to be ignored
  • Formatting a PlainMonthDay will cause all options related to times and years to be ignored
  • Formatting a PlainTime will cause all date-related options to be ignored
  • Formatting a PlainDate will cause all time-related options to be ignored
  • Formatting a PlainDateTime will cause the timeZone option to be interpreted differently. Instead of changing the value displayed, the timeZone option will only control the time zone shown when the timeZoneName option is present.

In other words, the behavior of Intl.DateTimeFormat is only unchanged for Temporal.Instant because its data model matches Date. All other Temporal types cause changes to Intl.DateTimeFormat behavior. We may decide that the ZDT changes are different and deserve different treatment than the others, but we should have a theory for why ZDTs changes are not OK while the others are OK.

@FrankYFTang
Copy link
Contributor

FrankYFTang commented Feb 9, 2023

one possible alternative from this PR is

  1. Make Intl.DateTimeFormat.prototype.format always throw error if the timeZone in the Intl.DateFormat.Time is different from the one from the ZDT and
  2. the ZDT.prototype.toLocaleString always pass it's timeZone own into the options bag and pass into
3. Let dateFormat be ? Construct(%DateTimeFormat%, « locales, options »).

in
https://tc39.es/proposal-temporal/#sup-temporal.zoneddatetime.prototype.tolocalestring
so the dateFormat used inside ZDT.prototype.toLocaleString is always the same as the timeZone in ZDT

Notice 1 and 2 could be discussed separately. For example, as long as you do 2 above, the toLocaleString of the ZDT will be correct. And we can still not take 1 above, and just let Intl.DateTimeFormat 's format to format the ZDT under the timeZone of the Intl.DateTimeFormat and ignore the timeZone from the ZDT.

@sffc
Copy link
Collaborator

sffc commented Feb 10, 2023

TG2 discussion 2023-02-09: https://github.com/tc39/ecma402/blob/master/meetings/notes-2023-02-09.md#temporal-issues-2479-prevent-loss-of-timezone-information

In the meeting, I proposed 7 options; I am adding number 8 that is similar to number 3 except slightly stricter to fully prevent programmer errors [EDIT: and also a further variation in number 9]:

  1. Don't accept ZDT in DTF.p.format
  2. Accept ZDT; throw if the ZDT's time zone does not match the DTF's time zone (including the implicit system time zone)
  3. Accept ZDT; if the DTF was constructed with an undefined time zone, use the ZDT's time zone; otherwise, if the DTF's explicit time zone differs from the ZDT's time zone, throw; otherwise, the time zone matches, and use it
  4. Accept ZDT; use the ZDT's time zone, ignoring the DTF's time zone
  5. Accept ZDT; use the DTF's time zone, ignoring the ZDT time zone
  6. Add some option to the DTF constructor that tweaks this behavior
  7. Add Intl.ZonedDateTimeFormat
  8. Accept ZDT; if the DTF was constructed with an undefined time zone, use the ZDT's time zone; otherwise (if the DTF has an explicit time zone), throw (even if the time zone matches the ZDT)
  9. Accept ZDT; if the DTF was constructed with an undefined time zone, use the ZDT's time zone. Otherwise, use the DTF's time zone, ignoring the ZDT time zone.

@sffc
Copy link
Collaborator

sffc commented Feb 10, 2023

I like option 8 because it makes clear what is going on. new Intl.DateTimeFormat("en", { timeZone: "x" }) will never work, and new Intl.DateTimeFormat("en") will always work. Implementation-wise, this requires storing exactly 1 bit of extra information in the Intl.DateTimeFormat: whether the timeZone is explicit or not. The format calls for all types except ZDT remain the same, and for ZDT, we have a check at the top if the bit is set to throw, and otherwise to do the runtime lookup of the time zone name.

@justingrant
Copy link
Collaborator

justingrant commented Feb 10, 2023

Here's what I think are requirements of a solution:

  • a) ZDT can be formatted using existing DateTimeFormat API.
  • b) Doesn't ignore or override the time zone of a ZDT. (If you want another time zone, call withTimeZone and format the result.)
  • c) Option-less new Intl.DateTimeFormat().format(zdt) matches agreed-on behavior of option-less ZDT.p.toLocaleString, which is to use the ZDT's time zone not the system time zone.
  • d) Conflict between ZDT time zone and timeZone option should throw an exception.

With those in mind, here's how I'd group the proposed solutions.

Preferred (whichever we choose, I'd want toLocaleString to share the same behavior)

  • 3. Accept ZDT. If the DTF was constructed with an undefined time zone, use the ZDT's time zone. Otherwise, if the DTF's explicit time zone differs from the ZDT's time zone, throw; otherwise, the time zone matches, and use it.
  • 8. Accept ZDT. If the DTF was constructed with an undefined time zone, use the ZDT's time zone. Otherwise (if the DTF has an explicit time zone), throw (even if the time zone matches the ZDT).
  • 9. Accept ZDT; if the DTF was constructed with an undefined time zone, use the ZDT's time zone. Otherwise, use the DTF's time zone, ignoring the ZDT time zone.

Meh

  • 6. Add some option to the DTF constructor that tweaks this behavior (my assumption is that this new option would be to opt out of using the ZDT time zone e.g. ignoreZonedDateTimeTimeZone: true, not to opt into it e.g. timeZone: 'auto')

Not OK

  • 1. Don't accept ZDT in DTF.p.format
  • 2. Accept ZDT; throw if the ZDT's time zone does not match the DTF's time zone (including the implicit system time zone)
  • 4. Accept ZDT; use the ZDT's time zone, ignoring the DTF's time zone
  • 5. Accept ZDT; use the DTF's time zone, ignoring the ZDT time zone
  • 7. Add Intl.ZonedDateTimeFormat

@FrankYFTang
Copy link
Contributor

FrankYFTang commented Feb 23, 2023

In the meeting, I proposed 7 options; I am adding number 8 that is similar to number 3 except slightly stricter to fully prevent programmer errors [EDIT: and also a further variation in number 9]:

Option 3, 8, and 9 all consider a concept of “if the DTF was constructed with an undefined time zone” and I would like to clarify 1) WHAT does “the DTF was constructed with an undefined time zone” mean and 2) WHEN would developer write code for such case.

I think the Temporal champion discussion seems to treat the undefined value as a value which the developers “do NOT specify a time zone” not realizing that actually is “the ONLY WAY for developers to SPECIFY to use the host environment's current time zone”.

According to step 29-30 of 11.1.2 InitializeDateTimeFormat ( dateTimeFormat, locales, options ) in
https://tc39.es/ecma402/#sec-initializedatetimeformat

29. Let timeZone be ? Get(options, "timeZone").
30. If timeZone is undefined, then
  a. Set timeZone to DefaultTimeZone(). 

And “6.4.3 DefaultTimeZone ( )” https://tc39.es/ecma402/#sup-defaulttimezone
“The implementation-defined abstract operation DefaultTimeZone takes no arguments and returns a String. It returns a String value representing the host environment's current time zone, which is a valid (6.4.1) and canonicalized (6.4.2) time zone name.”

And also
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
“timeZone The time zone to use. The only value implementations must recognize is "UTC"; the default is the runtime's default time zone. Implementations may also recognize the time zone names of the IANA time zone database, such as "Asia/Shanghai", "Asia/Kolkata", "America/New_York".”

Notice, this is the ONLY WAY a developer could SPECIFY their intent to construct an Intl.DateTimeFormat under “the host environment's current time zone”. In other words, we have to understand, when the developer write not to include a timeZone key with a string value, either by A) not including an option bag, B) an option bag without a timeZone key, or C) an option bag with a timeZone key and an undefined value , the developers are not telling the engine they do not care about what the timezone would be use (so the cannot just call a random() to pick one of the valid timezone from the IANA db), but instead, they are SPECIFYING that timezone MUST be the one which match the user’s “host environment's current time zone”. And this (all three A) - C) lead to the condition “30. If timeZone is undefined” to true) is the ONLY WAY the developers to specify such intent. Also, it is the most common usage of Intl.DateTimeFormat- showing the formatted result according to the “host environment's current time zone”. The specified semantic is ONLY THE “host environment's current time zone” at the time of the construction (not 1 ms before, nor 1 ms after) could be used for such a time zone.

The behavior of Option 3, 8, and 9 violates such intent and provides the developers NO WAY to format the given ZDT under the “host environment's current time zone” with that DTF object.

Therefore, I strongly object to Option 3, 8 and 9. It gave the developers no option to use DTF w/ ZDT for the most common use case so far existing on the internet.

Also, consider the following code snippet

let host_dtf = new Intl.DateTimeFormat(undefined, {timeStyle: "full"});
host_dtf.resolvedOptions().timeZone;
// “America/Los_Angeles”
host_dtf.format(new Date());
// "11:47:58 AM Pacific Standard Time"
host_dtf.format(Temporal.Now.plainDateTime("iso8601"));
// "2/23/2023, 11:47:58 AM"
host_dtf.format(Temporal.Now.plainDateTimeISO());
// "2/23/2023, 11:47:58 AM"
host_dtf.format(Temporal.Now.plainTimeISO());
// "11:47:58 AM"

host_dtf.format(Temporal.Now.plainDateTime("iso8601", "Asia/Taipei"));
// "2/24/2023, 3:47:58 AM"
host_dtf.format(Temporal.Now.plainDateTimeISO("Asia/Taipei"));
// "2/24/2023, 3:47:58 AM"
host_dtf.format(Temporal.Now.plainTimeISO("Asia/Taipei"));
// "3:47:58 AM"

host_dtf.format(Temporal.Now.zonedDateTime("iso8601"));
// "2/23/2023, 11:47:58 AM Pacific Standard Time"
host_dtf.format(Temporal.Now.zonedDateTimeISO());
// "2/23/2023, 11:47:58 AM Pacific Standard Time"

Now, the question is what should be the output of the following two lines? Notice all the results above were either no timezone displayed since the value is not bound to a specific timezone (for the cases of Temporal PT and PDT) or under the “host environment's current time zone”.

host_dtf.format(Temporal.Now.zonedDateTime("iso8601", "Asia/Taipei"));
// ???
host_dtf.format(Temporal.Now.zonedDateTimeISO("Asia/Taipei"));
// ???

I believe it should be
Output I:

host_dtf.format(Temporal.Now.zonedDateTime("iso8601", "Asia/Taipei"));
// "2/23/2023, 11:47:58 AM Pacific Standard Time"
host_dtf.format(Temporal.Now.zonedDateTimeISO("Asia/Taipei"));
// "2/23/2023, 11:47:58 AM Pacific Standard Time"

Not Output II:

host_dtf.format(Temporal.Now.zonedDateTime("iso8601", "Asia/Taipei"));
// "2/24/2023, 3:47:58 AM Taipei Standard Time"
host_dtf.format(Temporal.Now.zonedDateTimeISO("Asia/Taipei"));
// "2/24/2023, 3:47:58 AM Taipei Standard Time"

Because

  1. the host_dtf is constructed to use the “host environment's current time zone” , not "Asia/Taipei". Also because
  2. host_dtf.resolvedOptions().timeZone clearly stated it will be used to format the string under “America/Los_Angeles” but "2/24/2023, 3:47:58 AM Taipei Standard Time" is not under “America/Los_Angeles”.

I would object Option 3, 4, 8 and 9 because they do not allow a DTF constructed for the purpose of formatting under “host environment's current time zone” to be used for ZDT.
I would prefer Option 5 since it allows the developer to use a DTF constructed for the purpose of formatting under “host environment's current time zone” to do its SPECIFIED job. And if the developer wants to display the timezone under the timezone of the ZDT, they could simply construct another DTF for that.
I am open to Option 2 too.
I think 1 and 7 (not mutually exclusive) may be a good way to go since we then have the opportunity to not call Get(options, "timeZone") on its constructor.

@FrankYFTang
Copy link
Contributor

FrankYFTang commented Feb 23, 2023

Here's what I think are requirements of a solution:

I need to add another most important requirement for the solution:

e) The ZDT need to be able to be formatted under "the host environment's current time zone" by the DTF.

and another major requirment

f) The formatting behavior of the DTF need to consistently within the same object and should not preduce conflicting output.

@gibson042
Copy link
Collaborator

@FrankYFTang I think you're overly hung up on "DTF was constructed with an undefined time zone", which is understandable because that's what appears in the option descriptions. But as I attempted to clarify in the February 15 meeting, there could be a different way to create a DateTimeFormat instance in a mode where it uses the time zone of an input ZonedDateTime instance—the key question is whether such a mode should exist. If so, then the mechanism of expressing it with respect to backwards compatibility of details like «timeZone: undefined already has an interpretation of "host time zone" and is the only way to specify that» is a secondary concern.

@FrankYFTang
Copy link
Contributor

FrankYFTang commented Feb 23, 2023

Let's consider if the host time zone is “America/Los_Angeles” and we have the following code

let zdt = Temporal.Now.zonedDateTimeISO("Asia/Taipei");

under Option 5, developers can do the following

let dtf = new Intl.DateTimeFormat(undefined, {timeStyle: "full"});
let formatted_in_host_timezone = dtf.format(zdt);
// "2/23/2023, 11:47:58 AM Pacific Standard Time"
let dtf2 = new Intl.DateTimeFormat(undefined, {timeStyle: "full", timeZone: zdt.timeZone});
let formatted_in_zdt_timezone = dtf2.format(zdt);
// "2/24/2023, 3:47:58 AM Taipei Standard Time"

for either choice, you create only 1 DTF to format the string.
for formatted_in_host_timezone, we need to create one DTF only
for formatted_in_zdt_timezone, we also need to create one DTF only . Notice https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.timezone does NOT create observable object, it only return a pre-existing object which is already created during the constructor of ZDT and stored in its internal slot.
The developers need to understand T.ZDT, T.TimeZone and Intl.DTF to write this code.

Under Option 3, 8, or 9, how would you get the same result as the one under "the host environment's current time zone" ? and how many object do you need to create?
For those who think Option 3, 8 or 9 are good choice, could you write down how the code would looks like to get

formatted_in_host_timezone as "2/23/2023, 11:47:58 AM Pacific Standard Time"

and

formatted_in_zdt_timezone as "2/24/2023, 3:47:58 AM Taipei Standard Time"

and tell me how many objects would need to be created to get each string?

@FrankYFTang
Copy link
Contributor

FrankYFTang commented Feb 23, 2023

there could be a different way to create a DateTimeFormat instance in a mode where it uses the time zone of an input ZonedDateTime instance

It existed already, we do not need a new mode for that in DTF.

new Intl.DateTimeFormat(undefined, {timeStyle: "full", timeZone: zdt.timeZone});

@sffc
Copy link
Collaborator

sffc commented Feb 23, 2023

If you really want to format using the local time zone, you can be explicit, like this:

let dtf = new Intl.DateTimeFormat(undefined, {timeStyle: "full", timeZone: Temporal.Now.timeZone()});
dtf.format(zdt.toInstant());

P.S. If I got to choose by myself, my preference is still for option 1, just don't ever accept ZDT into a DTF. Users migrating from legacy Date can use .toInstant() to get the behavior they are accustomed to.

@FrankYFTang
Copy link
Contributor

FrankYFTang commented Feb 23, 2023

let dtf = new Intl.DateTimeFormat(undefined, {timeStyle: "full", timeZone: Temporal.Now.timeZone()});
dtf.format(zdt.toInstant());

In the above codes, you need to create 3 observable objects to do the job-

  1. one T.TimeZone: Temporal.Now.timeZone() , which internally always call SystemTimeZone() then always call CreateTemporalTimeZone().
  2. one Intl.DateTimeFormat
  3. one T.Instant : Notice in https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.toinstant it always call CreateTemporalInstant() internally

Also, the developers now need to understand T.ZDT, T.TimeZone, Intl.DTF and also T.Now and T.Instant to write this code. TWO more things they need to learn (the T.Now and T.Instant) before they can write that line of code.

In comparison to my demo code to show for Option 5 that use ZDT.p.timeZone, ZDT.p.timeZone does NOT create a new T.TimeZone, it only return the already created TimeZone stored inside zdt. see https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.timezone

so this way you will need to crate 2 more (one T.TimeZone and another T.Instant) , total 3, observable objects to do a task which in my demo code for option 5 that only require to crate one observable object.

BTW, If you are not passing the ZDT itself to the DTF here anyway, then why we need to to construct the DTF with timeZone: Temporal.Now.timeZone() here? instead of just

let dtf = new Intl.DateTimeFormat(undefined, {timeStyle: "full"});
dtf.format(zdt.toInstant());

In that way, you will still need to create one extra observable object - one T.Instant

@FrankYFTang
Copy link
Contributor

Another thing I would like to point out, as the spec stand as today, T.ZoneDateTime can only be used with the Intl.DTF which the timeZone is in the DefaultTimeZone() or otherwise it will throw RangeError during the format. And I do not believe this PR in the current shape address that.

  1. all the T.*.p.toLocaleString() and the Intl.DTF.p.format() call FormatDateTime()
  2. FormatDateTime() call PartitionDateTimePattern()
  3. PartitionDateTimePattern() call HandleDateTimeValue()
  4. in HandleDateTimeValue() if the object is T.ZDT call HandleDateTimeTemporalZonedDateTime()
  5. In HandleDateTimeTemporalZonedDateTime() step 6
5. Let timeZone be ? ToString(zonedDateTime.[[TimeZone]]).
6. If dateTimeFormat.[[TimeZone]] is not equal to DefaultTimeZone(), and timeZone is not equal to dateTimeFormat.[[TimeZone]], then
  a. Throw a RangeError exception.

So... as the Temporal spec which passed stage 3 and stand today, the Intl.DTF (and also T.ZDT.toLocaleString() ) can only be used if the ZDT is in the timezone equal to Temporal.Now.timeZome() and the Intl.DTF is in the same timezone. Otherwise the format will throw RangeError on the ZDT. And that limitation is not addressed by this PR which sent to TC39 several weeks ago.

@@ -927,7 +941,8 @@ <h1>HandleDateTimeTemporalZonedDateTime ( _dateTimeFormat_, _zonedDateTime_ )</h
1. Return the Record {
[[pattern]]: _pattern_.[[pattern]],
[[rangePatterns]]: _pattern_.[[rangePatterns]],
[[epochNanoseconds]]: _instant_.[[Nanoseconds]]
[[epochNanoseconds]]: _instant_.[[Nanoseconds]],
[[timeZone]]: _timeZone_,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the "[[timeZone]]: timeZone" here is always equal to "dateTimeFormat.[[TimeZone]]" and equal to "DefaultTimeZone()" because otherwise, a RangeError would already throw 3 steps above.

@@ -502,7 +504,7 @@ <h1>PartitionDateTimePattern ( _dateTimeFormat_, _x_ )</h1>
<emu-alg>
1. <ins>Let _x_ be ? HandleDateTimeValue(_dateTimeFormat_, _x_).</ins>
1. Let _patternParts_ be PartitionPattern(<del>_dateTimeFormat_.[[Pattern]]</del><ins>_x_.[[pattern]]</ins>).
1. Let _result_ be ? FormatDateTimePattern(_dateTimeFormat_, <ins>_x_.[[pattern]]</ins>, _patternParts_, <del>_x_</del><ins>_x_.[[epochNanoseconds]]</ins>, *undefined*).
1. Let _result_ be ? FormatDateTimePattern(_dateTimeFormat_, <ins>_x_.[[pattern]]</ins>, _patternParts_, <del>_x_</del><ins>_x_.[[epochNanoseconds]]</ins>, *undefined*, <ins>x.[[timeZone]]</ins>).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

notice the only possible value for x.[[timeZone]] here is either undefined or the same value of DefaultTimeZone() but nothing else. The value DefaultTimeZone() came from ZDT and the value undefined came from other data type. There is impossible to have any other values at this point Jan 31, 2023 655e3ef .

spec/intl.html Outdated
@@ -419,7 +418,10 @@ <h1>FormatDateTimePattern ( _dateTimeFormat_, <ins>_pattern_</ins>, _patternPart
1. Perform ! CreateDataPropertyOrThrow(_nf3Options_, *"minimumIntegerDigits"*, _fractionalSecondDigits_).
1. Perform ! CreateDataPropertyOrThrow(_nf3Options_, *"useGrouping"*, *false*).
1. Let _nf3_ be ? Construct(%NumberFormat%, &laquo; _locale_, _nf3Options_ &raquo;).
1. Let _tm_ be ToLocalTime(<del>_x_</del><ins>_epochNanoseconds_</ins>, _dateTimeFormat_.[[Calendar]], _dateTimeFormat_.[[TimeZone]]).
1. <ins>If _timeZone_ is not *undefined*, then</ins>
1. <ins>Let _tm_ be ToLocalTime(_epochNanoseconds_, _dateTimeFormat_.[[Calendar]], _timeZone_).</ins>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand this change, at this point, the only possible values for timeZone is either undefined or the same as dateTimeFormat.[[TimeZone]] and this change prodced exactly as

Let _tm_ be ToLocalTime(<del>_x_</del><ins>_epochNanoseconds_</ins>, _dateTimeFormat_.[[Calendar]], _dateTimeFormat_.[[TimeZone]]).

all the time .

@@ -436,7 +438,7 @@ <h1>FormatDateTimePattern ( _dateTimeFormat_, <ins>_pattern_</ins>, _patternPart
1. Append a new Record { [[Type]]: _p_, [[Value]]: _fv_ } as the last element of the list _result_.
1. Else if _p_ is equal to *"timeZoneName"*, then
1. Let _f_ be _dateTimeFormat_.[[TimeZoneName]].
1. Let _v_ be _dateTimeFormat_.[[TimeZone]].
1. Let _v_ be <del>_dateTimeFormat_.[[TimeZone]]</del><ins>_timeZone_</ins>.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice the timeZone here could only possible be undefined or the same as dateTimeFormat.[[TimeZone]] . there are no other possible values for timeZone , guarded by the RangeError throw in step 6 of HandleDateTimeTemporalZonedDateTime see https://tc39.es/proposal-temporal/#sec-temporal-handledatetimevaluetemporalzoneddatetime

@littledan
Copy link
Member

The undefined value is a SPECIFIED value, and also the only value to be use for the developer to contstruct the Intl.DateTimeFormat to use the DefaultTimeZone() as the timeZone in the constructor. It is not an UNSPECIFIED value, it is a special SPECIFIED value using the JavaScript undefined as a dedicated SPECIFIED value.

Although these are the semantics of Intl.DateTimeFormat today, I don't think it's how most developers think of it. Temporal adds an explicit API to get the current timezone, which I think developers will find a lot more intuitive than the current method with new Intl.DateTimeFormat().resolvedOptions.timeZone.

Given how much else in Date is a calculation based on the "current time zone" (at the time of the calculation, but not attached to any operator object), a reasonable mental model that gets you almost there (modulo timezone changes during JS execution, but how many developers know about that?) is that the current time zone is "in" the Date, rather than the DateTimeFormat object.

Given this, and given how implicit and tricky a missing property on an options bag is as an explicit signal, I think option 8 makes sense. I hope we can continue discussing this question at the next Temporal champions meeting on Thursday.

@sffc
Copy link
Collaborator

sffc commented Mar 14, 2023

"created without an explicit timeZone option" is actually the only way to create the Intl.DateTimeFormat with DefaultTimeZone and there are no other ways to do that

Temporal adds a new, explicit way to do it:

new Intl.DateTimeFormat("en", { timeZone: Temporal.Now.timeZone() })

I will argue you will also need to add such an internal flag to T.ZoneDateTime and T.TimeZone

The fundamental difference here is that ZonedDateTime and TimeZone are clearly defined to carry time zone information. It is part of their name.

The perspective that I think many people in this issue are taking is based on how Intl.DateTimeFormat reads in code. { timeZone: undefined } reads as though it means "please automatically use the most appropriate time zone". The spec currently uses the DefaultTimeZone in all cases. What is being proposed is that when a ZonedDateTime is being formatted, that time zone is a better choice than DefaultTimeZone.

@justingrant
Copy link
Collaborator

I find it helpful when trying to resolve disagreements like this to try to ensure that everyone understands everyone else's position. Not necessarily to agree (that can come later) but at least to have a common understanding about why we believe what we do.

With that in mind, Frank I'll try to summarize below what I understand about your positions. Please let us know if I'm understanding them correctly.

The time zone used by dtf.p.format(zdt) should never ignore the time zone in the DTF instance, even if the DTF instance was created with an undefined timeZone, because:

  • dtf.p.format(new Date()) uses the DTF's time zone (and has always done this for 10+ years), so users will expect this behavior when DTF is used to format a non-Date type too.
  • Users will have a similar expectation from new Intl.DateTimeFormat() and Temporal.Now.zonedDateTimeISO(), because neither API explicitly provides a time zone name. Therefore, any solution should treat those two cases the same way.
  • It is confusing if the value returned by resolvedOptions().timeZone differs from the timezone used in the localized string output of format().
  • There are potential performance and/or memory concerns if DateTimeFormat is forced to store an additional bit to track whether timeZone was undefined or not.
  • Degrading the performance of Date.p.toLocaleString (which is prominently featured in benchmarks) is not acceptable.

Is this correct? Please clarify (with short bullet points if possible!) the things I got wrong. Thanks!

There's also one additional concern that Shane raised that I think supports your position, but I don't remember if you agreed or not. If you do agree, then should we lump it in with the points above in our discussion?

  • DTF achieves performance gains by caching expensive-to-create ICU objects. If the localized name of the time zone varies based on the parameter to format, then we may not be able to get as much (or any?) performance gains from DTF compared to toLocaleString.

@gibson042
Copy link
Collaborator

  • Degrading the performance of Date.p.toLocaleString (which is prominently featured in benchmarks) is not acceptable.

I think it is important to note that degradation is only possible for code that works today, i.e. with input values that are not part of Temporal (undefined, Date instance, or other number-coercible).

@FrankYFTang
Copy link
Contributor

, a reasonable mental model that gets you almost there (modulo timezone changes during JS execution, but how many developers know about that?) is that the current time zone is "in" the Date, rather than the DateTimeFormat object.

Totally disagree.
Consider the following code in your developer console:

// The current timezone is in 'America/Los_Angeles'
let d = new Date();
let dft1 = new Intl.DateTimeFormat("en", {timeStyle: "short"}); 
// the timezone of dft1 is 'America/Los_Angeles'

now, go to your system setting set the timezone to 'America/New_York'

let dft2 = new Intl.DateTimeFormat("en", {timeStyle: "short"}); 
// the timezone of dft2 is 'America/New_York'

let result1 = dft1.format(d);
let result2 = dft2.format(d);

IF "the current time zone is "in" the Date, rather than the DateTimeFormat object." is true, then result1 and result2 will be exactly the same, right?

But no, that is not the case, (try it yourself). Therefore I prove your statement above is FALSE.

@FrankYFTang
Copy link
Contributor

"created without an explicit timeZone option" is actually the only way to create the Intl.DateTimeFormat with DefaultTimeZone and there are no other ways to do that

Temporal adds a new, explicit way to do it:

new Intl.DateTimeFormat("en", { timeZone: Temporal.Now.timeZone() })

The qeustion is NOT how could developers in the future create Intl.DateTimeFormat with explicit timezone , but rather to address Justin's statement in the TG2 meeting note:

"JGT: ... The goal for solving this is to recognize that a very large percentage of Intl.DateTimeFormat objects were not created with an explicit timeZone option. "

JGT claimed up to this point "a very large percentage of Intl.DateTimeFormat objects were not created with an explicit timeZone option. "

Temporal is not yet shipped with any browser yet, so no web page ever can use the new Intl.DateTimeFormat("en", { timeZone: Temporal.Now.timeZone() })
to create Intl.DateTimeFormat to specify the timeZone for the default timezone. So the only way to explicit the use of default timezone TODAY (2023-03-15) are

Intl.DateTimeFormat("en", { timeZone: undefined }) 
Intl.DateTimeFormat("en", {} )
Intl.DateTimeFormat("en") 

and no other way AS TODAY (and ever since the creation of the Intl.DateTimeFormat)

@FrankYFTang
Copy link
Contributor

The fundamental difference here is that ZonedDateTime and TimeZone are clearly defined to carry time zone information. It is part of their name.

I find the "part of the name" argument very strange. Since the name is ZonedDateTime, not ZoneDateTime. The Zoned part is a modifier with a suffix 'd' and make it an adjustive in English to modify DateTime. It only mean the DateTime is zoned, just like no body willl think Ice Cream carry Ice, Hot Dog carry hot, French Fries carry France, or Italaian Saussage carry Italy.

@FrankYFTang
Copy link
Contributor

FrankYFTang commented Mar 15, 2023

The perspective that I think many people in this issue are taking is based on how Intl.DateTimeFormat reads in code. { timeZone: undefined } reads as though it means "please automatically use the most appropriate time zone".

The problem is such definition violate what was clearly understand by the developers on the internet last 8-10 years. It does NOT mean "the most appropriate time zone". It specificlly mean "the timezone the user is in when the object is created". Why?
A. Because dft.resolovedOptions() has a timeZone property.
If it means "use the most appropriate time zone" then it should not have a timeZone property but it does has, and the document stated what that value is "the user default timezone" (not "the most appropriate time zone")

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat

"timeZone
The time zone to use. The only value implementations must recognize is "UTC"; the default is the runtime's default time zone. Implementations may also recognize the time zone names of the IANA time zone database, such as "Asia/Shanghai", "Asia/Kolkata", "America/New_York".
"
Notice it clearly states " the default is THE runtime's default time zone." NOT "the most appropriate timezone" NOR "a random timezone".

console.log(usedOptions.timeZone);
// "America/New_York" (the users default timezone)

also
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/resolvedOptions

"
timeZone
The value provided for this property in the options argument; undefined (representing the runtime's default time zone) if none was provided. Warning: Applications should not rely on undefined being returned, as future versions may return a String value identifying the runtime's default time zone instead.
"
It said clearly "the runtime's default time zone ".

B. Because dft.resolvedOptions().timeZone always match the system time zone while dft is created.

The concept that default represent anything other than " the system default time zone" is simply against the reality of the web and against all the documentation on the web about this.

Let me phrase it again, up until now, the one and only way to specify the Intl.DateTimeFormat to use the default timezone is by using the default (in the 3 ways I specified) and no other ways. I understand in the future after Temporal ship you can use Temporal.Now.timeZone() but that is not yet available to the users. And all the code currently written for Intl.DateTimeFormat can only use the default to create such.

Other printed sources to show the timeZone default value is clearly defined as "the default system time zone" NOT a non-existing concept of "the most appropriate time zone".

[1] Phang, Chong Lip, "ECMAScript 2022 The Definitive Guide to Modern JavaScript", 2022, p124
https://www.google.com/books/edition/ECMAScript_2022/K_uZEAAAQBAJ?hl=en&gbpv=1&dq=Intl.DateTimeFormat+default+timezone&pg=PA124&printsec=frontcover
[2] Phang, Chong Lip, "An Effective Guide to Modern JavaScript (ECMAScript 2017 / ES8)", 2017, p102
https://www.google.com/books/edition/An_Effective_Guide_to_Modern_JavaScript/--gvDwAAQBAJ?hl=en&gbpv=1&dq=Intl.DateTimeFormat+default+timezone&pg=PA102&printsec=frontcover
[3] Ferguson and Cirkel "JavaScript Recipes A Problem-Solution Approach", 2016, Apress, p179
https://www.google.com/books/edition/JavaScript_Recipes/hefIDQAAQBAJ?hl=en&gbpv=1&dq=Intl.DateTimeFormat+timezone&pg=PA179&printsec=frontcover
[4] Casado, Pablo Fernandez, "Domine Javascript 4a Edición", 2022, Ediciones de la U, p79
https://www.google.com/books/edition/Domine_Javascript_4a_Edici%C3%B3n/lANcEAAAQBAJ?hl=en&gbpv=1&dq=Intl.DateTimeFormat+timezone&pg=PA79&printsec=frontcover

@FrankYFTang
Copy link
Contributor

FrankYFTang commented Mar 15, 2023

Is this correct? Please clarify (with short bullet points if possible!) the things I got wrong. Thanks!

You summarized it very well. But there are additional points.

  • Intl.DateTimeFormat always carry a timeZone. (Therefore, in the TG2 meeting note your statement "Today in ECMAScript no ECMAScript object carries a timezone with it." is FALSE).
  • Vast majority of the developer want to create an Intl.DateTimeFormat with the the user's default time zone.
  • As today, which represent all the pre-existing code, the ONLY WAY to "create an Intl.DateTimeFormat with the the user's default time zone" is to use default because there are no other way (for example, developer cannot pass in {timeZone: "system"} or {timeZone:"default" } as string to indicate that.
  • timeZone inside the Intl.DateTimeFormat is an immutable property, once it is created, the value is not changed, even if the user use the UI to chagne the system timeZone to other value, or the user travel to a different place and his cell phone pick up the timeZone change and automatically change the default timezone to a different value. A Intl.DateTimeFormat created after that with default will have a different timeZone than the one which created before that . Therefore, this timeZone is immutable inside the Intl.DateTimeFormat object, it does NOT represent "the user system default at the time calling the format", it represent "the user system default at the time the object being created"
  • All objects created of the 3 classes - Intl.DateTimeFormat and Temporal.ZonedDateTime and Temporal.TimeZone carried a "resolved" timeZone in them (yes, even Temporal.TimeZone, because the timeZone created by Temporal.Now.timeZone() resolved to different timeZone if user change the system timezone between the calls. You have to treat the timeZone carried by these three object the same weight because internally it was resolved by the same routine. For example, if you believe new Intl.DateTimeFormat() carray the timeZone which represent "the most appropriate timeZone" (which I strongly against, but just for argument sake, let's say that is what it represent), then you have to also believe the timeZone created by Temporal.Now.timeZone() or the one carried inside Temporal.Now.zoneDateTimeISO() or Temporal.Now.zoneDateTime() also means "the most appropriate timeZone" because internally they call into the same routine. Then, we will end up have no routine in the JavaScript to represent creating the "default user timeZone", right?
  • Since the timeZone carried by objects of these 3 classes (assuming all created by new Intl.DateTimeFormat() without any argument, created by Temporal.Now.timeZone() which does not expect any argument, created by Temporal.Now.zonedDateTime() while without argument or with undefined, or created by Temporal.Now.zonedDateTimeISO("iso8601") without the 2nd argument or undefined as the 2nd one.) all created by the same underline routine, we have to treat them as equaliy important. Notice the value could be different because user may change the system timezone between the calls.

Another thing I want to point out is, even after we ship Temporal, the following lines still could be different

let tz = Temporal.Now.timeZone();
let dft1 = new Intl.DateTimeFormat('en', {timeZone: undefined});
let opt = {timeZone: Temporal.Now.timeZone()};
let dft2 = new Intl.DateTimeFormat('en', opt);
let dft3 = new Intl.DateTimeFormat('en', {timeZone: tz);

dft1 carry the timeZone of the value "user default timeZone" WHILE the Intl.DateTimeFormat is constructed.
dft2 carry the timeZone of the value "user default timeZone" BEFORE the Intl.DateTimeFormat is constructed.
dft3 carry the timeZone of the value "user default timeZone" WHILE tz is constructed.

If we run the following code and change timezone in between, for example

// set timezone to "Asia/Tapiei"
let tz = Temporal.Now.timeZone();
// set timezone to "Asia/Tokyo"
let dft1 = new Intl.DateTimeFormat('en', {timeZone: undefined});
// set timezone to "Europe/London"
let opt = {timeZone: Temporal.Now.timeZone()};
// set timezone to "Asia/Tokyo"
let dft2 = new Intl.DateTimeFormat('en', opt);
let dft3 = new Intl.DateTimeFormat('en', {timeZone: tz);

(notice I set the system timeZone back to "Asia/Tokyo" before all the calls to new Intl.DateTimeFormat() )
dft1 will set to "Asia/Tokyo"
dft2 will set to "Europe/London"
dft3 will set to "Asia/Tapiei"
passing undefined in, instead of passing Temporal.Now.timeZone() is "an atomic operation" to create that object with the timeZone of the "current user timezone" of its creation, which is not provided by passing in Temporal.Now.timeZone()

@sffc
Copy link
Collaborator

sffc commented Mar 15, 2023

The problem is such definition violate what was clearly understand by the developers on the internet last 8-10 years. It does NOT mean "the most appropriate time zone". It specificlly mean "the timezone the user is in when the object is created". Why?

OK, this is a reasonable argument, and you cited many JS guides that do explicitly explain that { timeZone: undefined } means "time zone of the user". Programmers who learned JS from these guides could certainly have that expectation.

@justingrant
Copy link
Collaborator

Intl.DateTimeFormat always carry a timeZone. (Therefore, in the TG2 meeting note your statement "Today in ECMAScript no ECMAScript object carries a timezone with it." is FALSE).

Sorry, the notes were not transcribed correctly. I filed yet another PR to correct them. Here's what was discussed and what was intended to be in the notes:

JGT: Today in ECMAScript no ECMAScript object that can be formatted by `Intl.DateTimeFormat`
carries a timezone with it. With Temporal.ZonedDateTime, this object carries a timezone, and there’s
a potential conflict. 

Vast majority of the developer want to create an Intl.DateTimeFormat with the the user's default time zone.

Agreed.

As today, which represent all the pre-existing code, the ONLY WAY to "create an Intl.DateTimeFormat with the the user's default time zone" is to use default because there are no other way (for example, developer cannot pass in {timeZone: "system"} or {timeZone:"default" } as string to indicate that.

Agreed.

All objects created of the 3 classes - Intl.DateTimeFormat and Temporal.ZonedDateTime and Temporal.TimeZone carried a "resolved" timeZone in them

Agreed.

Then, we will end up have no routine in the JavaScript to represent creating the "default user timeZone", right?

Agreed. All three classes fetch the current system time zone at time of construction, store it immutably, and use it later.

If we run the following code and change timezone in between, for example

Agree. We all understand the behavior when the system time zone changes and AFAIK are not suggesting to change that behavior.

You have to treat the timeZone carried by these three object the same weight because internally it was resolved by the same routine.

I think this might be where we may not be in alignment yet. Even though the time zone is stored the same way in all three classes, the use cases for each class are different. I'd like to collaborate in tomorrow's meeting to understand those use cases and to figure out how best to make them successful.

@justingrant
Copy link
Collaborator

The fundamental difference here is that ZonedDateTime and TimeZone are clearly defined to carry time zone information. It is part of their name.

I find the "part of the name" argument very strange. Since the name is ZonedDateTime, not ZoneDateTime. The Zoned part is a modifier with a suffix 'd' and make it an adjustive in English to modify DateTime. It only mean the DateTime is zoned, just like no body willl think Ice Cream carry Ice, Hot Dog carry hot, French Fries carry France, or Italaian Saussage carry Italy.

Another way to approach this would be to put yourself in the shoes of a novice developer who's heard of this new "Temporal" thing but has never used any Temporal types. So they open up Google, or VS Code, or browser devools and type "Temporal." and, like novice developers everywhere, look at what autocomplete shows them. Like this:

image

If a developer is looking for a type that has a date, a time, and a time zone, then there's only one obvious name in the list above. More than 10 years ago, Java named their type ZonedDateTime. We can argue whether they should have dropped the "d" or not. But they didn't, and avoiding needless cross-platform variation is important so we chose the same name for Temporal.ZonedDateTime.

Same for Temporal.Now.zonedDateTime. It's very clear which of those methods return something with a time zone.

image

Can we PLEASE all agree that the presence of the letters "Zone" in the name (with or without a "d") provide a very clear hint to the user that there's a time zone in there?

@justingrant
Copy link
Collaborator

justingrant commented Mar 16, 2023

As a starting point for discussing use cases in tomorrow's meeting, here's a few ZDT-formatting use cases that I'm familiar with. Feel free to add more that answer the question: why would a userland developer want to format a ZDT?

Here are a few cases that I expect to be used frequently. Feedback welcome!

  1. Speeding up code that currently uses ZonedDateTime.p.toLocaleString(). I expect this to be the primary use case for formatting ZDT objects, because if perf isn't that important then the developer would probably use toLocaleString which is more ergonomic.

  2. An app that currently formats a global list of data (e.g. a search engine of airplane flight departures) in the user's time zone wants to also format departures in other time zones (e.g. the time zone of the departing airport) side-by-side with the user's local time.

  3. Refactoring an implementation of (2) in a pre-Temporal codebase (where each data point is represented by a {date, timeZone} tuple) to instead use a ZonedDateTime to represent each event.

  4. Showing a list of local dates or times where the source data may be spread across multiple time zones and each data point is represented by a ZDT. Example: an advertising server showing the local time of day or local day of week that users clicked on a particular ad, so that the advertiser can decide what time/day is the best time to pay for ads.

Feel free to add other cases that you've I suspect we may think of some more in the meeting. But maybe we could start with those 4 cases?

@FrankYFTang
Copy link
Contributor

I wrote about two Use Case to show the use of Intl.DateTimeFormat with ZonedDateTime should format into the User Default TimeZone but somehow now it is not display here. Maybe I forget to submit but let me put down again

Use Case 1:

Frank take a vacation in Taiwan (Time Zone : "Asia/Taipei"), he like to schedule a meeting with Shane who is in "America/Los_Angles" time zone on a calendar App. He schedule 2023-03-16 9AM in Taipei time and send that invite to Shane. The code will create the Intl.DateTimeFormat() to indicate to use the default locale and the timeZone Frank reside (which is "Asia/Taipei") and construct a string to send to Shane, when Shane receive that , it construct a ZoneDateTime which has 2023-03-16 9AM in Taipei time . Shane's display is constrcut Intl.DateTimeFormat in "America/Los_Angles". When he view that entry, if it show "2023-03-16 9AM in Taipei time" it is meaningless for him. Since his Intl.DateTimeFormat is constucted by default, it is set to "America/Los_Angles". therefore the format will show that entry to "2023-03-15 6PM in PDT" to him. While Frank create the entry, he also want to know when is that time for Shane, so his app ask the server to find out Shane's timeZone, it get back "America/Los_Angles", but since Frank's Intl.DateTimeFormat is set to "Asia/Taipei" time as default, his code will create a Intl.DateTimeFormat with "America/Los_Angles" for the time in Shane's timeZone and take the ZonedDateTime in Frank's machine (which is set to "Asia/Taipei" to format and let Frank know in Shane's timezone "America/Los_Angles", it is "2023-03-15 6PM in PDT" .

Could you construct a complete use case like this for the case you need Intl.DateTimeFormat to use the timeZone in the ZonedDateTime instead?

@justingrant
Copy link
Collaborator

Meeting 2023-03-16: We could not reach consensus on the issues described above, so we're going to instead build a backup option that will allow maximum flexibility in the future.

  1. DTP.p.format and DTF.p.formatToParts will throw a TypeError when passed a ZDT instance.
  2. If a non-undefined timeZone option is passed to ZDT.p.toLocaleString, throw a TypeError.
  3. Otherwise, ZDT.p.toLocaleString will act as if it:
    a. Adds the ZDT's time zone and calendar to the options passed to the %DateTimeFormat% constructor.
    b. Sends the ZDT's instant to be formatted using those options
    c. mimics the observable behavior for how the Intl.DateTimeFormat constructor accesses options bags.

@sffc's suggestion was that we'd add an optional timeZone parameter to the InitializeDateTimeFormat AO, and presumably make upstream changes too so that that timeZone param can get into InitializeDateTimeFormat .

@FrankYFTang
Copy link
Contributor

I just want to point out one thing here, not an opposition:
"a. Adds the ZDT's time zone and calendar to the options passed to the %DateTimeFormat% constructor."

If you look at the current spec text (step 4 of HandleDateTimeTemporalZonedDateTime) , the toLocaleString will throw RangeError if the calendar of the ZDT is not "iso8601" so the step you describe above will change that (I am not oppose you to change that but just want to point out that is a change)

@FrankYFTang
Copy link
Contributor

Also, probably unrelated to this bug, the current spec also mandate

  1. if the calendar of T.PlainDateTime or T.PlainDate is not "iso8601", the calling to toLocaleString() will always throw RangeError (see step 4 of HandleDateTimeTemporalZonedDateTime and step 6 of HandleDateTimeTemporalDate)
  2. unless you call toLocaleString() with an explicit {calendar: zdt.calendar} , the toLocaleString will always throw RangeError for T.PlaneDate, T.PlainDateTime, T.PlainYearMonth, T.PlainMonthDay

so.... I want to point out, I just realize, not only T.ZDT is a new object which carry a TimeZone and request to format by Intl.DTF which Date does not carray a TimeZone. Similarly

T.PlainDateTime, T.PlainDate, T.PlainYearMonth, T.PlainMonthDay (maybe T.PlainTime too???) T.ZonedDateTime are also a new object which carray a calendar and may conflict (and since it is by default iso8601, it will most likely almost ALWAYS conflict ) with the calendar inside the Intl.DTF.

ptomato added a commit that referenced this pull request Mar 21, 2023
See PR #2479 about which a consensus was not reached. This change allows
Temporal.ZonedDateTime.prototype.toLocaleString to work by overriding the
time zone at the time of creating an Intl.DateTimeFormat object and
formatting the corresponding Temporal.Instant, but disallows calling any
of the Intl.DateTimeFormat methods on a Temporal.ZonedDateTime.

NOTE: The reference code does not implement the spec exactly as written.
It observably modifies the options before passing them to the real
Intl.DateTimeFormat constructor. The behaviour described in the spec is
the correct behaviour.
@ptomato
Copy link
Collaborator

ptomato commented Mar 21, 2023

I've implemented the backup solution in #2522

A few things to note:

  • formatRange and formatRangeToParts also do not work
  • I've kept the calendar behaviour as it was. If we want to change that (along with the other Temporal objects' calendar behaviour as suggested in The calendar inside Intl.DateTimeFormat and the calendar inside Temporal objects #2521) then I think it should be a separate change that we discuss, and I'd prefer to have that happen in a follow-up proposal, which should be possible since it would be changing behaviour that currently throws to be permissible.

ptomato added a commit that referenced this pull request Mar 21, 2023
See PR #2479 about which a consensus was not reached. This change allows
Temporal.ZonedDateTime.prototype.toLocaleString to work by overriding the
time zone at the time of creating an Intl.DateTimeFormat object and
formatting the corresponding Temporal.Instant, but disallows calling any
of the Intl.DateTimeFormat methods on a Temporal.ZonedDateTime.

NOTE: The reference code does not implement the spec exactly as written.
It observably modifies the options before passing them to the real
Intl.DateTimeFormat constructor. The behaviour described in the spec is
the correct behaviour.
@FrankYFTang
Copy link
Contributor

FrankYFTang commented Mar 21, 2023

About TimeZone and ZonedDateTime - I put down a Stage 0 in https://github.com/FrankYFTang/intl-zoneddatetimeformat
I will attempt to propose for Stage 1 in TC 39 May 2023 meeting about 2 months from now.

@ptomato
Copy link
Collaborator

ptomato commented Apr 3, 2023

Closing this for now, since it is superseded by #2522 which was approved at the March 2023 TC39 meeting.

@ptomato ptomato closed this Apr 3, 2023
ptomato added a commit that referenced this pull request Apr 10, 2023
See PR #2479 about which a consensus was not reached. This change allows
Temporal.ZonedDateTime.prototype.toLocaleString to work by overriding the
time zone at the time of creating an Intl.DateTimeFormat object and
formatting the corresponding Temporal.Instant, but disallows calling any
of the Intl.DateTimeFormat methods on a Temporal.ZonedDateTime.

NOTE: The reference code does not implement the spec exactly as written.
It observably modifies the options before passing them to the real
Intl.DateTimeFormat constructor. The behaviour described in the spec is
the correct behaviour.
ptomato added a commit that referenced this pull request Apr 10, 2023
See PR #2479 about which a consensus was not reached. This change allows
Temporal.ZonedDateTime.prototype.toLocaleString to work by overriding the
time zone at the time of creating an Intl.DateTimeFormat object and
formatting the corresponding Temporal.Instant, but disallows calling any
of the Intl.DateTimeFormat methods on a Temporal.ZonedDateTime.

NOTE: The reference code does not implement the spec exactly as written.
It observably modifies the options before passing them to the real
Intl.DateTimeFormat constructor. The behaviour described in the spec is
the correct behaviour.
ptomato added a commit that referenced this pull request Apr 11, 2023
See PR #2479 about which a consensus was not reached. This change allows
Temporal.ZonedDateTime.prototype.toLocaleString to work by overriding the
time zone at the time of creating an Intl.DateTimeFormat object and
formatting the corresponding Temporal.Instant, but disallows calling any
of the Intl.DateTimeFormat methods on a Temporal.ZonedDateTime.

NOTE: The reference code does not implement the spec exactly as written.
It observably modifies the options before passing them to the real
Intl.DateTimeFormat constructor. The behaviour described in the spec is
the correct behaviour.
ptomato added a commit that referenced this pull request Apr 11, 2023
See PR #2479 about which a consensus was not reached. This change allows
Temporal.ZonedDateTime.prototype.toLocaleString to work by overriding the
time zone at the time of creating an Intl.DateTimeFormat object and
formatting the corresponding Temporal.Instant, but disallows calling any
of the Intl.DateTimeFormat methods on a Temporal.ZonedDateTime.

NOTE: The reference code does not implement the spec exactly as written.
It observably modifies the options before passing them to the real
Intl.DateTimeFormat constructor. The behaviour described in the spec is
the correct behaviour.
ptomato added a commit that referenced this pull request Apr 13, 2023
See PR #2479 about which a consensus was not reached. This change allows
Temporal.ZonedDateTime.prototype.toLocaleString to work by overriding the
time zone at the time of creating an Intl.DateTimeFormat object and
formatting the corresponding Temporal.Instant, but disallows calling any
of the Intl.DateTimeFormat methods on a Temporal.ZonedDateTime.

NOTE: The reference code does not implement the spec exactly as written.
It observably modifies the options before passing them to the real
Intl.DateTimeFormat constructor. The behaviour described in the spec is
the correct behaviour.
justingrant pushed a commit that referenced this pull request Apr 20, 2023
See PR #2479 about which a consensus was not reached. This change allows
Temporal.ZonedDateTime.prototype.toLocaleString to work by overriding the
time zone at the time of creating an Intl.DateTimeFormat object and
formatting the corresponding Temporal.Instant, but disallows calling any
of the Intl.DateTimeFormat methods on a Temporal.ZonedDateTime.

NOTE: The reference code does not implement the spec exactly as written.
It observably modifies the options before passing them to the real
Intl.DateTimeFormat constructor. The behaviour described in the spec is
the correct behaviour.

UPSTREAM_COMMIT=b8e56c21cefbdb0ea68c3380a9d83c702461e0b9
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

Successfully merging this pull request may close these issues.

Clarify if / how Intl.DateTimePattern should format the time zone
7 participants