@@ -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
2425use 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+
398408impl 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 }
0 commit comments