-
Notifications
You must be signed in to change notification settings - Fork 152
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
DO NOT MERGE: Proof-of-concept for ZonedDateTime
(Instant + TimeZone + Calendar)
#700
Conversation
This is significantly different than either ZonedAbsolute or ZonedDateTime. It seems to be more like a ZonedAbsoluteDateTime which attempts to contain and harmonise all the types Timezone, Absolute and DateTime. I’ve only gone through this once, so this is a very raw impression: I actually like this. At a first glance mixing the three types rather than just two might give this proposal the missing je ne sais quois that made previous two-mix approaches fail. TL;DR - 👍let’s think this through and discuss further |
@justingrant Hi. I'm curious, why do you pick LocalDateTime as a name? If ZonedDateTime haven't been "occupied", were you choose that instead? I know you said is just a placeholder name, but I'd like to know your rationale behind that as, you know, names communicates a lot the intentions of a model. |
@InExtremaRes - Great question. I avoided This probably isn't the last feedback about naming, so I opened up a new issue. If anyone has more naming feedback, let's discuss further it over in #707. |
d835f3f
to
9fe4bc5
Compare
Codecov Report
@@ Coverage Diff @@
## main #700 +/- ##
===========================================
- Coverage 94.23% 41.88% -52.35%
===========================================
Files 18 18
Lines 6335 3261 -3074
Branches 957 715 -242
===========================================
- Hits 5970 1366 -4604
- Misses 358 1682 +1324
- Partials 7 213 +206
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
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 have not really looked at the implementation at this stage, but concentrated on how the examples needed to be adapted and on the API that we expose. I do think this API nicely solves the problem outlined in #569 but my impression is that it needs some work to get it to the point where it is as streamlined as the other Temporal types. I gave some suggestions for bits that seemed to me like they could be removed in favour of something else.
poc.d.ts
Outdated
getYearMonth(): Temporal.YearMonth; | ||
getMonthDay(): Temporal.MonthDay; | ||
getTime(): Temporal.Time; | ||
valueOf(): never; |
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.
TIL never
This should probably be added to the other types in index.d.ts
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.
Yep, agreed. never
is the right type for a method that always throws an exception.
aa5ba70
to
a633204
Compare
df40949
to
5b55abc
Compare
To prepare for our review meeting tomorrow, I just updated the proposal doc above with the latest details and open issues. |
db8a8db
to
0c92944
Compare
0c92944
to
91650ed
Compare
Updates are now checked in for the two significant LocalDateTime decisions from our 2020-07-31 meeting:
There's three more LocalDateTime updates planned for this week: |
* new Temporal.now.zonedDateTimeISO method * require calendar in Temporal.now.zonedDateTime * Update tests to handle default cal changes * Add missing TS type for ZDT now methods
* Shorten `offsetString` property to just `offset` * Remove `offsetNanoseconds` from ZDT fields. Replace it with `offset`. * NOTE: subminute offsets aren't supported yet, but will be later (tc39#935)
Adds full rounding support to difference(). Fixes tc39#1023. Notes: * See tc39#1023 for details on the algorithm. * I added a bunch of new test cases for various DST-related edge cases. * I suspect this algorothm can be simplified; it's not particularly DRY. * This commit does *not* enable DST awareness in the `round` method. `round` currently works, but without any DST support which is OK for the time being. We can PR a DST-aware `round` in the next few weeks. * The algorithm and/or implementation can probably be adapted for later use in `round`, as well as Duration's `add`, `subtract`, and `round` when `relativeTo` is a ZDT instance. I can help with this next week. * Docs updates for ZDT are not included in this commit. I'll file a new PR for docs changes next week.
5b0947c
to
8777de8
Compare
Rounding is currently implemented by delegating down to DateTime, so I only added a few sanity checks to make sure that basic rounding works. In a later PR we'll add DST-aware rounding and that will be the time to beef up tests.
ZonedDateTime is ready for handoff for a real implementation! Whoo-hoo! The last big missing piece was rounding support in You can look at the recent commit titles to see which of the recent Temporal-wide decisions have already been implemented in the code. I've been trying to keep up but things have been moving pretty fast so a few things are behind main. Here's the things I know about that are NOT done yet:
I put some minor docs updates (e.g. renaming @ptomato - Let me know how I can best help you! I'm happy to meet in real time this afternoon or tomorrow if there are things you want to go over. Also, if you'd like me to implement any of the items above on top of the current implementation, just let me know. Otherwise I'll plan to avoid new commits on this branch to stay out of your way. You can ignore the /polyfill/poc folder that contains TS source and build-steps used to transform TS to .mjs and .d.ts. I'm assuming as soon as you take ownership of the code that all subsequent work will be done directly in .mjs and .d.ts and the TS source will be retired. |
This is fine, thanks. I will start on this as soon as I've closed out 3 other issues today. I assume that the discussions have put us enough on the same page about this that a call won't be necessary, but I'll let you know if I do have questions. |
These conversions are redundant, they either go through ZonedDateTime or TimeZone. Cookbook examples that used these conversions are changed, where possible, to use ZonedDateTime. Where that is not possible because of unimplemented ZonedDateTime methods, we add a FIXME note and fall back to the more verbose TimeZone conversion methods. Updating some of the cookbook examples is taken from #700. Co-authored-by: Justin Grant <[email protected]> Closes: #1026
These conversions are redundant, they either go through ZonedDateTime or TimeZone. Cookbook examples that used these conversions are changed, where possible, to use ZonedDateTime. Where that is not possible because of unimplemented ZonedDateTime methods, we add a FIXME note and fall back to the more verbose TimeZone conversion methods. Updating some of the cookbook examples is taken from #700. Co-authored-by: Justin Grant <[email protected]> Closes: #1026
These conversions are redundant, they either go through ZonedDateTime or TimeZone. Cookbook examples that used these conversions are changed, where possible, to use ZonedDateTime. Where that is not possible because of unimplemented ZonedDateTime methods, we add a FIXME note and fall back to the more verbose TimeZone conversion methods. Updating some of the cookbook examples is taken from #700. Co-authored-by: Justin Grant <[email protected]> Closes: #1026
With #1153 I believe we have got everything needed from this PR, so I wll close this now. |
Wow, it's been a long road with this one! I've been so grateful to be able to help out here. It was fun! |
This PR is a proof-of-concept of a Temporal type that combines an
Instant
, aTimeZone
, and aCalendar
and exposes this combination with a superset of theDateTime
API. This type models an event that did happen, or will happen, from the perspective of a particular place on Earth. It supports use cases involving DST-safe date/time math, time zone conversions, and/or ergonomically retaining the same time zone across multiple operations. This PR is intended to continue discussion from #569 about whether such a type makes sense for Temporal.UPDATE: this type has been approved and we're working on the final details before building a real PR to merge it into Temporal. The real PR will probably land sometime in October 2020, but if you want to play with the type as-is, check out this branch, run
npm i && npm run build
, and then locally point a browser to any page in the built documentation like./docs/index.html
. If you open the browser devtools console, then you can run code using ZonedDateTime.I ran into a few tough problems with this type, e.g. cross-time-zone
difference
, DST corner cases, future times that were valid when stored but are no longer valid due to time zone rule changes, etc. There's a "Discussion" section below with details about these issues and how they were resolved.As a placeholder, the type is namedEDIT: the final name is "ZonedDateTime".LocalDateTime
. There are a few significant differences between what I'm proposing and what's previously been discussed forZonedInstant
. For example, this proposal includes a calendar. So for now there's a different placeholder name to avoid ambiguity. We are bikeshedding on a final name in #707.I've seen from GitHub history that types like this have been declined a few times before, so I know this is a hard problem to solve. Not sure if this proposal is the solution, but I hope it'll help move the discussion forward.
If it's decided that this kind of type doesn't belong in Temporal, that's OK too. I'd try to package it up into an OSS library.
What's in this PR
./polyfill/lib/zoneddatetime.mjs
. All methods are implemented, but this isn't intended to be a real implementation e.g. using slots, using internal APIs, supporting monkeypatching, etc. The intent was to see if there were fundamental blocking issues with the design and to encourage discussion. I wrote only enough code for those goals, although I'd be happy to help PR a real implementation later if it's decided that this type belongs in Temporal../polyfill/poc.d.ts
which is a port ofindex.d.ts
that merges in the this proposal's changes . For just the shape of the new type, see./polyfill/lib/poc/ZonedDateTime.nocomments.d.ts
.ZonedDateTime
instance, and anow.zonedDateTime()
method.ZonedDateTime
:ZonedDateTime
.Instant
->DateTime
->Instant
) seemed easier withZonedDateTime
.DateTime
value.ZonedDateTime
may help with this class of bugs. It'd also help if Temporal had strongly-typed durations (Interaction of Duration vs DST (or other TZ offset changes) #559, Options for handling "3 kinds of durations" problem #686)../polyfill/lib/poc
folder and some changes to./package.json
. You can ignore both of these. They're artifacts of how I'm building the POC by authoring in./polyfill/lib/poc/ZonedDateTime.ts
and using build scripts and transforms to produce./polyfill/lib/zoneddatetime.mjs
and the merged Temporal-wide./polyfill/poc.d.ts
. Tests are handled similarly by transforming./polyfill/lib/poc/ZonedDateTime.test.ts
to./polyfill/test/zoneddatetime.mjs
. A real implementation would be .mjs only.TODO / Open Issues
ZonedDateTime
is intended to be a comprehensive type for all use cases, a minimal type for only math, or something in between.LocalDateTime.from
accept atimezoneOffsetNanoseconds
field? #718 - ShouldZonedDateTime.from
accept atimezoneOffsetNanoseconds
field?getISOCalendarFields()
needed inLocalDateTime
? #704 - IsgetISOCalendarFields()
needed? Or is it not needed because all calendar access is delegated throughDateTime
? @ptomato may have an opinion.Absolute.prototype.toString(timeZone)
have options to omit the offset or time zone? #741 - ShouldInstant.prototype.toString(timeZone)
have options to omit the offset or time zone?LocalDateTime.prototype.compare
#719 - Concerns about options parameter inZonedDateTime.prototype.compare
LocalDateTime
expose its Absolute, TimeZone, Calendar, and (calculated) DateTime? #742 - How shouldZonedDateTime
expose its Instant, TimeZone, Calendar, and (calculated) DateTime?DateTime.prototype.toDate
) be property getters instead? #724 - Should parameterlesstoFoo
&getFoo
methods be property getters instead?Temporal.now
be a method that returns aLocalDateTime
instance? #725 - Crazy idea: shouldTemporal.now
be a method that returns aZonedDateTime
instance?disambiguation
(for DST) vs.overflow
(for ranges) #607 - Suggestion: split option name intodisambiguation
(for DST) vs.overflow
(for ranges)LocalDateTime
? #707 - What should be the long-term name ofZonedDateTime
?Goals
The
ZonedDateTime
type has the following goals:DateTime
" - Provide a familiar API that is mostly a superset ofDateTime
with only a handful of mostly-forward-compatible differences that are required to accommodate the presence of a time zone. Porting code fromDateTime
toZonedDateTime
should be easy.Built-in support for math (EDIT 2020-07-31: removedadd
,subtract
, anddifference
) using any of the three duration kinds: exact durations, dateTime durations, and hybrid (RFC 5545) durations. This type's math methods all have adurationKind
parameter that allows the caller to declare the meaning of the duration. This allows one-line-of-code math regardless of duration kind.durationKind
choice per Champions' consensus.ZonedDateTime
has built-in support for RFC 5545 durations.ZonedDateTime
with other types, especiallyDate
andTime
.Only use public Temporal APIs. This goal is irrelevant if this type makes it into Temporal, but I found it helpful to validate that this type could be built on top of Temporal's primitives without "cheating". So this proposal doesn't directly call internal Temporal APIs, legacyDate
,Intl
, or any other dependencies.Non-goals
ZonedDateTime
. These times can be parsed and disambiguated byZonedDateTime
(e.g. to handle the case where DST rules change after an ISO string is persisted), but if aZonedDateTime
instance is returned to the caller then it's guaranteed to be a real moment in time in a real time zone. Developers who want to work with invalid date/time data can drop down to a lower level of abstraction.durationKind
option, but some unusual cases (e.g. switching aZonedDateTime
to another time zone while keeping clock time constant) may require a little extra code compared to more popular use cases supported by defaults.ZonedDateTime
's implementation deals with RFC 5545 duration math or preventing inconsistencies infrom
andwith
.Proposal Summary
ZonedDateTime
is an ergonomic wrapper for use cases where the time zone is known. It models a real-world event: something that happens at a specific instant (anInstant
time) in a specific place (therefore in a specificTimeZone
) with a human-friendly representation (aDateTime
in a specificCalendar
).This proposal consists of:
A new
Temporal.ZonedDateTime
type(Instant, TimeZone, Calendar)
although the proposal's implementation also derives and caches aDateTime
for easy access to that type's methods and fields.ZonedDateTime
acts like the union ofInstant
,TimeZone
, andDateTime
.DateTime
andInstant
, with only a few changes:inTimeZone
is removed - switching time zones is done viawith({timeZone})
toInstant()
andtoDateTime()
downscaling methodsadd
andsubtract
.hoursInDay
andoffsetString
.timeZone
field. This field will be accepted bywith
andfrom
and emitted bygetFields
.offsetNanoseconds
field for disambiguation and round-trip serialization via objects. This field will be accepted bywith
andfrom
and emitted bygetFields
.from
andwith
add
/subtract
/difference
using exact, date/time, or hybrid (RFC 5545) durationsDateTime
API, non-ISO calendars are supported. See the "Non-ISO Calendars" section below for details.Links between
ZonedDateTime
and the rest ofTemporal
now
,DateTime
,Instant
,Date
,Time
, andTimeZone
that make it easy to get aZonedDateTime
instance from another type.Instant
) or to improve ergonomics (e.g. therelativeTo
option inDuration
's proposed balancing/rounding method.)Discussion
Handling Conflicts and Ambiguity in
from
andwith
ZonedDateTime
internally can be defined with just anInstant
,TimeZone
, andCalendar
, but externally it exposes allDateTime
fields too. So from the user's perspective it's a mix of all four types, which presents a challenge forfrom
andwith
because conflicts are possible.Conflicts between time zone and offset are unavoidable, because time zone definitions can change between the time that a
ZonedDateTime
is serialized and when it's deserialized. See below for detailed discussion of options we'll provide for dealing with these conflicts.Other conflicts will be prevented by only allowing fields that don't overlap so can't conflict: DateTime fields,
offsetNanoseconds
, andtimeZone
.String Initializer behavior in
from
RangeError
, including UTC strings ending in "Z". Time zone is always required. For UTC behavior, usetimeZone: 'UTC'
orInstant
.ZonedDateTime
. It's an interesting theoretical argument about whether "Z" in that context means "UTC time zone" or "zero offset". But IMHO, regardless of its theoretical meaning, allowing "Z" strings will introduce bugs where developers are surprised that all DateTime fields are "wrong" because (unlike legacyDate
) the local time zone is not automatically inferred. To avoid this class of bugs,ZonedDateTime
always requires the time zone to be set explicitly, including for UTC values.toString()
for round-trippable serialization.offset
option (offset?: 'use' | 'prefer' | 'ignore' | 'reject'
(default)) is used to resolve the conflict.reject
throws aRangeError
, which matches behavior elsewhere in Temporal. This is the default to let developers know when their data is ambiguous or erroneous.use
option parses the Instant of theZonedDateTime
instance solely from the date/time fields and the offset. The time zone is still parsed and persisted, but when the instant is matched with the time zone, its local time could be different than what is shown in the ISO string.EDIT 2020-09-02: default was changed touse
is the default because it's guaranteed to be the same UTC time, which is critical for near-future events like scheduled tasks which might be missed if UTC timestamps changed. Also, this default ensures that an ISO string that was valid when persisted will always parse into a validZonedDateTime
. Finally,use
avoids DST ambiguity which is important because a likely reason for this case is a change in DST rules.'reject'
.ignore
does the reverse: keeps local time constant and ignores the offset. This results in a different UTC time than when the ISO string originally stored. This option is useful for far-future events (e.g. conference agendas) where maintaining local time is important.prefer
will use the offset if it's a valid offset for the given time zone, but if it's not valid then the time zone (and thedisambiguation
option if present) will be used. This option is mainly helpful inwith
where it's the default. (See below.)Object Initializer behavior in
from
DateTime
fields includingcalendar
timeZone
- an IANA identifier orTimeZone
instanceinstant
- an ISO string ending in "Z" or anInstant
instanceoffsetNanoseconds
- this is used as an ergonomic alternative todisambiguation
in cases where the offset is known, so that users won't have to manually determine whichdisambiguation
to choose to get the desired offset in the result.timeZone
andeitheryear
/month
/day
fieldsormust be present. Other fields are optional.instant
It's OK if redundant fields are provided, e.g.instant
andyear
. But redundant fields must match in the given time zone. Any conflicting values (e.g. different years inyear
vs.instant
) will cause aRangeError
to be thrown, except...offsetNanoseconds
andtimeZone
can conflict, just like with ISO strings. Conflicts are handled identically to how they're handled for ISO strings, using theoffset
option. See the ISO parsing section above for more details.neitherinstant
noroffsetNanoseconds
is not provided, then the time can be ambiguous around DST transitions. Thedisambiguation
option (disambiguation?: 'compatible' (default) | 'earlier' | 'later' | 'reject'
) resolves this ambiguity.from
does not balance. This matches the behavior ofDateTime
and other non-Duration
Temporal types. Math should be done withadd
andsubtract
.with
acts likefrom
with only the following exceptions:IfUPDATE 2020-10-11 We decided to limit timezone-changing to a withTimeZone method.timeZone
and/orcalendar
fields are provided, then a newZonedDateTime
will be created (using the newtimeZone
/calendar
fields if in input, or the old ones if not) and then other input fields will be applied on top of the newtimeZone
and/orcalendar
values. This matches the behavior ofDateTime.prototype.with({calendar})
.offset
is'prefer'
. This default enables users to make small changes to the time without unexpectedly changing the hour due to disambiguation (because theoffsetNanoseconds
value will be retained if it's still valid) but still allows larger changes (e.g. multiple hours or days) without an exception. For example, if aTemporal.ZonedDateTime
is set to the "second" 1:30AM on a day where the 1-2AM clock hour is repeated after a backwards DST transition, then calling.with({minute: 45})
will result in an ambiguity which is resolved using the defaultoffset: 'prefer'
option. Because the existing offset is valid for the new time, it will be retained so the result will be the "second" 1:45AM. However, if the existing offset is not valid for the new result (e.g..with({hour: 0})
), then the offset will be changed.If the
offsetNanoseconds
field is not provided, then the existingoffsetNanoseconds
field will be used bywith
as if it had been provided by the caller. By default, this will prefer the existing offset when resolving ambiguous results.withTimeZone
can be used to change the time zone while keeping the instant constant. ThetimeZone
andcalendar
fields are not allowed as input towith
. To keep clock time as-is while resetting the time zone, different code is required. Examples:Kinds of Durations & RFC 5545 Duration Math
Central to the design of (and justification for)
ZonedDateTime
is the complexity of usingadd
,subtract
, anddifference
in the face of three kinds of durations that can be stored in aTemporal.Duration
:difference()
method ofTemporal.Instant
.difference()
method ofTemporal.DateTime
,Temporal.Date
,Temporal.Time
, orTemporal.YearMonth
.All three kinds of durations usually have the same value. But if a DST transition (or other timezone-offset change) happened during the duration, then the date/time duration will be smaller or larger than the corresponding exact duration. This occasional difference can lead to bugs like showing reminders 60 minutes late on some mornings, or charging a customer for a 2-day rental on the 25-hour day when DST ends in the Fall.
Hybrid (RFC5545) durations in particular are very challenging without `ZonedDateTime, for two reasons:
Instant
=>DateTime
=>Instant
) which require dealing with DST-related edge cases.ZonedDateTime
tries to handles these cases in an RFC5545-compliant way.EDIT 2020-09-02: We decided that ZonedDateTime supports only hybrid (RFC 5545) durations.
Therefore,ZonedDateTime
supports one-line-of-code arithmetic on any of the three duration kinds, via adurationKind
option on math methods for picking the type of duration.hybrid
is the default because it satisfies the most real-world use cases and is safest for users who don't understand DST very well. But the other kinds are always available for opt-in use.An alternate approach to adurationKind
option would be to use Temporal types to determine duration behavior, e.g.ZonedDateTime
math methods would use hybrid durations only,DateTime
math methods would use date/time durations, andInstant
math methods would use exact durations. The problem: a "use different types" approach requires theDateTime
orInstant
result to be converted back toZonedDateTime
. This conversion is sometimes lossy (if from DateTime) and is always ergonomically awful because the same time zone must be repeated. Also, novice or lazy developers may not convert back toZonedDateTime
, leaving later code vulnerable to DST bugs. To avoid data loss and conversion-related bugs, it's important to keep developers in the world ofZonedDateTime
unless they explicitly opt out of it. For this reason, having adurationKind
option inZonedDateTime
math methods is preferable to relying on different Temporal types to choose the duration kind for math operations.Hybrid math issues and resolutions for DST edge cases:
add
(onInstant
andDateTime
types) currently uses butsubtract
does not. See Inconsistent order of operations for Date and DateTimeplus
andminus
#653 for a discussion.DateTime
,Date
, etc. And there's a good reason forsubtract
to reverse the order of operations, because that enables more-reversible math operations, e.g.A + B - B => A
. We've gotten tentative agreement with Neil Jenkins of the JSCalendar team that JSCalendar will match the reversed order-of-operations of subtraction in Temporal. See RFC 5545 vs DST questions for IETF calendar standards ("calext") group #702.add
anddifference
will increment results by a full day if the result is on the other side of the missing day.subtract
will decrement by one day for the same reason. This isn't special-case logic; instead it "just works" as long as transitions are understood to happen immediately before 0:00 and not at 0:00.hoursInDay
will return 24 for both the day before and the day after the missing day, because this property is always calculated as the number of real-world hours between midnight today and midnight tomorrow using'later'
disambiguation. If a day is skipped, the next midnight disambiguates to 2 days-from-now midnight, which is still 24 real-world hours from today midnight.ZonedDateTime
math operations.hoursInDay
will consider DST transitions at midnight as if they happened at the end of the previous calendar day, because the discontinuity actually happens at one instant before midnight, not between midnight and the next tick.ZonedDateTime
instances are in different time zones, then how to handledifference
given that the same days in each time zone can be different lengths?RangeError
will be thrown whenlargestUnit
is'days'
or larger and the time zones have differentname
fields..with({timeZone: other.timeZone})
and then calculate the same-timezone difference.hours
or smaller units, use.toInstant().difference(other.toInstant())
, as long as it's OK if all days are assumed to be 24 hours long, DST is ignored, and you don't need units larger thanhours
..toDate().difference(other.toDate())
..toTime().difference(other.toTime())
.largestUnit
ofdifference
?hours
is the default forlargestUnit
.Non-ISO Calendars
To support the
DateTime
compatibility goal of this type, and to keep things simple for non-ISO developers,ZonedDateTime
matches DateTime's non-ISO calendar behavior:ZonedDateTime.prototype.calendar
field can be set in the constructor,from
, orwith
calendar
field is used to set the calendar of any DateTime instances created insideZonedDateTime
.withCalendar
method clones the instance with the newcalendar
field set. It behaves the same as.with({calendar})
.from
accepts acalendar
and delegates toDateTime.prototype.from
using that calendar.with
accepts acalendar
and also delegates toDateTime
, with any additional input fields are played on top of a new instance in the new calendar. The same approach is used for a newinstant
and/or a newtimeZone
.getFields
andgetISOCalendarFields
is delegated toDateTime
but a fewZonedDateTime
fields are appended before returning. Results will includecalendar
fromDateTime
.DateTime
delegate to their correspondingDateTime
properties.DateTime
.ZonedDateTime
makes no assumptions about length of any unit except thatInstant
days are 24 hours long.ZonedDateTime
behavior not discussed aboveThe sections above discuss the
add
,subtract
,difference
,with
, andfrom
methods. This section outlines the rest ofZonedDateTime
, almost all of which is trivial wrapper methods which delegate to other Temporal types.epochNanoseconds
bigint
, aTimeZone
, and optionalCalendar
(default to iso8601). This proposal's code also derives and caches aDateTime
from these three values to avoid having to create a new DateTime with most method calls.getFields()
- delegates toDateTime.prototype.getFields
and appendstimeZone
, andoffsetNanoseconds
.getISOCalendarFields()
- same as above but delegates toDateTime.prototype.getISOCalendarFields
.offsetString
,offsetNanoseconds
,isoffsetTransition
,hoursInDay
compare
delegates toInstant.prototype.compare()
.equals
- normally delegates toInstant.prototype.equals()
but will returnfalse
iftimeZone.id
andcalendar.id
don't match too. To check for UTC equality, use.toInstant().equals()
.toLocaleString
- the time zone is automatically set in options before delegating toInstant.prototype.toLocaleString()
. If a time zone option is passed by the caller, then aRangeError
will be thrown if the timezone doesn't match the current instance's time zone. Time zone conversion should be separate from time zone formatting.toString
/toJSON
- string representation has the timezone offset, time zone identifier, and optionally calendar appended to theDateTime
string representation.toDateTime()
- convenience method. This isn't a.dateTime
property to subtly discourage users from callingDateTime
methods instead ofZonedDateTime
methods which will be safer for DST and not lossy.epochXxx
fields from Instant are exposed via getters that delegate toInstant
gettersDateTime
-like fields are exposed via getters that delegate toDateTime
gettersDateTime
:toDate()
,toYearMonth()
,toMonthDay()
,toTime()
.valueOf()
- throwsConversions and Integration With the Rest of Temporal
To make it easy to interop between ZonedDateTime and other Temporal types, this PR proposes:
Temporal.now
would get azonedDateTime()
method.DateTime
,Date
,Time
, andInstant
would each get atoZonedDateTime()
method to convert the current type toZonedDateTime
.TimeZone
would get agetZonedDateTimeFor()
analogous togetInstantFor
andgetDateTimeFor
.All these methods have a similar shape:
now
where it's optional for compatibility with its siblings.now
andInstant
types, the last parameter is an optional calendar name or protocol, like other methods that convertInstant
->DateTime
.Date
/DateTime
/Time
types, the last parameter is anoptions
bag defaulting todisambiguation: 'compatible'
to enable DST disambiguation like other methods that convertDateTime
->Instant
.Date
andTime
methods have aTime
orDate
parameter, in between the time zone and options. For theDate
version, the time is optional and will default to midnight. TheTime
version's Date parameter will be required.ZonedDateTime
.Signatures:
Temporal.now
-zonedDateTime(tzLike: TimeZoneProtocol | string, calendar?: CalendarProtocol | string)
DateTime
-toZonedDateTime(tzLike: TimeZoneProtocol | string, options?: ToInstantOptions)
Instant
-toZonedDateTime.toZonedDateTime(tzLike: TimeZoneProtocol | string, calendar?: CalendarProtocol | string)
Date
-toZonedDateTime(tzLike: TimeZoneProtocol | string, temporalTime?: Temporal.Time, options?: ToInstantOptions)
Time
-toZonedDateTime(tzLike: TimeZoneProtocol | string, temporalDate: DateLike, options?: ToInstantOptions)
TimeZone
-getZonedDateTimeFor(instant: Temporal.Instant, calendar?: CalendarProtocol | string)
Changes and Usage Elsewhere in Temporal
Once
ZonedDateTime
exists, we may choose to change or remove functionality elsewhere in Temporal that now makes more sense to live insideZonedDateTime
. Also, any Temporal features that need both aTimeZone
and aInstant
or aDateTime
are candidates to use ZonedDateTime.Instant.from
remove the ability to parse and/or emit IANA time zone suffixes? If so, this avoids issues like Should Absolute.from() have an option to resolve offset vs. timezone conflicts? #716 and ShouldAbsolute.prototype.toString(timeZone)
have options to omit the offset or time zone? #741? DECISION: YESadd
,subtract
, anddifference
usingdays
(or require arelativeTo
option like withDuration
in Proposal: rounding method & total method for Duration type #789), because a day isn't always 24 hours? See LimitAbsolute
math to hours or smaller units (no days) ? #802. DECISION: YESZonedDateTime
can be used as therelativeTo
option in the new "dedicated Duration balancing method" proposal in Proposal: rounding method & total method for Duration type #789.Prior Art
I'm new here so don't know much history. Feel free to fill in!
As I understand from reading old GitHub issues, there were once
ZonedDateTime
andZonedInstant
types but they were removed because of a number of problems. I tried to identify these problems from reading GitHub history and then tried to address them in this PR. But I'm sure I missed some! If you know from Temporal history about a problem with the last go-round that I missed here, please share!@sffc recently proposed a
ZonedInstant
type. This PR shamelessly copies his idea, works through corner cases, and then adds 3-duration-kinds support and non-ISO calendars.