-
Notifications
You must be signed in to change notification settings - Fork 155
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
Proposal: Negative Durations #782
Comments
Are you proposing to get rid of minus? (I would be in favor of getting rid of minus)
I'm trying to think of use cases. People might try formatting strings like "the game starts 1 hour after the train arrives" for a positive duration, or "the train arrives 1 hour before the game starts" for a negative duration. In English, the sign doesn't seem to matter, but you need to handle it properly to choose whether to say "before" or "after". My gut feeling is if we don't know better, we should make toLocaleString throw an exception for a negative duration. But, I'll double-check with the other i18n experts on my team. |
➕ to all of this. I agree that all nonzero fields being either negative or positive is the least surprising. As for toLocaleString(), I think we should leave it entirely to the discretion of Intl.DurationFormat. |
I wasn't proposing to make that change. Minus is trivial to implement and is ergonomically helpful for users so I'm not sure there's enough value in omitting it that outweighs those benefits.
Apparently, console.log(rtf1.format(3, 'quarter'));
//expected output: "in 3 qtrs."
console.log(rtf1.format(-1, 'day'));
//expected output: "1 day ago" So I think we should not throw for negative durations and instead to follow this same convention, e.g. It is interesting that
EDIT: Sorry, above I mixed up It's an interesting question, therefore, what the format of toLocaleString should be. Should it be a relative-to-now format (e.g. "1 day and 12 hours ago"), a "relative-to-something" format (e.g. "1 day and 12 hours before"), or a non-relative format (e.g. "1 day and 12 hours"). I updated the OP with @ptomato's suggestion to defer to |
Intl.DurationFormat is non-relative. It's for things like "the video is 10 minutes long" or "it takes 6 hours and 30 minutes to drive from San Jose to Los Angeles". However, I could see a future proposal extending Intl.RelativeTimeFormat to accept a duration as an argument. Anyway, follow up on toLocaleString in tc39/proposal-intl-duration-format#29. |
OK, I edited the proposal to resolve any open issues that I knew about. AFAIK , the only remaining open issue is whether At this point unless there are objections, I think we're ready to move to a PR. Any objections or other concerns? |
+1 |
Decision at 2020-07-31 Champions' meeting: proposal is approved. @ptomato will build the PR because he is awesome. ;-) |
After today's meeting, I realized we forgot to discuss aligning the names of disambiguation options between
I added this into new sections 6.4 and 6.5. |
IIRC, we used different names for constrain and balanceConstrain because the latter can move data between fields, and we wanted to emphasize that. |
I'm OK to bikeshed on the "constrain" vs. "balanceConstrain" name-- that's easy to change later. Mostly I wanted to capture the core requirements to use the same three options for both The challenge with naming is that the same name needs to work for both addition "constraining" and subtraction "balance constraining". Or we'd need to have 4 options which seems unnecessarily complicated. I assumed that "constrain" is the common attribute. Regardless, I don't think naming should block implementation. |
When we added Duration.plus and Duration.minus I didn't think it was useful to have "reject" — see #408 (comment) |
@ptomato - what do you think the options should be here? I don't have a strong opinion as long as they're the same names for |
If we have negative durations then we need to have The only time that So, I would advocate for just two options: |
Sounds good to me. Would |
That's correct. |
A couple of implementation notes:
|
FWIW, I really like Agreed on preventing -0 and NaN. Agreed on creating new object. My assumption is that this should be a general rule for all Temporal methods: if an object is returned, it's a new object. |
One thing I'm not sure we discussed, I assume reverting #667 is part of this? |
Yep agreed |
Changes Temporal.Duration to allow negative values in the fields, as long as all the fields have the same sign. Adds negated() and abs() methods to Temporal.Duration, and a sign property. On all types with arithmetic, subtracting a negative duration is equivalent to adding the absolute value of that duration, and adding a negative duration is equivalent to subtracting the absolute value of that duration. Closes: #782
This undoes the work in commit d84653e although it's not exactly a revert, because the situation before that was that durations were always positive. We also don't reinstate the behaviour of never returning a duration larger than 12 hours. Now, for all types, calling smaller.difference(larger) will result in a negative duration. See: #782
Changes Temporal.Duration to allow negative values in the fields, as long as all the fields have the same sign. Adds negated() and abs() methods to Temporal.Duration, and a sign property. On all types with arithmetic, subtracting a negative duration is equivalent to adding the absolute value of that duration, and adding a negative duration is equivalent to subtracting the absolute value of that duration. Closes: #782
This undoes the work in commit d84653e although it's not exactly a revert, because the situation before that was that durations were always positive. We also don't reinstate the behaviour of never returning a duration larger than 12 hours. Now, for all types, calling smaller.difference(larger) will result in a negative duration. See: #782
Changes Temporal.Duration to allow negative values in the fields, as long as all the fields have the same sign. Adds negated() and abs() methods to Temporal.Duration, and a sign property. On all types with arithmetic, subtracting a negative duration is equivalent to adding the absolute value of that duration, and adding a negative duration is equivalent to subtracting the absolute value of that duration. Closes: #782
This undoes the work in commit d84653e although it's not exactly a revert, because the situation before that was that durations were always positive. We also don't reinstate the behaviour of never returning a duration larger than 12 hours. Now, for all types, calling smaller.difference(larger) will result in a negative duration. See: #782
Changes Temporal.Duration to allow negative values in the fields, as long as all the fields have the same sign. Adds negated() and abs() methods to Temporal.Duration, and a sign property. On all types with arithmetic, subtracting a negative duration is equivalent to adding the absolute value of that duration, and adding a negative duration is equivalent to subtracting the absolute value of that duration. Closes: #782
This undoes the work in commit d84653e although it's not exactly a revert, because the situation before that was that durations were always positive. We also don't reinstate the behaviour of never returning a duration larger than 12 hours. Now, for all types, calling smaller.difference(larger) will result in a negative duration. See: #782
Changes Temporal.Duration to allow negative values in the fields, as long as all the fields have the same sign. Adds negated() and abs() methods to Temporal.Duration, and a sign property. On all types with arithmetic, subtracting a negative duration is equivalent to adding the absolute value of that duration, and adding a negative duration is equivalent to subtracting the absolute value of that duration. Closes: #782
This undoes the work in commit d84653e although it's not exactly a revert, because the situation before that was that durations were always positive. We also don't reinstate the behaviour of never returning a duration larger than 12 hours. Now, for all types, calling smaller.difference(larger) will result in a negative duration. See: #782
Changes Temporal.Duration to allow negative values in the fields, as long as all the fields have the same sign. Adds negated() and abs() methods to Temporal.Duration, and a sign property. On all types with arithmetic, subtracting a negative duration is equivalent to adding the absolute value of that duration, and adding a negative duration is equivalent to subtracting the absolute value of that duration. Closes: #782
This undoes the work in commit d84653e although it's not exactly a revert, because the situation before that was that durations were always positive. We also don't reinstate the behaviour of never returning a duration larger than 12 hours. Now, for all types, calling smaller.difference(larger) will result in a negative duration. See: #782
Changes Temporal.Duration to allow negative values in the fields, as long as all the fields have the same sign. Adds negated() and abs() methods to Temporal.Duration, and a sign property. On all types with arithmetic, subtracting a negative duration is equivalent to adding the absolute value of that duration, and adding a negative duration is equivalent to subtracting the absolute value of that duration. Closes: #782
This undoes the work in commit d84653e although it's not exactly a revert, because the situation before that was that durations were always positive. We also don't reinstate the behaviour of never returning a duration larger than 12 hours. Now, for all types, calling smaller.difference(larger) will result in a negative duration. See: #782
Changes Temporal.Duration to allow negative values in the fields, as long as all the fields have the same sign. Adds negated() and abs() methods to Temporal.Duration, and a sign property. On all types with arithmetic, subtracting a negative duration is equivalent to adding the absolute value of that duration, and adding a negative duration is equivalent to subtracting the absolute value of that duration. Closes: #782
This undoes the work in commit d84653e although it's not exactly a revert, because the situation before that was that durations were always positive. We also don't reinstate the behaviour of never returning a duration larger than 12 hours. Now, for all types, calling smaller.difference(larger) will result in a negative duration. See: #782
Changes Temporal.Duration to allow negative values in the fields, as long as all the fields have the same sign. Adds negated() and abs() methods to Temporal.Duration, and a sign property. On all types with arithmetic, subtracting a negative duration is equivalent to adding the absolute value of that duration, and adding a negative duration is equivalent to subtracting the absolute value of that duration. Closes: #782
This undoes the work in commit d84653e although it's not exactly a revert, because the situation before that was that durations were always positive. We also don't reinstate the behaviour of never returning a duration larger than 12 hours. Now, for all types, calling smaller.difference(larger) will result in a negative duration. See: #782
Changes Temporal.Duration to allow negative values in the fields, as long as all the fields have the same sign. Adds negated() and abs() methods to Temporal.Duration, and a sign property. On all types with arithmetic, subtracting a negative duration is equivalent to adding the absolute value of that duration, and adding a negative duration is equivalent to subtracting the absolute value of that duration. Closes: #782
This undoes the work in commit d84653e although it's not exactly a revert, because the situation before that was that durations were always positive. We also don't reinstate the behaviour of never returning a duration larger than 12 hours. Now, for all types, calling smaller.difference(larger) will result in a negative duration. See: #782
Changes Temporal.Duration to allow negative values in the fields, as long as all the fields have the same sign. Adds negated() and abs() methods to Temporal.Duration, and a sign property. On all types with arithmetic, subtracting a negative duration is equivalent to adding the absolute value of that duration, and adding a negative duration is equivalent to subtracting the absolute value of that duration. Closes: #782
This undoes the work in commit d84653e although it's not exactly a revert, because the situation before that was that durations were always positive. We also don't reinstate the behaviour of never returning a duration larger than 12 hours. Now, for all types, calling smaller.difference(larger) will result in a negative duration. See: #782
Hi there! I spent a couple of hours understanding the bug behavior in my application which brought me here. The limitation of the same sign values mixed is not expressible by static types and it's definitely not obvious, why it needed? Here is my case: const moscowTime = Temporal.now.zonedDateTimeISO('Europe/Moscow')
const startDate = moscowTime
.add({ days: moscowTime.hour < 9 ? 0 : 1, hours: 9 - moscowTime.hour })
.round({ smallestUnit: 'hours', roundingMode: 'floor' })
.toInstant() It works fine from 0 to 9 and throws an error from 9 to 24. |
Hi @artalar! We limited durations to the same sign values for avoiding a number of complicated situations, including the question of representation of mixed sign values. In case of Temporal, you don't need mixed sign values to begin with since the API allows you to conveniently do arithmetic as you show above. All you need to do is avoid mixed signs. For example, check this out: const startDate = moscowTime
.add({ days: moscowTime.hour < 9 ? 0 : 1 })
.add({ hours: 9 - moscowTime.hour })
.round({ smallestUnit: 'hours', roundingMode: 'floor' })
.toInstant() I did not check yet, but I think this example should work fine? |
Alternatively, to avoid calculating hours and rounding, you could set the time explicitly: const startDate = moscowTime
.add({ days: moscowTime.hour < 9 ? 0 : 1 })
.withPlainTime('09:00')
.toInstant() |
Today we reached tentative consensus in favor of offering negative durations in Temporal. There was a long discussion in #558 about whether we should do this, but now that we're moving forward I wanted to open a separate issue for how exactly negative durations should work. That's this issue.
1. A
Duration
can be negative.2. All
Duration
units must share the same sign; intra-duration sign variation is not supportedDuration
. We explicitly stopped doing this in Remove{disambiguation: 'balance'}
inwith()
andfrom()
of non-Duration
types #642, so I don't see any need to support it now. The (easy) workaround is to construct a single-sign duration, and then apply the math operation.3. The string persistence format for
Duration
will be extended with an optional leading sign-P2D
means a negative 2-day duration, whileP2D
and+P2D
both mean a positive 2-day duration.Duration.prototype.toString
will emit the leading negative sign for negative durations, but will NOT emit a leading plus for positive durations, so that users who are using ISO8601-compliant positive duration will get an ISO8601-compliant string persistence format.Duration.from
will accept a leading minus, a leading plus, or no leading sign. Intra-duration (non-leading) plus or minus characters are not supported and must throw when parsed.4. If a
Duration
is negative, its nonzero fields will all be negative too.Duration
fields (property getters,getFields
,from
, andwith
) will emit and accept only negative integer values for every nonzero unit of a negative duration.sign
field. The main problem with this approach is that it makeswith
seem ambiguous. If you have a duration-P2D
and you say.with({days: 1})
do you mean that the resulting duration should be positive or negative? We'd define it to mean the latter, but this seems like it'd be a source of confusion. If the sign is represented in every unit, then there's no ambiguity about the meaning of a negative or positive field value.Duration.from
,Duration.prototype.with
, or any type'splus
orminus
method) should throw if any of the non-zero input units have different signs.Duration
constructor must throw if it's passed non-zero units with different signs.Duration.with
can reverse the sign of a duration, but only if all of the existing duration's non-zero units are replaced.5.
Duration
should gain a few convenience properties/methodsDuration.prototype.negated()
- reverse the signDuration.prototype.sign
- 0, -1, or 1. Not included ingetFields
because it's redundant. Not accepted bywith
orfrom
because of potential conflicts.Duration.prototype.abs()
- if negative, reverse the sign6. Non-
Duration
plus
andminus
methods should accept negative durationsplus
is passed a negative duration, then the implementation should treat it as if the user had calledminus
on the equivalent positive duration.minus
is passed a negative duration, then the implementation should treat it as if the user had calledplus
on the equivalent positive duration.plus
andminus
methods can now perform addition or subtraction according to the sign of the duration, both methods must now accept identical options. Currentlyplus
uses'constrain'
(default) and'reject'
whileminus
uses'balanceConstrain'
(default) and'balance'
. To align these and to retain consistency with other Temporal uses, we'll rename'balanceConstrain'
to'constrain'
. Both methods will now accept'constrain'
(default),'reject'
, or'balance'
.balance
option will now be supported for addition operations.7. Order of operations should not be affected by negative durations
8. No change to
Duration.prototype.toLocaleString
; continue passthrough toIntl.DurationFormat
toLocaleString
should follow the conventions already used byIntl.RelativeTimeFormat
, where both negative and positive values are accepted. Examples from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormatIntl.RelativeTimeFormat
.Intl.RelativeTimeFormat
today.Intl.DurationFormat
to decide both whether to accept (or throw for) negative durations and what format options should be accepted. This means that for the purposes of this proposal, theDuration.prototype.toLocaleString
should not change from the current implementation which simply passes the duration and options through unchanged toIntl.DurationFormat
, which should decide how to display the duration (or to throw if that's decided).The text was updated successfully, but these errors were encountered: