@@ -736,87 +736,117 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
736736 sign : i64 ,
737737 cal : & C ,
738738 ) -> bool {
739- // 1. Let _parts_ be CalendarISOToDate(_calendar_, _fromIsoDate_).
739+ // NOTE: Numbered comments refer to the Temporal `NonISODateSurpasses` spec.
740+ // Because this implementation refactors the algorithm into stateful components
741+ // to reduce redundant calculations, references to numbered lines of the spec are
742+ // distributed across multiple functions. This is intentional and meaningful.
743+ let sign_mul = if duration. is_negative { -1i64 } else { 1i64 } ;
744+ let years = i64:: from ( duration. years ) * sign_mul;
745+ let months = i64:: from ( duration. months ) * sign_mul;
746+
747+ let month_checker = self . surpasses_month_checker ( other, years, sign, cal) ;
748+ if month_checker. surpasses_month ( months) {
749+ return true ;
750+ }
751+
752+ // 8. If weeks = 0 and days = 0, return false.
753+ if duration. weeks == 0 && duration. days == 0 {
754+ return false ;
755+ }
756+
757+ let months_added =
758+ Self :: new_balanced ( month_checker. y0 , months + i64:: from ( month_checker. m0 ) , 1 , cal) ;
759+ let week_day_checker =
760+ self . surpasses_week_day_checker_from_months_added ( & month_checker, months_added) ;
761+ week_day_checker. surpasses_week_day ( duration)
762+ }
763+
764+ /// Prepares a stateful checker for month iteration in surpasses().
765+ fn surpasses_month_checker < ' a > (
766+ & ' a self ,
767+ other : & ' a Self ,
768+ years : i64 ,
769+ sign : i64 ,
770+ cal : & ' a C ,
771+ ) -> SurpassesMonthChecker < ' a , C > {
772+ // 1. Let parts be CalendarISOToDate(calendar, fromIsoDate).
740773 let parts = self ;
741- // 1 . Let _calDate2_ be CalendarISOToDate(_calendar_, _toIsoDate_ ).
774+ // 2 . Let calDate2 be CalendarISOToDate(calendar, toIsoDate ).
742775 let cal_date_2 = other;
743- // 1. Let _y0_ be _parts_.[[Year]] + _years_.
744- let y0 =
745- cal. year_info_from_extended ( duration. add_years_to ( parts. year ( ) . to_extended_year ( ) ) ) ;
746- // 1. If CompareSurpasses(_sign_, _y0_, _parts_.[[MonthCode]], _parts_.[[Day]], _calDate2_) is *true*, return *true*.
776+ // 3. Let y0 be parts.[[Year]] + years.
777+ let y0 = cal. year_info_from_extended ( parts. year ( ) . to_extended_year ( ) + years as i32 ) ;
747778 let base_month = cal. month_from_ordinal ( parts. year ( ) , parts. month ( ) ) ;
748- if Self :: compare_surpasses_lexicographic ( sign, y0, base_month, parts. day ( ) , cal_date_2, cal)
749- {
750- return true ;
751- }
752- // 1. Let _m0_ be MonthCodeToOrdinal(_calendar_, _y0_, ! ConstrainMonthCode(_calendar_, _y0_, _parts_.[[MonthCode]], ~constrain~)).
753779 let constrain = DateFromFieldsOptions {
754780 overflow : Some ( Overflow :: Constrain ) ,
755781 ..Default :: default ( )
756782 } ;
757- let m0_result = cal. ordinal_from_month ( y0, base_month, constrain) ;
758- let m0 = match m0_result {
759- Ok ( m0) => m0,
760- Err ( _) => {
761- debug_assert ! (
762- false ,
763- "valid month code for calendar, and constrained to the year"
764- ) ;
765- 1
766- }
767- } ;
768- // 1. Let _monthsAdded_ be BalanceNonISODate(_calendar_, _y0_, _m0_ + _months_, 1).
769- let months_added = Self :: new_balanced ( y0, duration. add_months_to ( m0) , 1 , cal) ;
783+ // 5. Let m0 be MonthCodeToOrdinal(calendar, y0, ! ConstrainMonthCode(calendar, y0, parts.[[MonthCode]], constrain)).
784+ let m0 = cal
785+ . ordinal_from_month ( y0, base_month, constrain)
786+ . unwrap_or ( 1 ) ;
770787
771- // 1 . If CompareSurpasses(_sign_, _monthsAdded_ .[[Year]], _monthsAdded_ .[[Month]], _parts_ .[[Day]], _calDate2_ ) is * true* , return * true* .
772- if Self :: compare_surpasses_ordinal (
773- sign,
774- months_added . year ,
775- months_added . ordinal_month ,
776- parts. day ( ) ,
788+ // 7 . If CompareSurpasses(sign, monthsAdded .[[Year]], monthsAdded .[[Month]], parts .[[Day]], calDate2 ) is true, return true.
789+ let lexicographic_surpasses =
790+ Self :: compare_surpasses_lexicographic ( sign, y0 , base_month , parts . day ( ) , cal_date_2 , cal ) ;
791+
792+ SurpassesMonthChecker {
793+ parts,
777794 cal_date_2,
778- ) {
779- return true ;
780- }
781- // 1. If _weeks_ = 0 and _days_ = 0, return *false*.
782- if duration. weeks == 0 && duration. days == 0 {
783- return false ;
795+ y0,
796+ m0,
797+ lexicographic_surpasses,
798+ sign,
799+ cal,
784800 }
785- // 1. Let _endOfMonth_ be BalanceNonISODate(_calendar_, _monthsAdded_.[[Year]], _monthsAdded_.[[Month]] + 1, 0).
801+ }
802+
803+ /// Prepares a stateful checker for week and day iteration in surpasses().
804+ fn surpasses_week_day_checker < ' a > (
805+ & ' a self ,
806+ other : & ' a Self ,
807+ years : i64 ,
808+ months : i64 ,
809+ sign : i64 ,
810+ cal : & ' a C ,
811+ ) -> SurpassesWeekDayChecker < ' a , C > {
812+ let month_checker = self . surpasses_month_checker ( other, years, sign, cal) ;
813+ // 6. Let monthsAdded be BalanceNonISODate(calendar, y0, m0 + months, 1).
814+ let months_added =
815+ Self :: new_balanced ( month_checker. y0 , months + i64:: from ( month_checker. m0 ) , 1 , cal) ;
816+ self . surpasses_week_day_checker_from_months_added ( & month_checker, months_added)
817+ }
818+
819+ /// Prepares a checker for week and day iteration from an existing months_added date.
820+ fn surpasses_week_day_checker_from_months_added < ' a > (
821+ & ' a self ,
822+ month_checker : & SurpassesMonthChecker < ' a , C > ,
823+ months_added : UncheckedArithmeticDate < C > ,
824+ ) -> SurpassesWeekDayChecker < ' a , C > {
825+ // 9. Let endOfMonth be BalanceNonISODate(calendar, monthsAdded.[[Year]], monthsAdded.[[Month]] + 1, 0).
786826 let end_of_month = Self :: new_balanced (
787827 months_added. year ,
788828 i64:: from ( months_added. ordinal_month ) + 1 ,
789829 0 ,
790- cal,
830+ month_checker . cal ,
791831 ) ;
792- // 1 . Let _baseDay_ be _parts_ .[[Day]].
793- let base_day = parts . day ( ) ;
794- // 1 . If _baseDay_ ≤ _endOfMonth_ .[[Day]], then
795- // 1 . Let _regulatedDay_ be _baseDay_ .
796- // 1 . Else,
797- // 1 . Let _regulatedDay_ be _endOfMonth_ .[[Day]].
832+ // 10 . Let baseDay be parts .[[Day]].
833+ let base_day = self . day ( ) ;
834+ // 11 . If baseDay ≤ endOfMonth .[[Day]], then
835+ // a . Let regulatedDay be baseDay .
836+ // 12 . Else,
837+ // a . Let regulatedDay be endOfMonth .[[Day]].
798838 let regulated_day = if base_day < end_of_month. day {
799839 base_day
800840 } else {
801841 end_of_month. day
802842 } ;
803- // 1. Let _daysInWeek_ be 7 (the number of days in a week for all supported calendars).
804- // 1. Let _balancedDate_ be BalanceNonISODate(_calendar_, _endOfMonth_.[[Year]], _endOfMonth_.[[Month]], _regulatedDay_ + _daysInWeek_ * _weeks_ + _days_).
805- // 1. Return CompareSurpasses(_sign_, _balancedDate_.[[Year]], _balancedDate_.[[Month]], _balancedDate_.[[Day]], _calDate2_).
806- let balanced_date = Self :: new_balanced (
807- end_of_month. year ,
808- i64:: from ( end_of_month. ordinal_month ) ,
809- duration. add_weeks_and_days_to ( regulated_day) ,
810- cal,
811- ) ;
812-
813- Self :: compare_surpasses_ordinal (
814- sign,
815- balanced_date. year ,
816- balanced_date. ordinal_month ,
817- balanced_date. day ,
818- cal_date_2,
819- )
843+ SurpassesWeekDayChecker {
844+ cal_date_2 : month_checker. cal_date_2 ,
845+ end_of_month,
846+ regulated_day,
847+ sign : month_checker. sign ,
848+ cal : month_checker. cal ,
849+ }
820850 }
821851
822852 /// Implements the Temporal abstract operation `NonISODateAdd`.
@@ -934,8 +964,8 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
934964 }
935965 }
936966
937- // 1. Let _sign_ be -1 × CompareISODate(_one_, _two_ ).
938- // 1 . If _sign_ = 0, return ZeroDateDuration().
967+ // 1. Let sign be -1 × CompareISODate(one, two ).
968+ // 2 . If sign = 0, return ZeroDateDuration().
939969 let sign = match other. cmp ( self ) {
940970 Ordering :: Greater => 1i64 ,
941971 Ordering :: Equal => return DateDuration :: default ( ) ,
@@ -959,12 +989,12 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
959989 cal,
960990 ) ) ;
961991
962- // 1 . Let _years_ be 0.
963- // 1 . If _largestUnit_ is ~ year~ , then
964- // 1 . Let _candidateYears_ be _sign_ .
965- // 1 . Repeat, while NonISODateSurpasses(_calendar_, _sign_, _one_, _two_, _candidateYears_ , 0, 0, 0) is * false* ,
966- // 1 . Set _years_ to _candidateYears_ .
967- // 1 . Set _candidateYears_ to _candidateYears_ + _sign_ .
992+ // 3 . Let years be 0.
993+ // 4 . If largestUnit is year, then
994+ // a . Let candidateYears be sign .
995+ // b . Repeat, while NonISODateSurpasses(calendar, sign, one, two, candidateYears , 0, 0, 0) is false,
996+ // i . Set years to candidateYears .
997+ // ii . Set candidateYears to candidateYears + sign .
968998
969999 let mut years = 0 ;
9701000 if matches ! ( options. largest_unit, Some ( DateDurationUnit :: Years ) ) {
@@ -986,12 +1016,12 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
9861016 }
9871017 }
9881018
989- // 1 . Let _months_ be 0.
990- // 1 . If _largestUnit_ is ~ year~ or _largestUnit_ is ~ month~ , then
991- // 1 . Let _candidateMonths_ be _sign_ .
992- // 1 . Repeat, while NonISODateSurpasses(_calendar_, _sign_, _one_, _two_, _years_, _candidateMonths_ , 0, 0) is * false* ,
993- // 1 . Set _months_ to _candidateMonths_ .
994- // 1 . Set _candidateMonths_ to _candidateMonths_ + _sign_ .
1019+ // 5 . Let months be 0.
1020+ // 6 . If largestUnit is year or largestUnit is month, then
1021+ // a . Let candidateMonths be sign .
1022+ // b . Repeat, while NonISODateSurpasses(calendar, sign, one, two, years, candidateMonths , 0, 0) is false,
1023+ // i . Set months to candidateMonths .
1024+ // ii . Set candidateMonths to candidateMonths + sign .
9951025 let mut months = 0 ;
9961026 if matches ! (
9971027 options. largest_unit,
@@ -1015,57 +1045,44 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
10151045 candidate_months = min_months
10161046 }
10171047
1018- while !self . surpasses (
1019- other,
1020- DateDuration :: from_signed_ymwd ( years, candidate_months, 0 , 0 ) ,
1021- sign,
1022- cal,
1023- ) {
1048+ let checker = self . surpasses_month_checker ( other, years, sign, cal) ;
1049+ while !checker. surpasses_month ( candidate_months) {
10241050 months = candidate_months;
10251051 candidate_months += sign;
10261052 }
10271053 }
1028- // 1 . Let _weeks_ be 0.
1029- // 1 . If _largestUnit_ is ~ week~ , then
1030- // 1 . Let _candidateWeeks_ be _sign_ .
1031- // 1 . Repeat, while NonISODateSurpasses(_calendar_, _sign_, _one_, _two_, _years_, _months_, _candidateWeeks_ , 0) is * false* ,
1032- // 1 . Set _weeks_ to _candidateWeeks_ .
1033- // 1 . Set _candidateWeeks_ to _candidateWeeks_ + sign.
1054+ // 7 . Let weeks be 0.
1055+ // 8 . If largestUnit is week, then
1056+ // a . Let candidateWeeks be sign .
1057+ // b . Repeat, while NonISODateSurpasses(calendar, sign, one, two, years, months, candidateWeeks , 0) is false,
1058+ // i . Set weeks to candidateWeeks .
1059+ // ii . Set candidateWeeks to candidateWeeks + sign.
10341060 let mut weeks = 0 ;
1061+ let checker = self . surpasses_week_day_checker ( other, years, months, sign, cal) ;
10351062 if matches ! ( options. largest_unit, Some ( DateDurationUnit :: Weeks ) ) {
10361063 let mut candidate_weeks = sign;
1037- while !self . surpasses (
1038- other,
1039- DateDuration :: from_signed_ymwd ( years, months, candidate_weeks, 0 ) ,
1040- sign,
1041- cal,
1042- ) {
1064+ while !checker. surpasses_week ( candidate_weeks) {
10431065 weeks = candidate_weeks;
10441066 candidate_weeks += sign;
10451067 }
10461068 }
10471069
1048- // 1 . Let _days_ be 0.
1049- // 1 . Let _candidateDays_ be _sign_ .
1050- // 1 . Repeat, while NonISODateSurpasses(_calendar_, _sign_, _one_, _two_, _years_, _months_, _weeks_, _candidateDays_ ) is * false* ,
1051- // 1 . Set _days_ to _candidateDays_ .
1052- // 1 . Set _candidateDays_ to _candidateDays_ + _sign_ .
1070+ // 9 . Let days be 0.
1071+ // 10 . Let candidateDays be sign .
1072+ // 11 . Repeat, while NonISODateSurpasses(calendar, sign, one, two, years, months, weeks, candidateDays ) is false,
1073+ // a . Set days to candidateDays .
1074+ // b . Set candidateDays to candidateDays + sign .
10531075 let mut days = 0 ;
10541076 // There is no pressing need to optimize candidate_days here: the early-return RD arithmetic
10551077 // optimization will be hit if the largest_unit is weeks/days, and if it is months or years we will
10561078 // arrive here with a candidate date that is at most 31 days off. We can run this loop 31 times.
10571079 let mut candidate_days = sign;
1058- while !self . surpasses (
1059- other,
1060- DateDuration :: from_signed_ymwd ( years, months, weeks, candidate_days) ,
1061- sign,
1062- cal,
1063- ) {
1080+ while !checker. surpasses_day ( weeks, candidate_days) {
10641081 days = candidate_days;
10651082 candidate_days += sign;
10661083 }
10671084
1068- // 1 . Return ! CreateDateDurationRecord(_years_, _months_, _weeks_, _days_ ).
1085+ // 12 . Return ! CreateDateDurationRecord(years, months, weeks, days ).
10691086 DateDuration :: from_signed_ymwd ( years, months, weeks, days)
10701087 }
10711088
@@ -1075,6 +1092,89 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
10751092 }
10761093}
10771094
1095+ /// Stateful checker for month iteration in surpasses().
1096+ ///
1097+ /// By saving intermediary computations based on a fixed year,
1098+ /// only the computations relating to the month are done. The week
1099+ /// and day are expected to be zero.
1100+ struct SurpassesMonthChecker < ' a , C : DateFieldsResolver > {
1101+ parts : & ' a ArithmeticDate < C > ,
1102+ cal_date_2 : & ' a ArithmeticDate < C > ,
1103+ y0 : C :: YearInfo ,
1104+ m0 : u8 ,
1105+ lexicographic_surpasses : bool ,
1106+ sign : i64 ,
1107+ cal : & ' a C ,
1108+ }
1109+
1110+ impl < ' a , C : DateFieldsResolver > SurpassesMonthChecker < ' a , C > {
1111+ fn surpasses_month ( & self , months : i64 ) -> bool {
1112+ // 4. If CompareSurpasses(sign, y0, parts.[[MonthCode]], parts.[[Day]], calDate2) is true, return true.
1113+ if self . lexicographic_surpasses {
1114+ return true ;
1115+ }
1116+ // 6. Let monthsAdded be BalanceNonISODate(calendar, y0, m0 + months, 1).
1117+ let months_added =
1118+ ArithmeticDate :: < C > :: new_balanced ( self . y0 , months + i64:: from ( self . m0 ) , 1 , self . cal ) ;
1119+
1120+ // 7. If CompareSurpasses(sign, monthsAdded.[[Year]], monthsAdded.[[Month]], parts.[[Day]], calDate2) is true, return true.
1121+ ArithmeticDate :: < C > :: compare_surpasses_ordinal (
1122+ self . sign ,
1123+ months_added. year ,
1124+ months_added. ordinal_month ,
1125+ self . parts . day ( ) ,
1126+ self . cal_date_2 ,
1127+ )
1128+ }
1129+ }
1130+
1131+ /// Stateful checker for week and day iteration in surpasses().
1132+ ///
1133+ /// By saving intermediary computations based on a fixed year and month,
1134+ /// only the computations relating to the week and day are done.
1135+ struct SurpassesWeekDayChecker < ' a , C : DateFieldsResolver > {
1136+ cal_date_2 : & ' a ArithmeticDate < C > ,
1137+ end_of_month : UncheckedArithmeticDate < C > ,
1138+ regulated_day : u8 ,
1139+ sign : i64 ,
1140+ cal : & ' a C ,
1141+ }
1142+
1143+ impl < ' a , C : DateFieldsResolver > SurpassesWeekDayChecker < ' a , C > {
1144+ fn surpasses_balanced_day ( & self , balanced_day : i64 ) -> bool {
1145+ // 14. Let balancedDate be BalanceNonISODate(calendar, endOfMonth.[[Year]], endOfMonth.[[Month]], regulatedDay + daysInWeek * weeks + days).
1146+ let balanced_date = ArithmeticDate :: < C > :: new_balanced (
1147+ self . end_of_month . year ,
1148+ i64:: from ( self . end_of_month . ordinal_month ) ,
1149+ balanced_day,
1150+ self . cal ,
1151+ ) ;
1152+
1153+ // 15. Return CompareSurpasses(sign, balancedDate.[[Year]], balancedDate.[[Month]], balancedDate.[[Day]], calDate2).
1154+ ArithmeticDate :: < C > :: compare_surpasses_ordinal (
1155+ self . sign ,
1156+ balanced_date. year ,
1157+ balanced_date. ordinal_month ,
1158+ balanced_date. day ,
1159+ self . cal_date_2 ,
1160+ )
1161+ }
1162+
1163+ fn surpasses_week ( & self , weeks : i64 ) -> bool {
1164+ // 13. Let daysInWeek be 7 (the number of days in a week for all supported calendars).
1165+ self . surpasses_balanced_day ( 7 * weeks + i64:: from ( self . regulated_day ) )
1166+ }
1167+
1168+ fn surpasses_day ( & self , weeks : i64 , days : i64 ) -> bool {
1169+ // 13. Let daysInWeek be 7 (the number of days in a week for all supported calendars).
1170+ self . surpasses_balanced_day ( 7 * weeks + days + i64:: from ( self . regulated_day ) )
1171+ }
1172+
1173+ fn surpasses_week_day ( & self , duration : DateDuration ) -> bool {
1174+ self . surpasses_balanced_day ( duration. add_weeks_and_days_to ( self . regulated_day ) )
1175+ }
1176+ }
1177+
10781178#[ cfg( test) ]
10791179mod tests {
10801180 use super :: * ;
0 commit comments