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

Proposed Temporal changes required to adopt RFC 5545 (iCalendar) meaning of durations #759

Closed
justingrant opened this issue Jul 11, 2020 · 17 comments

Comments

@justingrant
Copy link
Collaborator

justingrant commented Jul 11, 2020

At the end of the 2020-07-09 meeting, we (@sffc, @gibson042, @pdunkel, and I) unexpectedly reached tentative consensus in favor of Option 3c from #686, which is to define Temporal.Duration to mean the same as durations are defined in iCalendar/RFC5545. @ptomato, @littledan, @ryzokuken - you guys were in the meeting but I'm not sure if you were still there at the end, so you should weigh in!

Anyway, this writeup attempts to flesh out the changes this decision would imply for Temporal APIs. It's OK to have buyer's remorse; if you agreed in the meeting but are skeptical after seeing the implications, then now is a good time to speak up.

Requirements

Here's a summary of how RFC 5545 durations differ from current Temporal durations:

  • RFC 5545 duration have a date portion that acts like a DateTime difference and a time portion that represents an Absolute difference. So PT2H would be considered absolute time, P2D would be two calendar days, and P2DT2H would mean 2 calendar days plus 2 real-world hours.
  • RFC5545 durations are persisted using ISO 8601 format, with three exceptions:
    • An optional sign prefix is supported. Negative durations (No negative durations? #558) are represented with a single minus-sign prefix, e.g. -P2DT2H. A no-op plus-sign prefix is also supported.
    • RFC 5545 durations do not support months or years; weeks is the largest unit supported.
    • RFC 5545 durations do not support fractional seconds or units smaller than "seconds".
  • RFC 5545 also defines a specific set of rules for using durations in math operations:

IMHO, I don't think it makes sense to adopt the units limitations (no months/years/partial-seconds) in Temporal. There are many common use-cases (e.g. localized formatting, stopwatch timing, etc.) that require these units. Also, being unable to parse valid ISO 8601 durations seems like a bad idea. Other than those unit limitations, my assumption is that "Adopt RFC 5545 durations" means that we'd adopt the rest of the requirements above.

At the end of this issue are excerpts from the RFC 5545 spec that describe the above in more detail.

Specific Changes

Below are one possible implementation of the requirements above. Note that each of the top-level numbered points below are intentionally independent. We could PR them separately even if we choose not to do all of them.

Feel free to suggest other ways to achieve the same results.

1a. DateTime math methods should require a time zone parameter

  • 1a.1 DateTime.prototype.plus and DateTime.prototype.minus should add a REQUIRED second parameter for the time zone. e.g. dt.plus({hour: 1}, 'Europe/Paris', {disambiguation: 'reject'}). Developers who want the current clock-time-only DateTime math semantics can pass 'utc' for the time zone. (Or null we we adopt @sffc's "null time zone" idea from LocalDateTime fields in from and with #706 where null implies DateTime behavior.)
  • 1a.2 DateTime.prototype.difference should add a REQUIRED second parameter for the time zone. In theory it's only required if the times of both instances are different, but IMHO it's cleaner to just make it always required. Users who want date-only behavior can get it via a 'utc' (or null) time zone or dt.toDate().difference(other).
  • 1a.3 How should we handle the case where this (for all math methods) and/or other (for difference) are ambiguous due to DST? Should we have two disambiguation options on each call? One option that applies to both endpoints? Or should we just use compatible disambiguation because this is what RFC 5545 mandates for its equivalent of DateTime.prototype.toAbsolute? Note that if we offered DST disambiguation control, then presumably we'd need to rename the existing disambiguation option, e.g. to overflow per Suggestion: split option name into disambiguation (for DST) vs. overflow (for ranges) #607.
  • 1a.4 There's one more possible disambiguation point if there's both a date and a time in the duration. For example, when adding P2DT12H to 2020-03-06T02:30 in America/Los_Angeles, after the date portion is added the result is 2020-03-08T02:30 which is in the middle of an hour skipped by DST. DST disambiguation will be needed for that intermediate value. I've been working through this problem for LocalDateTime and I have some tentative solutions, of which the most appealing is simply to use compatible disambiguation which is what RFC 5545 relies on for its equivalent of Date.prototype.toAbsolute. BTW, I'd prefer to get a consensus recommendation on how we should solve this from the IETF owners of RFC 5545. See RFC 5545 vs DST questions for IETF calendar standards ("calext") group #702.

======== OR ======== (1a/1b/1c are mutually exclusive)

1b. Remove DateTime math methods (or even remove the entire DateTime type?) and rely on LocalDateTime for non-Absolute date/time math

  • 1b.1 If we think that adding time zone + disambiguation would make DateTime math methods too complex, should we just remove math methods from DateTime and push developers to just use LocalDateTime for math instead? Here's an example of DateTime math and its LocalDateTime equivalent:
// Option 1a
dt.plus(
  { days: 2, hours: 12 }, 
  'Europe/Paris', 
  { disambiguation: { start: 'earlier', end: 'reject' }, overflow: 'reject'  }
);

// Option 1b
dt.toLocalDateTime(
  'Europe/Paris', 
  { disambiguation: 'earlier' }
).plus(
  { days: 2, hours: 12 }, 
  { disambiguation: 'reject', overflow: 'reject' }
);
  • 1b.2 Developers who wanted DateTime's current timezone-less, DST-unsafe math behavior could use 'utc' or an offset timezone when creating the LocalDateTime.
  • 1b.3 Disambiguation of this (see 1a.3) would be handled on the "convert to LocalDateTime" step rather than being part of the add/subtract/difference operation. This may be clearer than trying to disambiguate multiple DateTime values in one call per (1a.3) and (1a.4).
  • 1b.4 An even more radical idea would be to remove DateTime completely and just rely on LocalDateTime. @sffc has encouraged considering this option. I admittedly haven't thought through the implications of this and don't yet have an opinion for or against it. But I wanted to mention it here for completeness. BTW, if we did remove the DateTime type, then we'd probably want to rename LocalDateTime to DateTime for brevity.
  • 1b.5 Assuming we keep the DateTIme type but remove its math methods, then should we make its name more obscure (e.g. ZonelessDateTime) to avoid disappointing developers who expect to find math capability in a type called DateTime?
  • 1b.6 Obviously (1b) depends on adopting LocalDateTime in DO NOT MERGE: Proof-of-concept for ZonedDateTime (Instant + TimeZone + Calendar) #700. If we decide not to use that type, then this option is moot.

======== OR ======== (1a/1b/1c are mutually exclusive)

1c. Limit DateTime's plus/minus methods to days or larger units, and remove its difference method.

  • 1c.1 This is like (2) below, but for DateTime.
  • 1c.2 DateTime.prototype.plus and DateTime.prototype.minus would throw if the duration has any non-zero hour (or smaller) units. Developers who want to add durations with both date and time units would have to use LocalDateTime math methods. (Or roll their own implementation, which is hard to get right because of DST disambiguation in the middle of the calculation.)
  • 1c.3 DateTime.prototype.difference will be removed, because it's likely to be unexpected (and therefore bug-inducing) for it to throw only when the arguments have different local times. Developers can use toDate().difference(other) for date-only values. Developers who have DateTimes with different times who wanted DST-ignoring math would use .toLocalDateTime('utc').difference(). Otherwise, developers would use toLocalDateTime into a real time zone and calculate the difference there.
  • 1c.4 TS types for DateTime.prototype.plus and DateTime.prototype.minus would be revised to accept a Duration | DateDurationLike instead of Duration | DurationLike currently. This would enable compile-time detection of object literals with illegal units.

2. Absolute math methods should be limited to hours or smaller units.

  • 2.1 Absolute.prototype.plus and Absolute.prototype.minus should throw if the duration has any non-zero day (or larger) units. Developers who really want to calculate (24-hour) days can use .toDateTime('utc').plus() (unless we choose 1c) or .toLocalDateTime('utc').plus().
  • 2.2 Absolute.prototype.difference should throw if largestUnit is days or larger. Developers who really want Temporal to calculate the number of 24-hours days can use .toDateTime('utc').difference() (unless we choose 1c) or .toLocalDateTime('utc').difference().
  • 2.3 Should Absolute math methods have an optional time zone parameter to allow math with days and larger units without requiring a round trip to DateTime? My inclination is "no" to simplify the ergonomics for working with absolute time, but I admittedly haven't though this through very carefully. Feedback welcome.
  • 2.4 Even if we don't adopt RFC 5545 semantics for durations, these changes may be good anyways to prevent ambiguity in days math with Absolute.
  • 2.5 TS types for Absolute.prototype.plus and Absolute.prototype.minus would be revised to accept a Duration | TimeDurationLike instead of Duration | DurationLike currently. This would prevent object literals with illegal units.

3. Temporal math methods should match RFC 5545 expectations around negative durations and (maybe) order-of-operations

  • 3.1 It'd be weird to align with RFC 5545 but couldn't parse or use negative durations defined in that standard. A draft proposal for negative durations is at No negative durations? #558 (comment).
  • 3.2 RFC 5545 requires largest-units-first order of operations. plus in Temporal already works that way; minus doesn't. In our previous champions-meeting discussion about negative durations, @pdunkel was concerned that using largest-first order-of-operations for subtraction would break reversibility, e.g. A - B + B === A. After more reading of the RFC 5545 spec, I'm now unsure about whether the spec actually defines subtraction behavior, because all its discussion is about "addition" and all its examples of negative durations are under 24 hours where order of operations is irrelevant anyways. My suggestion: let's discuss this problem with the calext/calconnect working group that owns the iCalendar spec, and see if we can come to consensus about what the subtraction order-of-operations should be, and then adopt that behavior in Temporal. And if they don't care, we're probably free to keep @pdunkel's preferred reversibility. This is already on the list of questions for that working group in RFC 5545 vs DST questions for IETF calendar standards ("calext") group #702. I just need an email intro!

("4" was removed but I forgot to renumber the "5" section, so leaving numbering as-is)

5. The Duration type's math and balancing methods will get more complicated

  • 5.1 Balancing of durations across the hours/date boundary is the problem. Because balancing is currently baked into from, with, plus, and minus, this implies a lot more complexity for those methods OR we could remove balancing from at least some of them. I'll discuss each below.
  • 5.2 from and with seem like the easiest cases: we could simply remove the balance feature and offer balancing via a balance method. Note that there's already been discussion about a separate balancing method to handle rounding too. See @sffc's idea at Round a Temporal.Duration to the nearest small unit #337 (comment).
  • 5.3 minus is the tough one because subtraction inherently needs balancing. Even if we support negative durations, RFC 5545 doesn't support mixed-sign units; durations are either negative or positive as a whole. So balancing is always needed for minus. Here's a few possible solutions:
const dur = Temporal.Duration.from('P2M3DT18H30M');

// Idea A: Add time zone, absolute starting point, and optional calendar to `options`.
const dtStart = Temporal.DateTime.from('2020-03-08T00:00');
dur.minus(
  {days: 2, hours: 12}, 
  {
    timeZone: 'America/Los_Angeles',
    start: dtStart.toAbsolute('Europe/Paris', { disambiguation: 'reject' }), 
    calendar: 'japanese',
    disambiguation: 'reject'
  }
);

// Idea B: require a `LocalDateTime` second parameter
const ldtStart = Temporal.LocalDateTime.from('2020-03-08T00:00-0800[America/Los_Angeles]');
dur.minus(
  { days: 2, hours: 12 }, 
  ldtStart, 
  { disambiguation: 'reject' }
);

// Idea C: remove plus and minus from `Duration`
// FWIW, Idea B above is an ergonomic wrapper around the code below 
const ldtStart = Temporal.LocalDateTime.from('2020-03-08T00:00-0800[America/Los_Angeles]');
ldtStart.difference(
  ldtStart.minus(dur, { disambiguation: 'reject' }),
  { disambiguation: 'reject' }
);
  • 5.4 Duration.prototype.plus doesn't have a balancing option today (see Suggestion: consistent balancing behavior for Duration's plus and minus methods #645), but it'd need it if we did negative durations. So however we solved this problem for minus, we'd want to clone the same solution for plus.
  • 5.5 A nice side effect of the changes above would be that we could support calendar-aware balancing, which implies that we could do Duration math for months and years too, and could offer a Duration.prototype.difference method.
  • 5.6 Balancing that doesn't cross the hours/days boundary doesn't need to know the time zone, starting absolute, or calendar. Should including those parameters be optional? Here are five possibilities:
    • 5.6.1 Required, because it'd be easier to catch bugs earlier instead of waiting until the app is in production when 24-hour timers (e.g. "total game play") roll over to the next day. TS could also catch these bugs at compile time.
    • 5.6.2 Optional, because many use cases (and therefore durations) have only time units.
    • 5.6.3 Time zone could be required but start could be optional (ignored, actually) if time zone is 'utc'. This is the same as 5.6.1 except that the TS types would treat start as optional.
    • 5.6.4 Time zone could be required but start could be optional (ignored, actually) if time zone is null. This is the same as 5.6.3 except TS would have an easier time detecting the null type vs 'utc'.
    • 5.6.5 Required, but also add a TimeDuration class without YMWD fields and whose math and balance methods wouldn't accept timezone, starting point, and calendar options. This type would be returned by Absolute.prototype.difference and Time.prototype.difference.

Relevant Specification Excerpts

From https://tools.ietf.org/html/rfc5545#section-3.3.6:

Format Definition: This value type is defined by the following notation:

   dur-value  = (["+"] / "-") "P" (dur-date / dur-time / dur-week)

   dur-date   = dur-day [dur-time]
   dur-time   = "T" (dur-hour / dur-minute / dur-second)
   dur-week   = 1*DIGIT "W"
   dur-hour   = 1*DIGIT "H" [dur-minute]
   dur-minute = 1*DIGIT "M" [dur-second]
   dur-second = 1*DIGIT "S"
   dur-day    = 1*DIGIT "D"

If the property permits, multiple "duration" values are specified by a COMMA-separated list of values. The format is based on the [ISO.8601.2004] complete representation basic format with designators for the duration of time. The format can represent nominal durations (weeks and days) and accurate durations (hours, minutes, and seconds). Note that unlike [ISO.8601.2004], this value type doesn't support the "Y" and "M" designators to specify durations in terms of years and months.

The duration of a week or a day depends on its position in the calendar. In the case of discontinuities in the time scale, such as the change from standard time to daylight time and back, the computation of the exact duration requires the subtraction or addition of the change of duration of the discontinuity. Leap seconds MUST NOT be considered when computing an exact duration. When computing an exact duration, the greatest order time components MUST be added first, that is, the number of days MUST be added first, followed by the number of hours, number of minutes, and number of seconds.

Negative durations are typically used to schedule an alarm to trigger before an associated time (see Section 3.8.6.3).

From https://tools.ietf.org/html/rfc5545#section-3.3.5

FORM #3
DATE WITH LOCAL TIME AND TIME ZONE REFERENCE

The date and local time with reference to time zone information is identified by the use the "TZID" property parameter to reference the appropriate time zone definition. "TZID" is discussed in detail in Section 3.2.19. For example, the following represents 2:00 A.M. in New York on January 19, 1998:

TZID=America/New_York:19980119T020000

If, based on the definition of the referenced time zone, the local time described occurs more than once (when changing from daylight to standard time), the DATE-TIME value refers to the first occurrence of the referenced time. Thus, TZID=America/ New_York:20071104T013000 indicates November 4, 2007 at 1:30 A.M. EDT (UTC-04:00). If the local time described does not occur (when changing from standard to daylight time), the DATE-TIME value is interpreted using the UTC offset before the gap in local times. Thus, TZID=America/New_York:20070311T023000 indicates March 11, 2007 at 3:30 A.M. EDT (UTC-04:00), one hour after 1:30 A.M. EST (UTC-05:00).

A time value MUST only specify the second 60 when specifying a positive leap second. For example: 19970630T235960Z. Implementations that do not support leap seconds SHOULD interpret the second 60 as equivalent to the second 59.

@sffc
Copy link
Collaborator

sffc commented Jul 12, 2020

It's worth adding 1c, throw an exception when the Temporal.Duration has illegal fields when operating on a Temporal.DateTime. This is the currently existing behavior when you give a Temporal.Duration with illegal fields to a Temporal.Absolute. There would be no timeZone option. This assumes that we have LocalDateTime.

@justingrant
Copy link
Collaborator Author

has illegal fields when operating on a Temporal.DateTime

By "illegal fields" do you mean any time units for plus/minus and args with different times for difference?

@justingrant
Copy link
Collaborator Author

@sffc - Ping! (If possible, I want to wrap up feedback on this issue before this week's meeting)

has illegal fields when operating on a Temporal.DateTime

By "illegal fields" do you mean any time units for plus/minus and args with different times for difference?

@sffc
Copy link
Collaborator

sffc commented Jul 14, 2020

Yes, that's what I meant for plus/minus. I hadn't thought about what my suggestion meant for difference.

@justingrant
Copy link
Collaborator Author

OK, I added (1c) with this suggestion. That said, an API that sometimes throws and sometimes doesn't based on the time portion of its arguments feels like it will be easy for bugs slip through that would only be caught in production. With 1b and 1a, failures would tend to happen on the first run of the code or could be caught at compile/lint time.

Also (from #686 (comment)):

I've been thinking lately that the best way to handle these use cases is via math methods on the Date type. If you see code like foo.toDate().difference(other) then it's really clear that only dates will come back, and it's always safe for DST. So although date-only math is safe for a DateTime, the easy alternative using Date makes me relatively unworried about removing DateTime math or making it harder to use by requiring a time zone parameter.

@sffc
Copy link
Collaborator

sffc commented Jul 15, 2020

I agree that it would be weird for .difference() to throw when the time portion is different.

Can you change 1c to have the stated behavior for .plus() and .minus(), and remove the .difference() method, instead preferring the .toDate().difference() pattern?

@justingrant
Copy link
Collaborator Author

Sounds good. 1c is now updated to remove difference.

@pipobscure
Copy link
Collaborator

pipobscure commented Jul 15, 2020

So far this sounds ok to me.


As for reversibility, what is meant us that for any given datetime A and duration D the statements:

A.plus(D) = B and B.minus(D) = A
and
A.minus(D) = C and C.plus(D) = A
as well as
B.difference(A) = D and A.difference(C) = D

should be true. In short date maths should remain commutative/reversible as mentioned above.


I also agree that we need to keep months/years supported. Otherwise we’ll need to start a whole new secondary duration object.


One of the big reasons why I’m OK with this is that because durations don’t balance (by default) and you can apply this to Absolutes it is still possible to get any other behaviour by carefully crafting the durations. And as a default behaviour: doing what every calendaring app on the planet does seems quite reasonable.

@justingrant
Copy link
Collaborator Author

@pdunkel - how would you rank 1a (add TimeZone arg to all Date math methods) vs. 1b (remove math from DateTime; use Date or LocalDateTime math instead) vs. 1c (remove difference from DateTime; throw if a duration with nonzero time is passed to plus/minus)? Also, are there any of those three options that you think we definitely shouldn't do?

I also agree that we need to keep months/years supported. Otherwise we’ll need to start a whole new secondary duration object.

Does this mean that you like 5.5 (copied below)? Or do you mean something else?

5.5 A nice side effect of the changes above would be that we could support calendar-aware balancing, which implies that we could do Duration math for months and years too, and could offer a Duration.prototype.difference method.

@pipobscure
Copy link
Collaborator

pipobscure commented Jul 16, 2020

Ok in more detail:

1.a: Remove maths from DateTime: why??? the DateTime objects are intentionally like a self-winding clock independent of any Time-Zone. So Date/Time-Maths on those are the easiest thing ever. Time-Zones simply do not apply. (see end comments for solution to “how to do DST”)

1.b: Same thing. DateTime does not need a TimeZone parameter for maths. Time-Zones do NOT apply. And this distinction is very important, because without it the teachability and clarity of the whole suffers.

1.c: WHY??? Their behaviour is well defined and entirely predictable. As a matter of fact NOT doing 1 and keeping things on 1 DateTime and Time exactly the same is a pre-requisite to doing RFC5545 !!!

2: Restricting Absolute Maths: There is no way to do it correctly without a calendar. This is the case already so nothing new here. And also not what we suggested agreement on.

3: This is what I was mainly referring to and what I thought had achieved a form of rough agreement: RFC 5545 durations, which are basically means: RFC5545 does the larger units given a calendar which defaults to ISO and the sub-day units as in an absolute way thereby ignoring DST changes making the need to have a TimeZone entirely superfluous.

5: I see this a complimentary / orthogonal to everything else. The reason this would matter at all is because NOT balancing would let you control exactly how your maths are done, which in my view is essential to make 3 even feasible.

In short I believe we need to have 3 and 5 to make this work. We can then optionally add (And I believe we should) a LocalDateTime like Justin proposed which combines DateTime/TimeZone/Calendar/Absolute into one.

What we would be left with is:

  • Date which works as it does now
  • Time which also works as it does now
  • DateTime which also works as it does now
  • Absolute which now supports full math via RFC 5545
  • LocalDateTime which does the hybrid and whose difference/plus/minus have (thanks RFC 5545) a clear way to deal with DST changes.

So to answer the question directly: I rank all of the 1s as entirely unsuitable, and yes I think 5 has merit given that I understand it as durations do not balance by default, but can be balanced explicitly given a calendar. What that implies for Duration is that with, from, plus and minus all accept a calendar argument/parameter and balance iff the calendar is given. If the result would be a mixed set of values it could now deal with it gracefully, given that we apply from largest unit to smallest and have a calendar (defaulting to ISO) to disambiguate. The additional provision I would make is the difference method always returns a non-mixed result.

@littledan
Copy link
Member

(Just to respond to the ping, I haven't followed this closely enough to have an opinion, and I'm happy to defer to you all.)

@justingrant
Copy link
Collaborator Author

@pdunkel - Glad I asked for more detail! My understanding of the agreement from the last meeting was that we were going to enforce that all use of durations in Temporal adhered to RFC 5545 definition of durations. Let's discuss further in the meeting.

@ptomato
Copy link
Collaborator

ptomato commented Jul 16, 2020

I was convinced by @pipobscure's point that keeping DateTime and Absolute arithmetic as they currently are, already adheres to the RFC 5545 definition. (Except for the point about changing the order in which the fields are processed.)

(I'm less convinced by the idea of explicitly changing our explanation from "Absolute is just a number" to "Absolute is in UTC" and adding weeks/months/years to Absolute arithmetic.)

I think it's a very clean solution that we have 3 Temporal types, each with a different kind of arithmetic (absolute, wall calendar, and DST-aware) and therefore don't need to have 3 kinds of durations as well. I did quite like the explanation of "Temporal.Duration is basically a property bag with some methods for convenience" and would have been sad to lose that.

As for the open question about balancing durations, I'm fine with moving balance to a dedicated method, and making balanceConstrain the only option for Duration.minus (and with negative durations also Duration.plus) (which also gets rid of the awkwardly long name for that option). Currently, duration balancing converts 24 hours into 1 day, so balancing according to the RFC 5545 definition would require not doing that anymore. (I thought this had been mentioned above but on a second read I couldn't find it.)

@justingrant
Copy link
Collaborator Author

(I'm less convinced by the idea of explicitly changing our explanation from "Absolute is just a number" to "Absolute is in UTC" and adding weeks/months/years to Absolute arithmetic.)

I am also unconvinced. Everywhere we expose date/time unit getters, we introduce the possibility for bugs where a user thinks that they're getting one kind of hour or day but in fact they're getting another. It's a lot easier to avoid these bugs if the only way that users can have unit getters is via "Civil" types, because if the user starts with an Absolute then they're forced to explicitly decide what time zone they want. If you really want to get the UTC year from an Absolute, you can still do it, but you'd need to explicitly opt in, e.g. abs.toDateTime('utc').year. This seems like the right compromise of ergonomics vs. safety.

Another advantage of the current design is that if a developer accidentally passes an Absolute to a function that expects a DateTime or LocalDateTime, that call will probably throw an exception because Absolute doesn't have any of the same getters. This behavior seems like a feature, not a bug.

Currently, duration balancing converts 24 hours into 1 day, so balancing according to the RFC 5545 definition would require not doing that anymore

@ptomato - could you clarify what you mean by this? Do you mean the balanceConstrain balancing done in minus, or are you talking about balancing in a new balance method? Or both?

And what do you think about the solutions to this problem proposed in 5.3-5.6 above?

I was convinced by @pipobscure's point that keeping DateTime and Absolute arithmetic as they currently are, already adheres to the RFC 5545 definition. (Except for the point about changing the order in which the fields are processed.)

I think it's OK to leave DateTime and Absolute math as they are, but I don't think that this behavior matches RFC 5545, because RFC 5545 requires dates to be considered calendar dates and times to be considered Absolute time. Neither DateTime nor Absolute can possibly do that with durations that have both date and time parts, because neither type has awareness of actual time zones which would be required to do that calculation properly.

So I think what @pipobscure is recommending is closest to option 1d from #686:

1d. ZonedAbsolute + Defaults - Add a new class type that represents a particular instant at a particular place on earth, and leverage its time zone knowledge for better duration-kind-conversion ergonomics.

  • Add duration conversion methods on TimeZone (same as 1b)
  • Add options parameter (e.g. {durationKind: 'absolute' | 'dateTime' | 'hybrid'} to this class's plus/minus/difference methods to specify the kind of the input or output duration.
  • Consider making one of the kinds a default option (probably 'hybrid'), if we think it will work for a large majority of use cases. Developers could still opt into other kinds by changing the option.

@ptomato
Copy link
Collaborator

ptomato commented Jul 17, 2020

could you clarify what you mean by this? Do you mean the balanceConstrain balancing done in minus, or are you talking about balancing in a new balance method? Or both?

Both.

And what do you think about the solutions to this problem proposed in 5.3-5.6 above?

I agree that subtraction needs balancing, and therefore addition with negative durations as well. Instead of any of the solutions in 5.3 I'd rather just throw if the result of the operation was something like "3 days and –2 hours".

My opinion is that we should not have calendar-aware balancing. It seems easy enough to do on a case-by-case basis in userland, there are several examples of it in the documentation already, yet a general-case API for it would be complicated. It's also separate enough from the set of operations that we currently have, and it seems uncommon enough that the lack of it doesn't detract from Temporal, so it could easily be introduced in a follow-up proposal.

I think it's OK to leave DateTime and Absolute math as they are, but I don't think that this behavior matches RFC 5545, because RFC 5545 requires dates to be considered calendar dates and times to be considered Absolute time. Neither DateTime nor Absolute can possibly do that with durations that have both date and time parts, because neither type has awareness of actual time zones which would be required to do that calculation properly.

OK, maybe that was a bit optimistic of me. You are right that DateTime math doesn't match RFC 5545. I was thinking if you add 1 day then it's a calendar day, but I didn't consider that if you add 24 hours then that effectively adds 1 calendar day, which is not RFC 5545. On the other hand, I do think Absolute math does match if you consider it to be in UTC, but I had already said above that I didn't want to consider it to be in UTC.

@ptomato
Copy link
Collaborator

ptomato commented Sep 17, 2020

What's left to discuss on this issue? I have the feeling that we've addressed some of these concerns, and accepted others as necessary complexity for supporting enough common use cases.

@pipobscure
Copy link
Collaborator

I think we can indeed close this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants