Skip to content

Commit f67645a

Browse files
Unify day-of-week calculation (#6368)
Weekdays are not calender-specific, and the current code is overly complex.
1 parent 17517bb commit f67645a

File tree

13 files changed

+54
-113
lines changed

13 files changed

+54
-113
lines changed

components/calendar/src/cal/coptic.rs

-4
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,6 @@ impl Calendar for Coptic {
131131
date.0.days_in_month()
132132
}
133133

134-
fn day_of_week(&self, date: &Self::DateInner) -> types::Weekday {
135-
Iso.day_of_week(Coptic.date_to_iso(date).inner())
136-
}
137-
138134
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
139135
date.0.offset_date(offset, &());
140136
}

components/calendar/src/cal/dangi.rs

-4
Original file line numberDiff line numberDiff line change
@@ -242,10 +242,6 @@ impl Calendar for Dangi {
242242
date.0 .0.day_of_year()
243243
}
244244

245-
fn day_of_week(&self, date: &Self::DateInner) -> crate::types::Weekday {
246-
self.date_to_iso(date).day_of_week()
247-
}
248-
249245
fn any_calendar_kind(&self) -> Option<crate::AnyCalendarKind> {
250246
Some(crate::any_calendar::IntoAnyCalendar::kind(self))
251247
}

components/calendar/src/cal/ethiopian.rs

-4
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,6 @@ impl Calendar for Ethiopian {
158158
date.0.days_in_month()
159159
}
160160

161-
fn day_of_week(&self, date: &Self::DateInner) -> types::Weekday {
162-
Iso.day_of_week(self.date_to_iso(date).inner())
163-
}
164-
165161
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
166162
date.0.offset_date(offset, &());
167163
}

components/calendar/src/cal/hebrew.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -497,10 +497,10 @@ mod tests {
497497
let cal = Hebrew::new();
498498
let era = "am";
499499
let month_code = MonthCode(tinystr!(4, "M01"));
500-
let dt = cal.date_from_codes(Some(era), 3760, month_code, 1).unwrap();
500+
let dt = Date::try_new_from_codes(Some(era), 3760, month_code, 1, cal).unwrap();
501501

502502
// Should be Saturday per:
503503
// https://www.hebcal.com/converter?hd=1&hm=Tishrei&hy=3760&h2g=1
504-
assert_eq!(6, cal.day_of_week(&dt) as usize);
504+
assert_eq!(6, dt.day_of_week() as usize);
505505
}
506506
}

components/calendar/src/cal/hijri.rs

-12
Original file line numberDiff line numberDiff line change
@@ -484,10 +484,6 @@ impl Calendar for HijriObservational {
484484
date.0.days_in_month()
485485
}
486486

487-
fn day_of_week(&self, date: &Self::DateInner) -> types::Weekday {
488-
Iso.day_of_week(self.date_to_iso(date).inner())
489-
}
490-
491487
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
492488
date.0.offset_date(offset, &self.precomputed_data())
493489
}
@@ -839,10 +835,6 @@ impl Calendar for HijriCivil {
839835
date.0.days_in_month()
840836
}
841837

842-
fn day_of_week(&self, date: &Self::DateInner) -> types::Weekday {
843-
Iso.day_of_week(self.date_to_iso(date).inner())
844-
}
845-
846838
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
847839
date.0.offset_date(offset, &())
848840
}
@@ -1020,10 +1012,6 @@ impl Calendar for HijriTabular {
10201012
date.0.days_in_month()
10211013
}
10221014

1023-
fn day_of_week(&self, date: &Self::DateInner) -> types::Weekday {
1024-
Iso.day_of_week(self.date_to_iso(date).inner())
1025-
}
1026-
10271015
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
10281016
date.0.offset_date(offset, &())
10291017
}

components/calendar/src/cal/indian.rs

-4
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,6 @@ impl Calendar for Indian {
156156
date.0.days_in_month()
157157
}
158158

159-
fn day_of_week(&self, date: &Self::DateInner) -> types::Weekday {
160-
Iso.day_of_week(Indian.date_to_iso(date).inner())
161-
}
162-
163159
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
164160
date.0.offset_date(offset, &());
165161
}

components/calendar/src/cal/iso.rs

-48
Original file line numberDiff line numberDiff line change
@@ -114,54 +114,6 @@ impl Calendar for Iso {
114114
date.0.days_in_month()
115115
}
116116

117-
fn day_of_week(&self, date: &Self::DateInner) -> types::Weekday {
118-
// For the purposes of the calculation here, Monday is 0, Sunday is 6
119-
// ISO has Monday=1, Sunday=7, which we transform in the last step
120-
121-
// The days of the week are the same every 400 years
122-
// so we normalize to the nearest multiple of 400
123-
let years_since_400 = date.0.year.rem_euclid(400);
124-
debug_assert!(years_since_400 >= 0); // rem_euclid returns positive numbers
125-
let years_since_400 = years_since_400 as u32;
126-
let leap_years_since_400 = years_since_400 / 4 - years_since_400 / 100;
127-
// The number of days to the current year
128-
// Can never cause an overflow because years_since_400 has a maximum value of 399.
129-
let days_to_current_year = 365 * years_since_400 + leap_years_since_400;
130-
// The weekday offset from January 1 this year and January 1 2000
131-
let year_offset = days_to_current_year % 7;
132-
133-
// Corresponding months from
134-
// https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Corresponding_months
135-
let month_offset = if Self::is_leap_year(date.0.year, ()) {
136-
match date.0.month {
137-
10 => 0,
138-
5 => 1,
139-
2 | 8 => 2,
140-
3 | 11 => 3,
141-
6 => 4,
142-
9 | 12 => 5,
143-
1 | 4 | 7 => 6,
144-
_ => unreachable!(),
145-
}
146-
} else {
147-
match date.0.month {
148-
1 | 10 => 0,
149-
5 => 1,
150-
8 => 2,
151-
2 | 3 | 11 => 3,
152-
6 => 4,
153-
9 | 12 => 5,
154-
4 | 7 => 6,
155-
_ => unreachable!(),
156-
}
157-
};
158-
let january_1_2000 = 5; // Saturday
159-
let day_offset = (january_1_2000 + year_offset + month_offset + date.0.day as u32) % 7;
160-
161-
// We calculated in a zero-indexed fashion, but ISO specifies one-indexed
162-
types::Weekday::from((day_offset + 1) as usize)
163-
}
164-
165117
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
166118
date.0.offset_date(offset, &());
167119
}

components/calendar/src/cal/julian.rs

-4
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,6 @@ impl Calendar for Julian {
122122
date.0.days_in_month()
123123
}
124124

125-
fn day_of_week(&self, date: &Self::DateInner) -> types::Weekday {
126-
Iso.day_of_week(Julian.date_to_iso(date).inner())
127-
}
128-
129125
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
130126
date.0.offset_date(offset, &());
131127
}

components/calendar/src/cal/persian.rs

-4
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,6 @@ impl Calendar for Persian {
123123
date.0.days_in_month()
124124
}
125125

126-
fn day_of_week(&self, date: &Self::DateInner) -> types::Weekday {
127-
Iso.day_of_week(self.date_to_iso(date).inner())
128-
}
129-
130126
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
131127
date.0.offset_date(offset, &())
132128
}

components/calendar/src/calendar.rs

-4
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,6 @@ pub trait Calendar {
4747
/// Count the number of days in a given month, specified by providing a date
4848
/// from that year/month
4949
fn days_in_month(&self, date: &Self::DateInner) -> u8;
50-
/// Calculate the day of the week and return it
51-
fn day_of_week(&self, date: &Self::DateInner) -> types::Weekday {
52-
self.date_to_iso(date).day_of_week()
53-
}
5450
// fn week_of_year(&self, date: &Self::DateInner) -> u8;
5551

5652
#[doc(hidden)] // unstable

components/calendar/src/date.rs

+21-3
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,9 @@ impl<A: AsCalendar> Date<A> {
168168
}
169169

170170
/// The day of the week for this date
171-
///
172-
/// Monday is 1, Sunday is 7, according to ISO
173171
#[inline]
174172
pub fn day_of_week(&self) -> types::Weekday {
175-
self.calendar.as_calendar().day_of_week(self.inner())
173+
Iso::to_fixed(self.to_iso()).into()
176174
}
177175

178176
/// Add a `duration` to this date, mutating it
@@ -449,6 +447,7 @@ impl<A> Copy for Date<A> where A: AsCalendar + Copy {}
449447
#[cfg(test)]
450448
mod tests {
451449
use super::*;
450+
use crate::types::Weekday;
452451

453452
#[test]
454453
fn test_ord() {
@@ -478,4 +477,23 @@ mod tests {
478477
}
479478
}
480479
}
480+
481+
#[test]
482+
fn test_day_of_week() {
483+
// June 23, 2021 is a Wednesday
484+
assert_eq!(
485+
Date::try_new_iso(2021, 6, 23).unwrap().day_of_week(),
486+
Weekday::Wednesday,
487+
);
488+
// Feb 2, 1983 was a Wednesday
489+
assert_eq!(
490+
Date::try_new_iso(1983, 2, 2).unwrap().day_of_week(),
491+
Weekday::Wednesday,
492+
);
493+
// Jan 21, 2021 was a Tuesday
494+
assert_eq!(
495+
Date::try_new_iso(2020, 1, 21).unwrap().day_of_week(),
496+
Weekday::Tuesday,
497+
);
498+
}
481499
}

components/calendar/src/types.rs

+24-16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
//! This module contains various types used by `icu_calendar` and `icu::datetime`
66
7+
use calendrical_calculations::rata_die::RataDie;
78
use core::fmt;
89
use core::num::NonZeroU8;
910
use tinystr::TinyAsciiStr;
@@ -420,23 +421,13 @@ pub enum Weekday {
420421
Sunday,
421422
}
422423

423-
impl From<usize> for Weekday {
424-
/// Convert from an ISO-8601 weekday number to an [`Weekday`] enum. 0 is automatically converted
425-
/// to 7 (Sunday). If the number is out of range, it is interpreted modulo 7.
426-
///
427-
/// # Examples
428-
///
429-
/// ```
430-
/// use icu::calendar::types::Weekday;
431-
///
432-
/// assert_eq!(Weekday::Sunday, Weekday::from(0));
433-
/// assert_eq!(Weekday::Monday, Weekday::from(1));
434-
/// assert_eq!(Weekday::Sunday, Weekday::from(7));
435-
/// assert_eq!(Weekday::Monday, Weekday::from(8));
436-
/// ```
437-
fn from(input: usize) -> Self {
424+
// RD 0 is a Sunday
425+
const SUNDAY: RataDie = RataDie::new(0);
426+
427+
impl From<RataDie> for Weekday {
428+
fn from(value: RataDie) -> Self {
438429
use Weekday::*;
439-
match input % 7 {
430+
match (value - SUNDAY).rem_euclid(7) {
440431
0 => Sunday,
441432
1 => Monday,
442433
2 => Tuesday,
@@ -450,6 +441,23 @@ impl From<usize> for Weekday {
450441
}
451442

452443
impl Weekday {
444+
/// Convert from an ISO-8601 weekday number to an [`Weekday`] enum. 0 is automatically converted
445+
/// to 7 (Sunday). If the number is out of range, it is interpreted modulo 7.
446+
///
447+
/// # Examples
448+
///
449+
/// ```
450+
/// use icu::calendar::types::Weekday;
451+
///
452+
/// assert_eq!(Weekday::Sunday, Weekday::from_days_since_sunday(0));
453+
/// assert_eq!(Weekday::Monday, Weekday::from_days_since_sunday(1));
454+
/// assert_eq!(Weekday::Sunday, Weekday::from_days_since_sunday(7));
455+
/// assert_eq!(Weekday::Monday, Weekday::from_days_since_sunday(8));
456+
/// ```
457+
pub fn from_days_since_sunday(input: isize) -> Self {
458+
(SUNDAY + input as i64).into()
459+
}
460+
453461
/// Returns the day after the current day.
454462
pub(crate) fn next_day(self) -> Weekday {
455463
use Weekday::*;

components/calendar/src/week.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ impl WeekCalculator {
126126
/// Returns the weekday that's `num_days` after `weekday`.
127127
fn add_to_weekday(weekday: Weekday, num_days: i32) -> Weekday {
128128
let new_weekday = (7 + (weekday as i32) + (num_days % 7)) % 7;
129-
Weekday::from(new_weekday as usize)
129+
Weekday::from_days_since_sunday(new_weekday as isize)
130130
}
131131

132132
/// Which year or month that a calendar assigns a week to relative to the year/month
@@ -409,13 +409,16 @@ mod tests {
409409
for min_week_days in 1..7 {
410410
for start_of_week in 1..7 {
411411
let calendar = WeekCalculator {
412-
first_weekday: Weekday::from(start_of_week),
412+
first_weekday: Weekday::from_days_since_sunday(start_of_week),
413413
min_week_days,
414414
};
415415
for unit_duration in super::MIN_UNIT_DAYS..400 {
416416
for start_of_unit in 1..7 {
417-
let unit =
418-
UnitInfo::new(Weekday::from(start_of_unit), unit_duration).unwrap();
417+
let unit = UnitInfo::new(
418+
Weekday::from_days_since_sunday(start_of_unit),
419+
unit_duration,
420+
)
421+
.unwrap();
419422
let expected = classify_days_of_unit(calendar, &unit);
420423
for (index, expected_week_of) in expected.iter().enumerate() {
421424
let day = index + 1;

0 commit comments

Comments
 (0)