Skip to content

Commit d3594c8

Browse files
committed
Update to new ComputeNudgeWindow spec text
1 parent dcd5688 commit d3594c8

File tree

2 files changed

+181
-36
lines changed

2 files changed

+181
-36
lines changed

src/builtins/core/duration/normalized.rs

Lines changed: 159 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ use crate::{
1818
primitive::{DoubleDouble, FiniteF64},
1919
provider::TimeZoneProvider,
2020
rounding::IncrementRounder,
21-
Calendar, TemporalError, TemporalResult, TemporalUnwrap, NS_PER_DAY, NS_PER_DAY_NONZERO,
21+
temporal_assert, Calendar, TemporalError, TemporalResult, TemporalUnwrap, NS_PER_DAY,
22+
NS_PER_DAY_NONZERO,
2223
};
2324

2425
use super::{DateDuration, Duration, Sign};
@@ -395,18 +396,26 @@ struct NudgeRecord {
395396
expanded: bool,
396397
}
397398

399+
struct NudgeWindow {
400+
r1: i128,
401+
r2: i128,
402+
start_epoch_ns: EpochNanoseconds,
403+
end_epoch_ns: EpochNanoseconds,
404+
start_duration: DateDuration,
405+
end_duration: DateDuration,
406+
}
407+
398408
impl InternalDurationRecord {
399-
// TODO: Add assertion into impl.
400-
// TODO: Add unit tests specifically for nudge_calendar_unit if possible.
401-
fn nudge_calendar_unit(
409+
/// <https://tc39.es/proposal-temporal/#sec-temporal-computenudgewindow>
410+
fn compute_nudge_window(
402411
&self,
403412
sign: Sign,
404413
origin_epoch_ns: EpochNanoseconds,
405-
dest_epoch_ns: i128,
406414
dt: &PlainDateTime,
407415
time_zone: Option<(&TimeZone, &(impl TimeZoneProvider + ?Sized))>, // ???
408416
options: ResolvedRoundingOptions,
409-
) -> TemporalResult<NudgeRecord> {
417+
additional_shift: bool,
418+
) -> TemporalResult<NudgeWindow> {
410419
// NOTE: r2 may never be used...need to test.
411420
let (r1, r2, start_duration, end_duration) = match options.smallest_unit {
412421
// 1. If unit is "year", then
@@ -417,11 +426,18 @@ impl InternalDurationRecord {
417426
options.increment.as_extended_increment(),
418427
)?
419428
.round(RoundingMode::Trunc);
420-
// b. Let r1 be years.
421-
let r1 = years;
422-
// c. Let r2 be years + increment × sign.
423-
let r2 = years
424-
+ i128::from(options.increment.get()) * i128::from(sign.as_sign_multiplier());
429+
let increment_x_sign =
430+
i128::from(options.increment.get()) * i128::from(sign.as_sign_multiplier());
431+
// b. If additionalShift is false, then
432+
let r1 = if !additional_shift {
433+
// i. Let r1 be years.
434+
years
435+
} else {
436+
// i. Let r1 be years + increment × sign.
437+
years + increment_x_sign
438+
};
439+
// c. Let r2 be r1 + increment × sign.
440+
let r2 = r1 + increment_x_sign;
425441
// d. Let startDuration be ? CreateNormalizedDurationRecord(r1, 0, 0, 0, ZeroTimeDuration()).
426442
// e. Let endDuration be ? CreateNormalizedDurationRecord(r2, 0, 0, 0, ZeroTimeDuration()).
427443
(
@@ -449,11 +465,18 @@ impl InternalDurationRecord {
449465
options.increment.as_extended_increment(),
450466
)?
451467
.round(RoundingMode::Trunc);
452-
// b. Let r1 be months.
453-
let r1 = months;
454-
// c. Let r2 be months + increment × sign.
455-
let r2 = months
456-
+ i128::from(options.increment.get()) * i128::from(sign.as_sign_multiplier());
468+
let increment_x_sign =
469+
i128::from(options.increment.get()) * i128::from(sign.as_sign_multiplier());
470+
// b. If additionalShift is false, then
471+
let r1 = if !additional_shift {
472+
// i. Let r1 be months.
473+
months
474+
} else {
475+
// i. Let r1 be months + increment × sign.
476+
months + increment_x_sign
477+
};
478+
// c. Let r2 be r1 + increment × sign.
479+
let r2 = r1 + increment_x_sign;
457480
// d. Let startDuration be ? CreateNormalizedDurationRecord(duration.[[Years]], r1, 0, 0, ZeroTimeDuration()).
458481
// e. Let endDuration be ? CreateNormalizedDurationRecord(duration.[[Years]], r2, 0, 0, ZeroTimeDuration()).
459482
(
@@ -602,6 +625,14 @@ impl InternalDurationRecord {
602625
}
603626
};
604627

628+
// 5. Assert: If sign is 1, r1 ≥ 0 and r1 < r2.
629+
// 6. Assert: If sign is -1, r1 ≤ 0 and r1 > r2.
630+
// n.b. sign == 1 means nonnegative
631+
crate::temporal_assert!(
632+
(sign != Sign::Negative && r1 >= 0 && r1 < r2)
633+
|| (sign == Sign::Negative && r1 < 0 && r1 > r2)
634+
);
635+
605636
let start_epoch_ns = if r1 == 0 {
606637
origin_epoch_ns
607638
} else {
@@ -646,36 +677,129 @@ impl InternalDurationRecord {
646677
end.as_nanoseconds()
647678
};
648679

649-
// TODO: look into handling asserts
650-
// 13. If sign is 1, then
651-
// a. Assert: startEpochNs ≤ destEpochNs ≤ endEpochNs.
652-
// 14. Else,
653-
// a. Assert: endEpochNs ≤ destEpochNs ≤ startEpochNs.
654-
// 15. Assert: startEpochNs ≠ endEpochNs.
680+
return Ok(NudgeWindow {
681+
r1,
682+
r2,
683+
start_epoch_ns,
684+
end_epoch_ns,
685+
start_duration,
686+
end_duration,
687+
});
688+
}
689+
// TODO: Add assertion into impl.
690+
// TODO: Add unit tests specifically for nudge_calendar_unit if possible.
691+
fn nudge_calendar_unit(
692+
&self,
693+
sign: Sign,
694+
origin_epoch_ns: EpochNanoseconds,
695+
dest_epoch_ns: i128,
696+
dt: &PlainDateTime,
697+
time_zone: Option<(&TimeZone, &(impl TimeZoneProvider + ?Sized))>, // ???
698+
options: ResolvedRoundingOptions,
699+
) -> TemporalResult<NudgeRecord> {
700+
let dest_epoch_ns = EpochNanoseconds(dest_epoch_ns);
701+
702+
// 1. Let didExpandCalendarUnit be false.
703+
let mut did_expand_calendar_unit = false;
704+
705+
// 2. Let nudgeWindow be ? ComputeNudgeWindow(sign, duration, originEpochNs, isoDateTime, timeZone, calendar, increment, unit, false).
706+
let mut nudge_window =
707+
self.compute_nudge_window(sign, origin_epoch_ns, dt, time_zone, options, false)?;
708+
709+
// 3. Let startEpochNs be nudgeWindow.[[StartEpochNs]].
710+
// 4. Let endEpochNs be nudgeWindow.[[EndEpochNs]].
711+
// (implicitly used)
712+
713+
// 5. If sign is 1, then
714+
if sign != Sign::Negative {
715+
// a. If startEpochNs ≤ destEpochNs ≤ endEpochNs is false, then
716+
if !(nudge_window.start_epoch_ns <= dest_epoch_ns
717+
&& dest_epoch_ns <= nudge_window.end_epoch_ns)
718+
{
719+
// i. Set nudgeWindow to ? ComputeNudgeWindow(sign, duration, originEpochNs, isoDateTime, timeZone, calendar, increment, unit, true).
720+
nudge_window =
721+
self.compute_nudge_window(sign, origin_epoch_ns, dt, time_zone, options, true)?;
722+
// ii. Assert: nudgeWindow.[[StartEpochNs]] ≤ destEpochNs ≤ nudgeWindow.[[EndEpochNs]].
723+
temporal_assert!(
724+
nudge_window.start_epoch_ns <= dest_epoch_ns
725+
&& dest_epoch_ns <= nudge_window.end_epoch_ns
726+
);
727+
// iii. Set didExpandCalendarUnit to true.
728+
did_expand_calendar_unit = true;
729+
}
730+
} else {
731+
// a. If endEpochNs ≤ destEpochNs ≤ startEpochNs is false, then
732+
if !(nudge_window.end_epoch_ns <= dest_epoch_ns
733+
&& dest_epoch_ns <= nudge_window.start_epoch_ns)
734+
{
735+
// i. Set nudgeWindow to ? ComputeNudgeWindow(sign, duration, originEpochNs, isoDateTime, timeZone, calendar, increment, unit, true).
736+
nudge_window =
737+
self.compute_nudge_window(sign, origin_epoch_ns, dt, time_zone, options, true)?;
738+
// ii. Assert: nudgeWindow.[[EndEpochNs]] ≤ destEpochNs ≤ nudgeWindow.[[StartEpochNs]].
739+
temporal_assert!(
740+
nudge_window.end_epoch_ns <= dest_epoch_ns
741+
&& dest_epoch_ns <= nudge_window.start_epoch_ns
742+
);
743+
// iii. Set didExpandCalendarUnit to true.
744+
did_expand_calendar_unit = true;
745+
}
746+
}
747+
748+
// 7. Let r1 be nudgeWindow.[[R1]].
749+
// 8. Let r2 be nudgeWindow.[[R2]].
750+
// 9. Set startEpochNs to nudgeWindow.[[StartEpochNs]].
751+
// 10. Set endEpochNs to nudgeWindow.[[StartEpochNs]].
752+
// 11. Let startDuration be nudgeWindow.[[StartDuration]].
753+
// 12. Let endDuration be nudgeWindow.[[EndDuration]].
754+
755+
let NudgeWindow {
756+
r1,
757+
r2,
758+
start_epoch_ns,
759+
end_epoch_ns,
760+
start_duration,
761+
end_duration,
762+
} = nudge_window;
763+
764+
// 13. Assert: startEpochNs ≠ endEpochNs.
765+
temporal_assert!(start_epoch_ns != end_epoch_ns);
655766

656767
// TODO: Don't use f64 below ...
657768
// NOTE(nekevss): Step 12..13 could be problematic...need tests
658769
// and verify, or completely change the approach involved.
659770
// TODO(nekevss): Validate that the `f64` casts here are valid in all scenarios
660-
// 16. Let progress be (destEpochNs - startEpochNs) / (endEpochNs - startEpochNs).
661-
// 17. Let total be r1 + progress × increment × sign.
662-
let progress =
663-
(dest_epoch_ns - start_epoch_ns.0) as f64 / (end_epoch_ns.0 - start_epoch_ns.0) as f64;
771+
// 14. Let progress be (destEpochNs - startEpochNs) / (endEpochNs - startEpochNs).
772+
// 15. Let total be r1 + progress × increment × sign.
773+
let progress = (dest_epoch_ns.0 - start_epoch_ns.0) as f64
774+
/ (end_epoch_ns.0 - start_epoch_ns.0) as f64;
664775
let total = r1 as f64
665776
+ progress * options.increment.get() as f64 * f64::from(sign.as_sign_multiplier());
666777

667-
// 14. NOTE: The above two steps cannot be implemented directly using floating-point arithmetic.
778+
// 16. NOTE: The above two steps cannot be implemented directly using floating-point arithmetic.
668779
// This division can be implemented as if constructing Normalized Time Duration Records for the denominator
669780
// and numerator of total and performing one division operation with a floating-point result.
670-
// 15. Let roundedUnit be ApplyUnsignedRoundingMode(total, r1, r2, unsignedRoundingMode).
671-
let rounded_unit =
781+
// 17. Assert: 0 ≤ progress ≤ 1.
782+
temporal_assert!(0. <= progress && progress <= 1.);
783+
// 18. If sign < 0, let isNegative be negative; else let isNegative be positive.
784+
// (used implicitly)
785+
786+
// 19. Let unsignedRoundingMode be GetUnsignedRoundingMode(roundingMode, isNegative).
787+
788+
// 20. If progress = 1, then
789+
let rounded_unit = if progress == 1. {
790+
// a. Let roundedUnit be abs(r2).
791+
r2.abs()
792+
} else {
793+
// a. Assert: abs(r1) ≤ abs(total) < abs(r2).
794+
temporal_assert!(r1.abs() as f64 <= total.abs() && total.abs() < r2.abs() as f64);
795+
// b. Let roundedUnit be ApplyUnsignedRoundingMode(abs(total), abs(r1), abs(r2), unsignedRoundingMode).
796+
// TODO: what happens to r2 here?
672797
IncrementRounder::from_signed_num(total, options.increment.as_extended_increment())?
673-
.round(options.rounding_mode);
798+
.round(options.rounding_mode)
799+
};
674800

675-
// 16. If roundedUnit - total < 0, let roundedSign be -1; else let roundedSign be 1.
676-
// 19. Return Duration Nudge Result Record { [[Duration]]: resultDuration, [[Total]]: total, [[NudgedEpochNs]]: nudgedEpochNs, [[DidExpandCalendarUnit]]: didExpandCalendarUnit }.
677-
// 17. If roundedSign = sign, then
678-
if rounded_unit == r2 {
801+
// 22. If roundedUnit is abs(r2), then
802+
if rounded_unit == r2.abs() {
679803
// a. Let didExpandCalendarUnit be true.
680804
// b. Let resultDuration be endDuration.
681805
// c. Let nudgedEpochNs be endEpochNs.
@@ -687,14 +811,13 @@ impl InternalDurationRecord {
687811
})
688812
// 18. Else,
689813
} else {
690-
// a. Let didExpandCalendarUnit be false.
691814
// b. Let resultDuration be startDuration.
692815
// c. Let nudgedEpochNs be startEpochNs.
693816
Ok(NudgeRecord {
694817
normalized: InternalDurationRecord::new(start_duration, TimeDuration::default())?,
695818
total: Some(FiniteF64::try_from(total)?),
696819
nudge_epoch_ns: start_epoch_ns.0,
697-
expanded: false,
820+
expanded: did_expand_calendar_unit,
698821
})
699822
}
700823
}

src/builtins/core/duration/tests.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::{
55
parsers::Precision,
66
partial::PartialDuration,
77
provider::NeverProvider,
8+
Calendar, PlainDate,
89
};
910

1011
use super::Duration;
@@ -452,3 +453,24 @@ fn total_full_numeric_precision() {
452453
let d = Duration::new(0, 0, 0, 0, 0, 0, 0, MAX_SAFE_INTEGER + 1, 1999, 0).unwrap();
453454
assert_eq!(d.total(Unit::Millisecond, None).unwrap(), 9007199254740994.);
454455
}
456+
457+
/// Test for https://github.com/tc39/proposal-temporal/pull/3172/
458+
///
459+
/// test262: built-ins/Temporal/Duration/prototype/total/rounding-window
460+
#[test]
461+
#[cfg(feature = "compiled_data")]
462+
fn test_nudge_relative_date_total() {
463+
let d = Duration::new(1, 0, 0, 0, 1, 0, 0, 0, 0, 0).unwrap();
464+
let relative = PlainDate::new(2020, 2, 29, Calendar::ISO).unwrap();
465+
assert_eq!(
466+
d.total(Unit::Year, Some(relative.into())).unwrap(),
467+
1.0001141552511414
468+
);
469+
470+
let d = Duration::new(0, 1, 0, 0, 10, 0, 0, 0, 0, 0).unwrap();
471+
let relative = PlainDate::new(2020, 1, 31, Calendar::ISO).unwrap();
472+
assert_eq!(
473+
d.total(Unit::Month, Some(relative.into())).unwrap(),
474+
1.0134408602150538
475+
);
476+
}

0 commit comments

Comments
 (0)