-
Notifications
You must be signed in to change notification settings - Fork 153
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
Conversation
Codecov Report
@@ 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
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
There was a problem hiding this 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.)
Since, |
There was a problem hiding this 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]].
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. |
34335c1
to
d98acd0
Compare
There was a problem hiding this 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.
d98acd0
to
655e3ef
Compare
@@ -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>. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
@@ -502,7 +504,7 @@ <h1>PartitionDateTimePattern ( _dateTimeFormat_, _x_ )</h1> | |||
<emu-alg> | |||
1. <ins>Let _x_ be ? HandleDateTimeValue(_dateTimeFormat_, _x_).</ins> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops!
I have a major concern about this change and like other members from TG2 to consider this. |
I've found it helpful to think about this PR as addressing three related but not identical questions:
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).
Not sure if it affects this discussion, but *all* Temporal objects change the design contract of
In other words, the behavior of |
one possible alternative from this PR is
in 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. |
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]:
|
I like option 8 because it makes clear what is going on. |
Here's what I think are requirements of a solution:
With those in mind, here's how I'd group the proposed solutions. Preferred (whichever we choose, I'd want
Meh
Not OK
|
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 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 And also 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 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
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 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. |
@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 « |
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. 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? 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? |
It existed already, we do not need a new mode for that in DTF.
|
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 |
In the above codes, you need to create 3 observable objects to do the job-
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 |
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.
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_, |
There was a problem hiding this comment.
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>). |
There was a problem hiding this comment.
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%, « _locale_, _nf3Options_ »). | |||
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> |
There was a problem hiding this comment.
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>. |
There was a problem hiding this comment.
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
Although these are the semantics of 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. |
Temporal adds a new, explicit way to do it:
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. |
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
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?
|
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 ( |
Totally disagree. // 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'
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. |
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 JGT claimed up to this point "a very large percentage of Intl.DateTimeFormat objects were not created with an explicit 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() }) 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) |
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. |
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? https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat "timeZone
" 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 |
You summarized it very well. But there are additional points.
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. 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() ) |
OK, this is a reasonable argument, and you cited many JS guides that do explicitly explain that |
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:
Agreed.
Agreed.
Agreed.
Agreed. All three classes fetch the current system time zone at time of construction, store it immutably, and use it later.
Agree. We all understand the behavior when the system time zone changes and AFAIK are not suggesting to change that behavior.
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. |
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: 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 Same for 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? |
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!
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? |
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? |
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.
@sffc's suggestion was that we'd add an optional |
I just want to point out one thing here, not an opposition: 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) |
Also, probably unrelated to this bug, the current spec also mandate
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. |
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.
I've implemented the backup solution in #2522 A few things to note:
|
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.
About TimeZone and ZonedDateTime - I put down a Stage 0 in https://github.com/FrankYFTang/intl-zoneddatetimeformat |
Closing this for now, since it is superseded by #2522 which was approved at the March 2023 TC39 meeting. |
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.
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.
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.
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.
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.
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
Modified
FormatDateTimePattern
to include TimeZone information.Fixes: #2013