diff --git a/components/calendar/src/cal/east_asian_traditional.rs b/components/calendar/src/cal/east_asian_traditional.rs index 080e1b77e53..cc09f06910d 100644 --- a/components/calendar/src/cal/east_asian_traditional.rs +++ b/components/calendar/src/cal/east_asian_traditional.rs @@ -589,8 +589,7 @@ impl DateFieldsResolver for EastAsianTraditional { #[inline] fn min_months_from_inner(_start: Self::YearInfo, years: i64) -> i64 { - // A lunisolar leap month is inserted at least every 3 years. Reingold denies - // that the Chinese calendar determines leap years using the 19-year Metonic cycle. + // TODO(#7077) This is not verified, and the error is not constant bounded 12 * years + (years / 3) } diff --git a/components/calendar/src/cal/hebrew.rs b/components/calendar/src/cal/hebrew.rs index 630f5068543..b374860bb9f 100644 --- a/components/calendar/src/cal/hebrew.rs +++ b/components/calendar/src/cal/hebrew.rs @@ -136,7 +136,30 @@ impl DateFieldsResolver for Hebrew { #[inline] fn min_months_from_inner(_start: HebrewYear, years: i64) -> i64 { - // There are 7 leap years in every 19-year Metonic cycle. + // The Hebrew Metonic cycle is 7 leap years every 19 years, + // which comes out to 235 months per 19 years. + // + // We need to ensure that this is always *lower or equal to* the number of + // months in a given year span. + // + // Firstly, note that this math will produce exactly the number of months in any given period + // that spans a whole number of cycles. Note that we are only performing integer + // ops here, and our SAFE_YEAR_RANGE is well within the range of allowed values + // for multiplying by 235. + // + // So we only need to verify that this math produces the right results within a single cycle. + // + // The Hebrew Metonic cycle has leap years in year 3, 6, 8, 11, 14, 17, and 19 (starting counting at year 1), + // i.e., leap year gaps of +3, +3, +2, +3, +3, +3, +2. + // + // 235 / 19 is ≈12 7/19 months per year, which leads to one leap month every three years plus 2/19 + // months left over. So this is correct as long as it does not predict a leap month in 2 years + // where the Hebrew calendar expects one in 3. + // + // The longest sequence of "three year leap months" in the Hebrew calendar + // is 4: year 8->11->14->17. In that time the error will accumulate to 6/19, which is not + // enough to create a "two year leap month" in our calculation. So this calculation cannot go past + // the actual cycle of the Hebrew calendar. 235 * years / 19 } diff --git a/components/calendar/src/calendar_arithmetic.rs b/components/calendar/src/calendar_arithmetic.rs index 0010e2a601f..0511a831b10 100644 --- a/components/calendar/src/calendar_arithmetic.rs +++ b/components/calendar/src/calendar_arithmetic.rs @@ -235,7 +235,11 @@ pub(crate) trait DateFieldsResolver: Calendar { 12 } - /// The minimum number of months over `years` years, starting from the given year. + /// A lower bound for the number of months in `years` years, starting from the beginning of this year. + /// + /// This may be equal to the number of months, but should never be larger. Ideally, + /// implementations should be close enough to the actual answer such that the error + /// is bounded by a constant with respect to `years`. /// /// The default impl is for non-lunisolar calendars with 12 months! #[inline]