When printing times, you often not only want to display times, but also distances between them. Conceptually, this includes things like alarms, where you might want to display time remaining, and schedules, where you may want to display how long an event is. So I feel a new type should be created to accommodate this scenario:
duration = new CivilDuration(instant1, instant2) - Create an object representing the difference in time between two instants. It's equivalent to calling new CivilDuration(instant1, instant1.nanoseconds - instant2.nanoseconds) as defined below.duration = new CivilDuration(instant, nanos) - Create an object representing a single instant with a particular duration.duration.start - A new Instant with the original instant's nanoseconds.duration.nanoseconds - The nanos passed to the constructor.duration.milliseconds - duration.nanoseconds / 1000000nduration.seconds - duration.milliseconds / 1000nduration.minutes - duration.seconds / 60nduration.hours - duration.minutes / 60nduration.days - duration.hours / 24nduration.weeks - duration.days / 7nduration.months - The number of months between min and max, negative if instant1.nanoseconds < instant2.nanoseconds. This takes the number of days in each month + leap years into account.duration.years - duration.months / 12n, but takes leap years into account.duration.nanosecondsPart - Number(duration.nanoseconds % 1000000n)duration.millisecondsPart - Number(duration.milliseconds % 1000n)duration.secondsPart - Number(duration.seconds % 60n).duration.minutesPart - Number(duration.minutes % 60n).duration.hoursPart - Number(duration.hours % 24n).duration.daysPart - Roughly Number(duration.days % daysInMonth), but takes into account leap years and the variable wrap-around that's not a simple modulus.duration.weeksPart - Math.floor(duration.daysPart / 7).duration.weekdayPart - duration.daysPart % 7.duration.monthsPart - Number(duration.months % 12n).Notes:
max is the larger and min is the smaller of the two instants. These are not exposed directly.duration.*Part all return positive numbers, and the other getters all return bigints.how would you JSON-serialize/revive ZonedDuration so it can be baton-passed in a web-system? its not very useful if its only localized, ephemereal-data that can't be stored/loaded between client <-> server <-> database.
i'm not sure BigInt is a good idea. if you follow the bigint-issues on https://github.com/tc39/proposal-bigint/issues it has numerous non-obvious gotchas and lack-of-features by-design that you intuitively would expect from normal Number (Number.isFinite(1n) returns false, all Math.xxx methods throw error when passed a BigInt, etc).
@kaizhu256
how would you JSON-serialize/revive ZonedDuration so it can be baton-passed in a web-system? its not very useful if its only localized, ephemereal-data that can't be stored/loaded between client <-> server <-> database.
You pass three values around: the instant's starting milliseconds, the total milliseconds, and the zone. I've updated the original comment to add access to the first (duration.start.milliseconds) and the third (duration.zone), but the second (duration.milliseconds) was there from the start, and the constructor from the start accepted those three, specifically via new ZonedDuration(new Instant(startMs), durationMs, zone).
Literally everything else is computed from those three bits.
i'm not sure BigInt is a good idea. if you follow the bigint-issues on tc39/proposal-bigint/issues it has numerous non-obvious gotchas and lack-of-features by-design that you intuitively would expect from normal Number (Number.isFinite(1n) returns false, all Math.xxx methods throw error when passed a BigInt, etc).
When you're doing date handling and manipulation, you need practically 0% of that. I've literally never used any of the methods you've listed with a date outside of implementing date primitives themselves and in contexts where any object could be reasonably expected, like a generic matching algorithm. So those issues with BigInt don't exist for this particular use case.
just clarifying, this is how JSON serialization/revival works for existing temporal objects (to my understanding)
// serialize to JSON with .toJSON method
JSON.stringify(civilDate) // "2017-12-31"
JSON.stringify(civilTime) // "17:00:00"
JSON.stringify(civilDateTime) // "2017-12-31T12:00:00"
JSON.stringify(instant) // "2017-12-31T00:00:00Z"
JSON.stringify(zonedInstant) // "2016‑12‑31T09:00:00+09:00"
// revive from JSON with .fromString method
CivilDate.fromString("2017-12-31");
CivilTime.fromString("17:00:00");
CivilDateTime.fromString("2017-12-31T12:00:00");
Instant.fromString("2017-12-31T00:00:00Z");
ZonedInstant.fromString("2017‑12‑31T09:00:00+09:00");
would the process work similarly for ZonedDuration? e.g.:
// serialize to JSON with .toJSON method
JSON.stringify(zonedDuration)
/*
{
"instant": "2016‑12‑31T09:00:00Z",
"duration": 1234567890, // ms
"zone": "+09:00" // only canonical-format "+/-xx:xx" supported in JSON
}
*/
// revive from JSON with .fromObject method?
ZonedDuration.fromObject({
"instant": "2016‑12‑31T09:00:00Z",
"duration": 1234567890, // ms
"zone": "+09:00" // only canonical-format "+/-xx:xx" supported in JSON
})
re-edited above to use milliseconds instead of bigint
What would be its significant advantage over just a pair of dates? You don't include intersections, comparisons with 7 possible results or anything like that. I'd suggest adding them.
See also: https://github.com/tc39/ecma402/issues/322. That's just about formatting, but in general such intervals (both year-month and day-time, with arithmetic on them and ability to add or subtract from dates) would be IMO very useful.
@isiahmeadows i think a canonical InstantDuration is more preferable to ZonedDuration. imagine yourself responsible for a multi-zoned application like airbnb/booking.com. its probably saner/more-maintainable to store all time-intervals for such an app as utc-based in a central-database.
and would you trust (on such application's life) that nodejs/safari/firefox/chrome/edge will give consistent ZonedDuration's? probably not -- edge/safari and chrome for example give different, off-by-an-hour locale-datetimes for new Date("1953-09-30T00:00:00Z") when in u.s. central-daylight-timezone (CDT).
console.log(new Date("1953-09-30T00:00:00Z"))
// Tue Sep 29 1953 19:00:00 GMT-0500 (CDT) - for safari and edge
// Tue Sep 29 1953 18:00:00 GMT-0600 (Central Daylight Time) - for chrome
going off-topic, but i also question the usefulness of ZonedInstant. its use-cases are mostly for presentation-views and user-input-logic which could be more efficiently done with Instant.toZonedView / Instant.fromZonedString methods respectively.
@kaizhu256 that definitely isn’t more practical; we always need to know the timezone every time was intended for, since time zones shift so unpredictably.
I'm going to update the original comment to remove the zone from the duration now that I realize it's not necessary to display it correctly. Then it'll just be two Instants or an Instant + bigint nanosecond duration. Edit: And I'll be renaming it to CivilDuration.
@isiahmeadows, note Math.round() throws error for BigInt. i'm not sure how you would round duration.seconds / 60n
an alternative approach is to have everything millisecond-based, with a secondary nanosecond-field from [0, 999999]
@kaizhu256 Couple nits:
13n / 5n === 2n since 13 / 5 === 2.6 and Math.trunc(2.6) === 2. (Note that BigInt has no concept of infinity, so division by 0 generates an exception.)we always need to know the timezone every time was intended for, since time zones shift so unpredictably.
yes, but ZonedInstant is not the answer. i'm skeptical this proposal can guarantee consistent ZonedInstant DST calculations across all engines.
if you want consistent datetime-arithmetic across all browsers/regions, then you're probably not going to be using ZonedInstant. instead, you'll likely calculate DST offsets yourself in utc-based time, to guarantee browser-consistency.
@kaizhu256
yes, but ZonedInstant is not the answer. i'm skeptical this proposal can guarantee consistent ZonedInstant DST calculations across all engines.
Not sure this is a valid concern. The data required for DST calculations come from an official IANA database, so the chances of issues arising inconsistencies at the data level are minimal at best.
Implementation-wise, if they can't read a table correctly, that's akin to a spec violation, and engines typically treat spec violations for supported stable features as high-priority bugs, second only to security bugs. I've even seen a few cases where literally no major browser was spec-compliant and once discovered, they all fixed their behavior to match spec. Also, the tz database comes with both the data and a few C files for parsing and reading from it, so there's a lot of relevant code that engines don't have to write.
So no, inconsistent DST calculations is not at all a concern of mine.
Not sure this is a valid concern. The data required for DST calculations come from an official IANA database, so the chances of issues arising inconsistencies at the data level are minimal at best.
repeating my previous example, this off-by-an-hour-bug among browsers with Date won't happen with ZonedInstant? its a real-world production-bug discovered when calculating birth-date.
console.log(new Date("1953-09-30T00:00:00Z"))
// Tue Sep 29 1953 19:00:00 GMT-0500 (CDT) - for safari and edge
// Tue Sep 29 1953 18:00:00 GMT-0600 (Central Daylight Time) - for chrome
currently the only way to fix the production-bug above is to use utc-based time for everything.
@kaizhu256 that's exactly something this proposal will address, by mandating these values according to existing databases in a way that Date currently delegates to "implementation-defined".
ok then that would be a +1. suggest the README should highlight something like this as a motivation.
@kaizhu256 the readme links to https://maggiepint.com/2017/04/09/fixing-javascript-date-getting-started/ which I think highlights it pretty well.
@pipobscure @sffc are durations something we plan to deal with in this proposal?
Okay, I just realized that this already exists as a TODO at https://github.com/tc39/proposal-temporal/issues/38. Thanks for your suggestion and research, @isiahmeadows, I’ll look into the current version of the spec and try to add something along the lines of what you suggested since that seems very reasonable and then we could work from there.
Closing the other issue in favor of this one since it has more discussion.
The question is, do we expose this functionality as a CivilDuration class as suggested by @isiahmeadows or does it make more sense to just add a bunch of functions to Instants?
This is addressed byt the Duration object in https://github.com/tc39/proposal-temporal/blob/master/objects.md#Duration but needs more fleshing out.
@pipobscure awesome! I hadn’t looked enough apparently, I didn’t know we already have an object for that. In that case, I guess this has been sufficiently addressed and I could close this. Meanwhile, I could help with fleshing out the spec for Duration.
Thanks again, @pipobscure and @isiahmeadows.
Most helpful comment
@kaizhu256 that's exactly something this proposal will address, by mandating these values according to existing databases in a way that Date currently delegates to "implementation-defined".