@@ -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.
@@ -922,7 +937,7 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
922937 cal : & C ,
923938 options : DateDifferenceOptions ,
924939 ) -> DateDuration {
925- // Fast path for day/week diffs
940+ // Non-spec optimization: fast path for day/week diffs
926941 // Avoids quadratic behavior in surpasses() for days/weeks
927942 if matches ! (
928943 options. largest_unit,
@@ -946,6 +961,7 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
946961 Ordering :: Less => -1i64 ,
947962 } ;
948963
964+ // Preparation for non-specced optimization:
949965 // We don't want to spend time incrementally bumping it up one year
950966 // at a time, so let's pre-guess a year delta that is guaranteed to not
951967 // surpass.
@@ -973,9 +989,10 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
973989 let mut years = 0 ;
974990 if matches ! ( options. largest_unit, Some ( DateDurationUnit :: Years ) ) {
975991 let mut candidate_years = sign;
992+
993+ // Non-spec optimization: try to fast-forward candidate_years to
994+ // a year value that does not surpass
976995 if min_years != 0 {
977- // Optimization: we start with min_years since it is guaranteed to not
978- // surpass.
979996 candidate_years = min_years
980997 } ;
981998
@@ -1003,13 +1020,29 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
10031020 ) {
10041021 let mut candidate_months = sign;
10051022
1023+ // Non-spec optimization: try to fast-forward candidate_months to
1024+ // a month value that does not surpass
10061025 if options. largest_unit == Some ( DateDurationUnit :: Months ) && min_years != 0 {
10071026 // If largest_unit = Months, then compute the calendar-specific minimum number of
1008- // months corresponding to min_years. For solar calendars, this is 12 * min_years.
1009- // For the Hebrew calendar, a leap month is added for 7 out of 19 years. East Asian
1010- // Calendars do not provide a specialized implementation of `min_months_from()`
1011- // because it would be too expensive to calculate; they default to 12 * min_years.
1012- 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 self . surpasses (
1034+ other,
1035+ DateDuration :: from_signed_ymwd ( years, guess, 0 , 0 ) ,
1036+ sign,
1037+ cal,
1038+ ) {
1039+ // surpasses, so it's not in range
1040+ guarantee
1041+ } else {
1042+ guess
1043+ }
1044+ }
1045+ } ;
10131046 debug_assert ! ( !self . surpasses(
10141047 other,
10151048 DateDuration :: from_signed_ymwd( years, min_months, 0 , 0 ) ,
@@ -1074,7 +1107,7 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
10741107 }
10751108
10761109 /// The minimum number of months over `years` years, starting from `self.year()`.
1077- pub ( crate ) fn min_months_from ( self , years : i64 ) -> i64 {
1110+ pub ( crate ) fn min_months_from ( self , years : i64 ) -> MinMonths {
10781111 C :: min_months_from_inner ( self . year ( ) , years)
10791112 }
10801113}
0 commit comments