-
Notifications
You must be signed in to change notification settings - Fork 153
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
Fewer CalendarDateAdd/DateUntil calls during relative rounding #2792
Comments
@arshaw I did not originally realize this, but when I went to make a test implementation of this, it seems that it would not be possible without #2758. Am I correct about this? If so, I think we should absorb it into that PR, as it's essentially an optimization that you discovered while reviewing it. |
Hi @ptomato, upon looking at #2758 more closely, yes I feel that optimizing relative rounding (this ticket) should be done in on top of #2758. You'll want to refactor these code blocks:
|
Status update: While attempting to make spec-like pseudocode to describe my algorithm, I encountered two bugs cataloged in these TODO list items for improved test coverage:
I'm reworking the algorithm a bit to fix these bugs and making things more DRY in the process. It's taking a while because many things are interconnected when super-DRY: Duration::total(), Duration::round(), Duration::add(), ZDT/PDT/PD::until/since. It'd be great to get some feedback on bug #2811 because, believe it or not, it will affect how I DRY-up the various utilities. This might take few more days, so if you want to deliver something for the April 2nd meeting, I'd recommend submitting #2758. This ticket depends on that, but no vice-versa. |
I'm having further delays because of this (problem in my polyfill, incidentally also wrong in spec): Still working on coming up w/ some bug-free pseudocode... |
I finally have the pseudo-code written for this. Please take a look and lemme know what you think @ptomato and others. I plan to implement this when I DRY up all duration arithmetics. It's already implemented in fullcalendar's temporal-polyfill. https://gist.github.com/arshaw/36d3152c21482bcb78ea2c69591b20e0 |
I'd like to incorporate this into #2758 before starting on #2825 or #2826, because #2758 is a normative change that was already approved in February and for the stuff we will present in June I'd like to start from a clean base where we are not juggling old PRs. Also it should make #2825 simpler since you've gotten rid of the AddDuration with zonedRelativeTo call in what was previously known as AdjustRoundedDurationDays. I can start on it this week if that works for you. |
@arshaw I had a start on this today. If you have a moment I'm wondering about a few things. (If you don't, no worries, I can figure them out myself by looking at fullcalendar)
|
Hi @ptomato,
|
Status of thisI have a tentative version of this available in d988b1a (note, most of the diff is hidden by default. You have to expand It's written as closely as possible to how I'd expect to write the spec text, so it's obviously a lot less elegant than the gist 😅 Test262 tests are here: tc39/test262@385a621 There are also several FIXMEs in the code that I have to address. Addressing these may also fix the test262 tests. Next stepsHere's what's on my to do list:
@arshaw If any of these items ring a bell for you, or seem obviously wrong, I'd appreciate your insights. GoalIdeally, the end state is one of these two:
|
Nice work @ptomato. Here are my responses to the specific test failures
This is a problem when smallestUnit is weeks. You current Duration::round implementation offloads the balancing to DifferenceZonedDateTimeWithRounding and DifferencePlainDateTimeWithRounding, which does a point-to-point diff. This is great for balancing days->months->years, but if smallestUnit is weeks AND largestUnit is greater than weeks, we need to separately balance day->weeks and then months->years. This requires a three-point diffing algorithm which fullcalendar has implemented here.
These are failing because, IMO, the original algorithm was incorrect, and now the bug-free results are unexpected. I created a bug report previously (for Duration::total only):
FullCalendar's polyfill was failing here as well. I don't have an opinion about what the correct precision is.
IMO, an error should be thrown. Modified test for fullcalendar's polyfill:
Will go away when observable CalendarProtocol removed. Here are my responses to your misc other comments:
I think you're right.
I believe |
@arshaw Thanks for the responses. This has been really helpful. endDateTime in nudgeToZonedTime: implemented, but didn't actually save a calendar call because AddDateTime would already avoid the call if years/months/days were 0. Re. #2817, I'll have a closer look at these and respond there. At first glance, it seems you might be right about the number of hours being calculated from the wrong day.
Makes sense, thanks.
🤷 I guess, since these intrinsically have a relativeTo? But I don't care all that much. "rounding increment 1 billion" is an entirely unimportant use case. I guess if you still want it you can take the duration and round it yourself while omitting relativeTo.
I think there must be more going on with these. I'm using const d = new Temporal.Duration(5, 6, 7, 8, 40, 30, 20, 123, 987, 500);
const relativeTo = new Temporal.PlainDate(2020, 4, 1);
const result = d.round({ smallestUnit: 'weeks', relativeTo }); as a test case, adapted from the test262 test.
You could argue that the P5Y6M8W result is correct (and I did for a long time). But we opened #2728 about this because it still resulted in cases that people found 'unnatural'. I resolved #2728 when it seemed like DifferencePlainDateTimeWithRounding/DifferenceZonedDateTimeWithRounding produced results that were more 'natural'. The original duration represents a span of 2067 days from that relativeTo. Rounded to P5Y7M4W it is 2068 days, i.e. rounded 1 day up. Rounded to P5Y6M8W it is 2065 days, i.e. rounded 2 days down. All that said, I think that in no case is P5Y7M1W correct. P5Y7M1W is 2047 days, or rounded by 20 days! So there must be a bug in my interpretation of the algorithm. |
Current state of this is in 2845e80 (with corresponding modifications to tests in ptomato/test262@12a9cd8; the tests are a work in progress and still need a few tweaks/additions). I fixed a few things that were unambiguously bugs. The weeks rounding problem I fixed by adding a special case for weeks to NudgeToCalendarUnit, to compensate for the fact that Fullcalendar's diffing algorithm is different. I also am convinced that #2817 is correct, although this change doesn't entirely sit well with me: const duration = new Temporal.Duration(0, 1, 0, 15);
let relativeTo = new Temporal.ZonedDateTime(
951991200_000_000_000n /* = 2000-03-02T10Z */,
'America/Vancouver'
); /* = 2000-03-02T02-08 in local time */
const result = duration.total({ unit: 'months', relativeTo });
// Previously: 1.5
// New: 1.4993045897079276 I suppose this is technically correct because the month lasts 1 hour less, but seems unintuitive. However, if you want the intuitive result, then use a PlainDate relativeTo, I guess? I'll proceed to finish up the tests and work on the spec text for this. If anyone has time to take a look at the reference code from which I will build the spec text, I'd much appreciate that. Hopefully we can land #2758 by mid-next week. |
If reviewing, best to look at the current state of #2758. |
These optimizations were developed by Adam Shaw: https://gist.github.com/arshaw/36d3152c21482bcb78ea2c69591b20e0 It does the same thing as previously, although fixes some incidental edge cases that Adam discovered. However, the algorithm is simpler to explain and to understand, and also makes fewer calls into user code. It uses three variations on a bounding technique for rounding: computing the upper and lower result, and checking which one is closer to the original duration, and 'nudging' the duration up or down accordingly. There is one variation for calendar units, one variation for rounding relative to a ZonedDateTime where smallestUnit is a time unit and largestUnit is a calendar unit, and one variation for time units. RoundDuration becomes a lot more simplified, any part of it that was complex is now split out into the new RoundRelativeDuration and BubbleRelativeDuration operations, and the three 'nudging' operations. The operations NormalizedTimeDurationToDays, BalanceTimeDurationRelative, BalanceDateDurationRelative, MoveRelativeDate, MoveRelativeZonedDateTime, and AdjustRoundedDurationDays are no longer needed. Their functionality is subsumed by the new operations. Closes: #2792 Closes: #2817
These optimizations were developed by Adam Shaw: https://gist.github.com/arshaw/36d3152c21482bcb78ea2c69591b20e0 It does the same thing as previously, although fixes some incidental edge cases that Adam discovered. However, the algorithm is simpler to explain and to understand, and also makes fewer calls into user code. It uses three variations on a bounding technique for rounding: computing the upper and lower result, and checking which one is closer to the original duration, and 'nudging' the duration up or down accordingly. There is one variation for calendar units, one variation for rounding relative to a ZonedDateTime where smallestUnit is a time unit and largestUnit is a calendar unit, and one variation for time units. RoundDuration becomes a lot more simplified, any part of it that was complex is now split out into the new RoundRelativeDuration and BubbleRelativeDuration operations, and the three 'nudging' operations. The operations NormalizedTimeDurationToDays, BalanceTimeDurationRelative, BalanceDateDurationRelative, MoveRelativeDate, MoveRelativeZonedDateTime, and AdjustRoundedDurationDays are no longer needed. Their functionality is subsumed by the new operations. Closes: #2792 Closes: #2817
These optimizations were developed by Adam Shaw: https://gist.github.com/arshaw/36d3152c21482bcb78ea2c69591b20e0 It does the same thing as previously, although fixes some incidental edge cases that Adam discovered. However, the algorithm is simpler to explain and to understand, and also makes fewer calls into user code. It uses three variations on a bounding technique for rounding: computing the upper and lower result, and checking which one is closer to the original duration, and 'nudging' the duration up or down accordingly. There is one variation for calendar units, one variation for rounding relative to a ZonedDateTime where smallestUnit is a time unit and largestUnit is a calendar unit, and one variation for time units. RoundDuration becomes a lot more simplified, any part of it that was complex is now split out into the new RoundRelativeDuration and BubbleRelativeDuration operations, and the three 'nudging' operations. The operations NormalizedTimeDurationToDays, BalanceTimeDurationRelative, BalanceDateDurationRelative, MoveRelativeDate, MoveRelativeZonedDateTime, and AdjustRoundedDurationDays are no longer needed. Their functionality is subsumed by the new operations. Closes: #2792 Closes: #2817
To the extent we care about reducing calls to user-code, the relative-rounding algorithm (used by Duration::round and since/until) can be optimized for many fewer calls. I can explain in further detail if desired.
Reduced calls in tests (not comprehensive):
Branch: https://github.com/fullcalendar/test262/tree/temporal-fewer-calls-rounding-rel
Diff: tc39/test262@main...fullcalendar:test262:temporal-fewer-calls-rounding-rel
temporal-polyfill's algorithm:
https://github.com/fullcalendar/temporal-polyfill/blob/2d97b7a038b99b0d491e79ed903f36f5275cb365/packages/temporal-polyfill/src/internal/round.ts#L311
(sorry for the code dump... I can explain it more thoroughly in the future...)
The text was updated successfully, but these errors were encountered: