Skip to content

Commit 6899eda

Browse files
authored
Fix ISO weekday calculations in negative years (#4894)
1 parent afe6679 commit 6899eda

File tree

3 files changed

+64
-3
lines changed

3 files changed

+64
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- `icu_calendar`
1111
- New `DateTime::local_unix_epoch()` convenience constructor (https://github.com/unicode-org/icu4x/pull/4479)
1212
- Improved approximation for Persian calendrical calculations (https://github.com/unicode-org/icu4x/issues/4713)
13+
- Fix weekday calculations in negative ISO years (https://github.com/unicode-org/icu4x/pull/4894)
1314
- `icu_datetime`
1415
- `FormattedDateTime` and `FormattedZonedDateTime` now implement `Clone` and `Copy` (https://github.com/unicode-org/icu4x/pull/4476)
1516
- `icu_locid`

components/calendar/src/hebrew.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ impl<A: AsCalendar<Calendar = Hebrew>> DateTime<A> {
470470
mod tests {
471471

472472
use super::*;
473-
use crate::types::MonthCode;
473+
use crate::types::{Era, MonthCode};
474474
use calendrical_calculations::hebrew_keviyah::*;
475475

476476
// Sentinel value for Adar I
@@ -587,4 +587,17 @@ mod tests {
587587
let yi = YearInfo::compute_for(88369);
588588
assert_eq!(yi.keviyah.year_length(), 383);
589589
}
590+
591+
#[test]
592+
fn test_weekdays() {
593+
// https://github.com/unicode-org/icu4x/issues/4893
594+
let cal = Hebrew::new();
595+
let era = "am".parse::<Era>().unwrap();
596+
let month_code = "M01".parse::<MonthCode>().unwrap();
597+
let dt = cal.date_from_codes(era, 3760, month_code, 1).unwrap();
598+
599+
// Should be Saturday per:
600+
// https://www.hebcal.com/converter?hd=1&hm=Tishrei&hy=3760&h2g=1
601+
assert_eq!(6, cal.day_of_week(&dt) as usize);
602+
}
590603
}

components/calendar/src/iso.rs

+49-2
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,9 @@ impl Calendar for Iso {
135135

136136
// The days of the week are the same every 400 years
137137
// so we normalize to the nearest multiple of 400
138-
let years_since_400 = date.0.year % 400;
138+
let years_since_400 = date.0.year.rem_euclid(400);
139+
debug_assert!(years_since_400 >= 0); // rem_euclid returns positive numbers
140+
let years_since_400 = years_since_400 as u32;
139141
let leap_years_since_400 = years_since_400 / 4 - years_since_400 / 100;
140142
// The number of days to the current year
141143
// Can never cause an overflow because years_since_400 has a maximum value of 399.
@@ -169,7 +171,7 @@ impl Calendar for Iso {
169171
}
170172
};
171173
let january_1_2000 = 5; // Saturday
172-
let day_offset = (january_1_2000 + year_offset + month_offset + date.0.day as i32) % 7;
174+
let day_offset = (january_1_2000 + year_offset + month_offset + date.0.day as u32) % 7;
173175

174176
// We calculated in a zero-indexed fashion, but ISO specifies one-indexed
175177
types::IsoWeekday::from((day_offset + 1) as usize)
@@ -832,4 +834,49 @@ mod test {
832834
check(-1439, 1969, 12, 31, 0, 1);
833835
check(-2879, 1969, 12, 30, 0, 1);
834836
}
837+
838+
#[test]
839+
fn test_continuity_near_year_zero() {
840+
// https://github.com/unicode-org/icu4x/issues/4893
841+
const ONE_DAY_DURATION: DateDuration<Iso> = DateDuration {
842+
years: 0,
843+
months: 0,
844+
weeks: 0,
845+
days: 1,
846+
marker: core::marker::PhantomData,
847+
};
848+
849+
let mut iso_date = Date::try_new_iso_date(-10, 1, 1).unwrap();
850+
let mut rata_die = iso_date.to_fixed();
851+
let mut weekday = iso_date.day_of_week();
852+
853+
for _ in 0..(366 * 20) {
854+
let next_iso_date = iso_date.added(ONE_DAY_DURATION);
855+
let next_rata_die = next_iso_date.to_fixed();
856+
assert_eq!(
857+
next_rata_die,
858+
rata_die + 1,
859+
"{iso_date:?}..{next_iso_date:?}"
860+
);
861+
let next_weekday = next_iso_date.day_of_week();
862+
assert_eq!(
863+
(next_weekday as usize) % 7,
864+
(weekday as usize + 1) % 7,
865+
"{iso_date:?}..{next_iso_date:?}"
866+
);
867+
if iso_date.month().code.parsed().unwrap().0 == 2 && iso_date.day_of_month().0 == 28 {
868+
assert_eq!(next_iso_date.is_in_leap_year(), iso_date.is_in_leap_year());
869+
if iso_date.is_in_leap_year() {
870+
assert_eq!(next_iso_date.month().code.parsed().unwrap().0, 2);
871+
assert_eq!(next_iso_date.day_of_month().0, 29);
872+
} else {
873+
assert_eq!(next_iso_date.month().code.parsed().unwrap().0, 3);
874+
assert_eq!(next_iso_date.day_of_month().0, 1);
875+
}
876+
}
877+
iso_date = next_iso_date;
878+
rata_die = next_rata_die;
879+
weekday = next_weekday;
880+
}
881+
}
835882
}

0 commit comments

Comments
 (0)