@@ -183,6 +183,17 @@ impl PackWithMD for i32 {
183183 }
184184}
185185
186+ /// A lower bound for the number of months in `years` years, for use in `until`.
187+ pub ( crate ) enum MinMonths {
188+ /// This number is guaranteed to be correct based on documented checking of invariants
189+ ///
190+ /// It is ok for ICU4X to `debug_assert` on things deriving from the correctness of this calculation
191+ Guaranteed ( i64 ) ,
192+ /// This number is not guaranteed. Code should check that it is not too large, and if not,
193+ /// fall back to the `guarantee` value (which *is* guaranteed).
194+ Guessed { guess : i64 , guarantee : i64 } ,
195+ }
196+
186197/// Trait for converting from era codes, month codes, and other fields to year/month/day ordinals.
187198pub ( crate ) trait DateFieldsResolver : Calendar {
188199 /// This stores the year as either an i32, or a type containing more
@@ -242,9 +253,13 @@ pub(crate) trait DateFieldsResolver: Calendar {
242253 /// is bounded by a constant with respect to `years`.
243254 ///
244255 /// The default impl is for non-lunisolar calendars with 12 months!
256+ ///
257+ /// `until()` will debug assert if this ever returns a value greater than the
258+ /// month diff betweeen two dates as a Guarantee. If such a value is returned as a Guess,
259+ /// it will simply be slow
245260 #[ inline]
246- fn min_months_from_inner ( _start : Self :: YearInfo , years : i64 ) -> i64 {
247- 12 * years
261+ fn min_months_from_inner ( _start : Self :: YearInfo , years : i64 ) -> MinMonths {
262+ MinMonths :: Guaranteed ( 12 * years)
248263 }
249264
250265 /// Calculates the ordinal month for the given year and month code.
@@ -923,7 +938,7 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
923938 cal : & C ,
924939 options : DateDifferenceOptions ,
925940 ) -> DateDuration {
926- // Fast path for day/week diffs
941+ // Non-spec optimization: fast path for day/week diffs
927942 // Avoids quadratic behavior in surpasses() for days/weeks
928943 if matches ! (
929944 options. largest_unit,
@@ -949,6 +964,7 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
949964
950965 let mut surpasses_checker = SurpassesChecker :: new ( self , other, sign, cal) ;
951966
967+ // Preparation for non-specced optimization:
952968 // We don't want to spend time incrementally bumping it up one year
953969 // at a time, so let's pre-guess a year delta that is guaranteed to not
954970 // surpass.
@@ -976,9 +992,10 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
976992 let mut years = 0 ;
977993 if matches ! ( options. largest_unit, Some ( DateDurationUnit :: Years ) ) {
978994 let mut candidate_years = sign;
995+
996+ // Non-spec optimization: try to fast-forward candidate_years to
997+ // a year value that does not surpass
979998 if min_years != 0 {
980- // Optimization: we start with min_years since it is guaranteed to not
981- // surpass.
982999 candidate_years = min_years
9831000 } ;
9841001
@@ -1002,13 +1019,25 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
10021019 Some ( DateDurationUnit :: Years ) | Some ( DateDurationUnit :: Months )
10031020 ) {
10041021 let mut candidate_months = sign;
1022+
1023+ // Non-spec optimization: try to fast-forward candidate_months to
1024+ // a month value that does not surpass
10051025 if options. largest_unit == Some ( DateDurationUnit :: Months ) && min_years != 0 {
10061026 // If largest_unit = Months, then compute the calendar-specific minimum number of
1007- // months corresponding to min_years. For solar calendars, this is 12 * min_years.
1008- // For the Hebrew calendar, a leap month is added for 7 out of 19 years. East Asian
1009- // Calendars do not provide a specialized implementation of `min_months_from()`
1010- // because it would be too expensive to calculate; they default to 12 * min_years.
1011- let min_months = self . min_months_from ( min_years) ;
1027+ // months corresponding to min_years.
1028+ let min_months = match self . min_months_from ( min_years) {
1029+ MinMonths :: Guaranteed ( m) => m,
1030+ // In case it's a guess, check that the guess is in range,
1031+ // and if it's not, return the guarantee
1032+ MinMonths :: Guessed { guess, guarantee } => {
1033+ if surpasses_checker. surpasses_months ( guess) {
1034+ // surpasses, so it's not in range
1035+ guarantee
1036+ } else {
1037+ guess
1038+ }
1039+ }
1040+ } ;
10121041
10131042 // clippy rejected: debug_assert!(!surpasses_checker.surpasses_months(min_months));
10141043 #[ cfg( debug_assertions) ]
@@ -1066,7 +1095,7 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
10661095 }
10671096
10681097 /// The minimum number of months over `years` years, starting from `self.year()`.
1069- pub ( crate ) fn min_months_from ( self , years : i64 ) -> i64 {
1098+ pub ( crate ) fn min_months_from ( self , years : i64 ) -> MinMonths {
10701099 C :: min_months_from_inner ( self . year ( ) , years)
10711100 }
10721101}
0 commit comments