-
Notifications
You must be signed in to change notification settings - Fork 162
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
More relativeTo rounding #1185
More relativeTo rounding #1185
Conversation
Codecov Report
@@ Coverage Diff @@
## main #1185 +/- ##
==========================================
+ Coverage 93.55% 94.73% +1.18%
==========================================
Files 19 19
Lines 7961 9429 +1468
Branches 1264 1388 +124
==========================================
+ Hits 7448 8933 +1485
+ Misses 506 490 -16
+ Partials 7 6 -1
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
In case you need to round the remainder of nanoseconds to the nearest day, you need to know how long the day is. This will be used to fix rounding relative to a ZonedDateTime. See: #1171
When rounding or totaling time units, the length of the last day needs to be taken into account if relativeTo is a ZonedDateTime. This fixes some of the broken test cases. See: #1171
348b214
to
fece748
Compare
fece748
to
9d7fdbd
Compare
Hi @ptomato - In case it's useful, here's a test case from a "DST best practices" page for the docs that I'm working on. Both days = 35;
// WRONG!!! One calendar day during this period might not be 24 hours long!
hours = days * 24; // => 840
// CORRECT: interpret the duration in the time zone where it was measured
// One hour may have been skipped by a DST transition.
duration = Temporal.Duration.from({ days });
referenceDate = Temporal.PlainDate.from('2020-03-01');
tzTokyo = Temporal.TimeZone.from('Asia/Tokyo');
tzBerlin = Temporal.TimeZone.from('Europe/Berlin');
[hoursInTokyo, hoursInBerlin] = [tzTokyo, tzBerlin].map(
tz => duration.total({
unit: 'hours',
relativeTo: referenceDate.toZonedDateTime(tz)
})
); // => [840, 839]
// ALSO CORRECT: Instead of using `total()` or `round()` on the
// duration, you could add the duration to the reference date
// and then use `since` or `until`.
[hoursInTokyo, hoursInBerlin] = [tzTokyo, tzBerlin].map(
tz => referenceDate.toZonedDateTime(tz)
.until(
referenceDate.toZonedDateTime(tz).add(duration),
{ largestUnit: 'hours' }
)
.hours
); // => [840, 839] |
9d7fdbd
to
6f811af
Compare
When converting between days and lower units in BalanceDuration, we need to take the exact time into account, and likewise when converting between lower units and days, we need to take the length of the last day into account. As well, we need to take into account the possibility of moving the time forward when adding, due to disambiguation. These were handled correctly in DifferenceZonedDateTime but not in NanosecondsToDays. In order to ensure that these algorithms don't diverge, move all the important stuff from DifferenceZonedDateTime into NanosecondsToDays, and use NanosecondsToDays in DifferenceZonedDateTime. See: #1171
6f811af
to
90ca146
Compare
@justingrant Thanks for the extra example! I finally figured out what the discrepancy was and fixed the tests, and now the example checks out as well. |
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 think this looks good and I think I understand what it's doing, although the logic is complicated enough that it may be worth a 1:1 walkthrough if you have time. Your call.
I did have one concern about O(n) looping on years, but that seems like something that could be optimized later if needed. As an alternative, it's probably possible to use PlainDateTime math (aka Calendar.prototype.dateUntil
) and then back up if if the ZDT result goes too far. This could delegate the O(n) problem to the calendar which might enable an ISO optimization.
@@ -3378,7 +3413,7 @@ export const ES = ObjectAssign({}, ES2020, { | |||
remainder = years; |
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.
Is an O(n) loop necessary for years? If there's a 400K-year duration, that's a lot of looping! Although some calendars may have a variable number of years, ISO years (the vast majority of usage) are deterministic and this calculation could probably be O(1) for ISO. Looping for days/months seems OK because there's not going to be 1000s of them.
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 was also thinking over the weekend that a lot of this code could be skipped in many cases if we used getNextTransition/getPreviousTransition.
Would you be interested in doing the walkthrough and possibly identifying ways to optimize these algorithms next week? I'm not sure I can bring myself to look at them again this week. 😬
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.
Sure, would be happy to review next week! I spent today doing a deep dive on the new code and have two questions that may be bugs in the algorithm. Will make a separate comment about that to get your feedback.
I was also thinking over the weekend that a lot of this code could be skipped in many cases if we used getNextTransition/getPreviousTransition.
Some time zones may not implement these functions-- they're optional AFAIK. Seems like a better approach might be to use PlainDateTime math and then adjust the endpoint as needed. But let's discuss when we chat 1:1 next week.
I did a deep-dive review of this logic today it seems solid. I thought I was seeing unexpected behavior in a few cases, but after digging into the polyfill a little more now I think the current behavior is fine. One thing we should probably document at some point is the expected behavior when Temporal.ZonedDateTime.from('2020-11-01[America/Los_Angeles]').until(
'2020-11-02T11:59[America/Los_Angeles]',
{ roundingIncrement: 3, smallestUnit: 'days', roundingMode: 'nearest' }
);
// => PT0S The only remaining issue I see with rounding is #1197, because all the rounding code is using multiple rounds of math operations on non-integer Am I correct that fixing #1197 doesn't require a spec change because the rounding abstract operations in the spec deal with "mathematical values" only and not |
This fixes rounding and totalling Durations with a ZonedDateTime
relativeTo
parameter.There is still one bug in the algorithm, which is causing one rounding test to fail, and the corresponding totalling test, so those two remain skipped until I can track down the bug.Closes: #1171