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

Proposal: Negative Durations #782

Closed
justingrant opened this issue Jul 17, 2020 · 22 comments · Fixed by #811
Closed

Proposal: Negative Durations #782

justingrant opened this issue Jul 17, 2020 · 22 comments · Fixed by #811
Assignees

Comments

@justingrant
Copy link
Collaborator

justingrant commented Jul 17, 2020

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 supported

  • 2.1 Intra-duration sign variation (e.g. "2 days and negative 12 hours") seems to have only one major use case: the ability to combine a math operation with construction of a Duration. We explicitly stopped doing this in Remove {disambiguation: 'balance'} in with() and from() 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

  • 3.1 The string format can optionally include a leading minus or (no-op) plus sign, e.g. -P2D means a negative 2-day duration, while P2D and +P2D both mean a positive 2-day duration.
  • 3.2 The leading plus/minus format is used by RFC 5545 and many other libraries and platforms. AFAIK there is no other alternative format used in mainstream platforms.
  • 3.3 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.
  • 3.3 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.

  • 4.1 Methods that accept or return Duration fields (property getters, getFields, from, and with) will emit and accept only negative integer values for every nonzero unit of a negative duration.
    • 4.1.1 The alternative would be for all fields to be positive and rely on a sign field. The main problem with this approach is that it makes with 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.
    • 4.1.2 Another disadvantage of all-positive units: it's easy to accidentally "lose" the sign info. For example, the code below would break for negative durations:
const getDateDuration = d => { years: d.years, months: d.months, weeks: d.weeks, days: d.days };
  • 4.1.3 Another disadvantage of having all-positive fields is ergonomics. If all fields were positive, then a very large % of calls to property getters would also need to fetch the sign. Example:
const totalDays = dur1.days + dur2.days;
const harderTotalDays = dur1.days*dur1.sign + dur2.days*dur2.sign;
  • 4.2 Any method that accepts a "Duration-like" property bag (Duration.from, Duration.prototype.with, or any type's plus or minus method) should throw if any of the non-zero input units have different signs.
  • 4.3 The Duration constructor must throw if it's passed non-zero units with different signs.
  • 4.4 Duration.with can reverse the sign of a duration, but only if all of the existing duration's non-zero units are replaced.
Duration.from('-P2DT12H').with({weeks: 3, days: 0, hours: 12}); // OK
Duration.from('-P2DT12H').with({weeks: 3, days: 0})`; // throws

5. Duration should gain a few convenience properties/methods

  • 5.1 Duration.prototype.negated() - reverse the sign
  • 5.2 Duration.prototype.sign - 0, -1, or 1. Not included in getFields because it's redundant. Not accepted by with or from because of potential conflicts.
  • 5.3 Duration.prototype.abs() - if negative, reverse the sign

6. Non-Duration plus and minus methods should accept negative durations

  • 6.1 If plus is passed a negative duration, then the implementation should treat it as if the user had called minus on the equivalent positive duration.
  • 6.2 If minus is passed a negative duration, then the implementation should treat it as if the user had called plus on the equivalent positive duration.
  • 6.3 This simple reversal means that the work to implement negative durations in the spec and polyfill should be mostly confined to the Duration type and its corresponding abstract operations, and won't require many changes to other types.
  • 6.4 Because Duration's plus and minus methods can now perform addition or subtraction according to the sign of the duration, both methods must now accept identical options. Currently plus uses 'constrain' (default) and 'reject' while minus 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'.
  • 6.5 A side effect of 6.4 is that a balance option will now be supported for addition operations.

7. Order of operations should not be affected by negative durations

  • 7.1 Addition with a negative duration should use the order of operations for subtraction with a positive duration. And vice versa: subtraction of a negative duration should use order-of-operations for addition.
  • 7.2 Therefore, adding negative durations shouldn't change how order of operations is implemented in Temporal.
  • 7.3 That said, there is an open issue about whether our current subtraction OOO conflicts with RFC 5545. This is a question for the calconnect group at IETF that owns the RFC 5545 spec and its descendants. Its resolution should not block negative durations; they're orthogonal issues.

8. No change to Duration.prototype.toLocaleString; continue passthrough to Intl.DurationFormat

const rtf1 = new Intl.RelativeTimeFormat('en', { style: 'narrow' });

console.log(rtf1.format(3, 'quarter'));
//expected output: "in 3 qtrs."

console.log(rtf1.format(-1, 'day'));
//expected output: "1 day ago"
  • 8.2 Note that there are (at least) three possible i18n-ized formats possible for durations:
    • 8.2.1 Non-relative / absolute value - e.g. "1 day and 12 hours". It's not obvious whether a negative duration should throw or should be shown using the absolute value.
    • 8.2.2 Relative to now - e.g. "1 day and 12 hours ago" or "in 1 day and 12 hours". This is the format currently used by Intl.RelativeTimeFormat.
    • 8.2.3 Relative-to-something - e.g. "1 day and 12 hours before". This format is not provided by Intl.RelativeTimeFormat today.
  • 8.3. All that said, IMHO it should be up to 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, the Duration.prototype.toLocaleString should not change from the current implementation which simply passes the duration and options through unchanged to Intl.DurationFormat, which should decide how to display the duration (or to throw if that's decided).
@sffc
Copy link
Collaborator

sffc commented Jul 17, 2020

  1. Non-Duration plus and minus methods should accept negative durations

Are you proposing to get rid of minus? (I would be in favor of getting rid of minus)

  1. Duration.prototype.toLocaleString behavior is TBD (open issue)

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.

@ptomato
Copy link
Collaborator

ptomato commented Jul 17, 2020

➕ 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.

@justingrant
Copy link
Collaborator Author

justingrant commented Jul 17, 2020

Are you proposing to get rid of minus? (I would be in favor of getting rid of minus)

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.

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".

Apparently, Intl.RelativeTimeFormat already supports negative units:

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. 1 hour and 30 minutes ago or in 3 weeks, 2 days, and 12 hours. But regardless of the actual formatted text emitted, I agree with @ptomato: "As for toLocaleString(), I think we should leave it entirely to the discretion of Intl.DurationFormat."

It is interesting that Intl.DurationFormat has chosen to format using "relative to now" language (e.g. "in 2 days", "2 days ago") and not "relative to another event" language (e.g. "2 days before", "2 days after"). I could see a future Intl.DurationFormat providing an option for that latter format. But that's outside the scope of Temporal, IMHO.

Is one of the assumptions in Temporal that Intl.DurationFormat will be extended to support being passed a Duration or a Duration-like property bag that can format multiple units?

EDIT: Sorry, above I mixed up RelativeTimeFormat with DurationFormat. The latter doesn't exist yet on MDN.

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. This means that, for the purposes of this proposal, toLocaleString implementation won't change-- it'll continue passing through all options unchanged to Intl.DurationFormat.

@sffc
Copy link
Collaborator

sffc commented Jul 18, 2020

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.

@justingrant
Copy link
Collaborator Author

OK, I edited the proposal to resolve any open issues that I knew about. AFAIK , the only remaining open issue is whether toLocaleString will accept or throw for negative durations, and that decision is up to DurationFormat so is out of scope to this proposal.

At this point unless there are objections, I think we're ready to move to a PR. Any objections or other concerns?

@ryzokuken
Copy link
Member

+1

@justingrant
Copy link
Collaborator Author

Decision at 2020-07-31 Champions' meeting: proposal is approved. @ptomato will build the PR because he is awesome. ;-)

@justingrant
Copy link
Collaborator Author

justingrant commented Jul 31, 2020

After today's meeting, I realized we forgot to discuss aligning the names of disambiguation options between plus and minus. Because both methods can now perform either subtraction or addition, the disambiguation options must be the same for both methods. My proposal is simply:

  • rename 'balanceConstrain' to 'constrain', so both plus and minus methods will accept 'constrain' (default), 'reject', and 'balance'.
  • support balance for both addition and subtraction operations

I added this into new sections 6.4 and 6.5.

@sffc
Copy link
Collaborator

sffc commented Jul 31, 2020

IIRC, we used different names for constrain and balanceConstrain because the latter can move data between fields, and we wanted to emphasize that.

@justingrant
Copy link
Collaborator Author

justingrant commented Jul 31, 2020

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 plus and minus and for the three options to have identical behavior for addition operations (either plus with a positive duration or minus with a negative one). And also for subtraction operations via plus with a negative duration or minus with a positive duration.

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.

@ptomato
Copy link
Collaborator

ptomato commented Jul 31, 2020

When we added Duration.plus and Duration.minus I didn't think it was useful to have "reject" — see #408 (comment)

@justingrant
Copy link
Collaborator Author

@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 plus and minus and the balance option works for both addition and subtraction. I don't think reject is particularly useful and would not cry if it vanished.

@ptomato
Copy link
Collaborator

ptomato commented Aug 3, 2020

If we have negative durations then we need to have balance and balanceConstrain for plus as well as minus.

The only time that constrain and reject are used for plus, is when the result is out of range of Number.MAX_VALUE. Since we changed the other types to always throw a RangeError when out of range in #664, I think maybe it would be best to do the same for Temporal.Duration, and just not have constrain (because it doesn't do anything). I also don't think reject is particularly useful (it would throw on operations like PT2H30MPT45M) so I would prefer not to have it.

So, I would advocate for just two options: balance and balanceConstrain, for both plus and minus.

@justingrant
Copy link
Collaborator Author

Sounds good to me. Would balanceConstrain do anything for plus with a positive duration or for minus with a negative duration? I assume not, but wanted to make sure I wasn't missing something.

@ptomato
Copy link
Collaborator

ptomato commented Aug 3, 2020

That's correct.

@ptomato
Copy link
Collaborator

ptomato commented Aug 5, 2020

A couple of implementation notes:

  • Math.sign() can return five values: -1, 1, -0, 0, NaN. We already disallow NaNs in durations because they are not finite. I'm inclined to silently disallow -0 as well: according to the current spec, any fields of -0 already get converted to 0 anyway by ToInteger, and it seems like a nightmare if you would have to make sure the sign of your zeroes agrees with the sign of the rest of the fields when constructing a duration!
  • duration.abs() returns a copy of the duration if its sign is already positive or zero. (That is, a new object.)
  • If we get rid of the constrain/reject distinction by always throwing a RangeError past the end of the range, then what should the non-balance mode be called? balanceConstrain makes sense for .plus and .minus, but not for .from and .with. disambiguation: 'none' vs. disambiguation: 'balance'? Or just balance: true and balance: false? This seems very much related to the balancing method / rounding method in Round a Temporal.Duration to the nearest small unit #337 so I'll just implement it as originally described (rename balanceConstrain to constrain, add reject) for the purposes of this PR, otherwise it could get out of scope.

@justingrant
Copy link
Collaborator Author

justingrant commented Aug 5, 2020

FWIW, I really like balance: boolean. Seems much clearer than overloading disambiguation for purposes of balancing. Given that balancing is already complicated (we wouldn't have an entire dedicated page in the docs if it were obvious!) then making the option more self-describing seems like a good idea to limit that complexity. Also, we're already planning to split disambiguation into a disambiguation and overflow property per #607, so retaining disambiguation as a universal option seems like it's already on its way out.

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.

@ptomato
Copy link
Collaborator

ptomato commented Aug 8, 2020

One thing I'm not sure we discussed, I assume reverting #667 is part of this?

@justingrant
Copy link
Collaborator Author

Yep agreed

ptomato added a commit that referenced this issue Aug 11, 2020
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
ptomato added a commit that referenced this issue Aug 11, 2020
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
ptomato added a commit that referenced this issue Aug 12, 2020
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
ptomato added a commit that referenced this issue Aug 12, 2020
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
ptomato added a commit that referenced this issue Aug 12, 2020
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
ptomato added a commit that referenced this issue Aug 12, 2020
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
ptomato added a commit that referenced this issue Aug 14, 2020
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
ptomato added a commit that referenced this issue Aug 14, 2020
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
ptomato added a commit that referenced this issue Aug 24, 2020
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
ptomato added a commit that referenced this issue Aug 24, 2020
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
ptomato added a commit that referenced this issue Aug 24, 2020
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
ptomato added a commit that referenced this issue Aug 24, 2020
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
ptomato added a commit that referenced this issue Aug 25, 2020
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
ptomato added a commit that referenced this issue Aug 25, 2020
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
@ptomato ptomato added this to the Stable API milestone Aug 27, 2020
ptomato added a commit that referenced this issue Aug 27, 2020
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
ptomato added a commit that referenced this issue Aug 27, 2020
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
ptomato added a commit that referenced this issue Aug 28, 2020
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
ptomato added a commit that referenced this issue Aug 28, 2020
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
ptomato added a commit that referenced this issue Sep 1, 2020
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
ptomato added a commit that referenced this issue Sep 1, 2020
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
ptomato added a commit that referenced this issue Sep 1, 2020
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
ptomato added a commit that referenced this issue Sep 1, 2020
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
@artalar
Copy link
Contributor

artalar commented Mar 15, 2021

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:
(I need to schedule some work for next 9am)

    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.

@ryzokuken
Copy link
Member

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?

@gilmoreorless
Copy link
Contributor

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()

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

Successfully merging a pull request may close this issue.

6 participants