Skip to main content

chrono_node/calculation/
weekdays.rs

1//! upstream `calculation/weekdays.ts`.
2
3use crate::calculation::duration::{Duration, DurationUnit};
4use crate::core::components::{weekday, Component, ParsingComponents};
5use crate::core::js_date::JsDate;
6use crate::core::reference::ReferenceWithTimezone;
7
8/// The `"this" | "next" | "last"` modifier word.
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum WeekdayModifier {
11    This,
12    Next,
13    Last,
14}
15
16/// `createParsingComponentsAtWeekday(reference, weekday, modifier)`.
17pub fn create_parsing_components_at_weekday(
18    reference: ReferenceWithTimezone,
19    weekday: i64,
20    modifier: Option<WeekdayModifier>,
21) -> ParsingComponents {
22    let ref_date = reference.get_date_with_adjusted_timezone();
23    let days_to_weekday = get_days_to_weekday(ref_date, weekday, modifier);
24
25    let mut components = ParsingComponents::new(reference, &[]);
26    components.add_duration_as_implied(&mut Duration::from_pairs(&[(
27        DurationUnit::Day,
28        days_to_weekday as f64,
29    )]));
30    components.assign(Component::Weekday, weekday);
31    components
32}
33
34/// `getDaysToWeekday(refDate, weekday, modifier)`.
35pub fn get_days_to_weekday(ref_date: JsDate, weekday: i64, modifier: Option<WeekdayModifier>) -> i64 {
36    let ref_weekday = ref_date.get_day();
37    match modifier {
38        Some(WeekdayModifier::This) => get_days_forward_to_weekday(ref_date, weekday),
39        Some(WeekdayModifier::Last) => get_backward_days_to_weekday(ref_date, weekday),
40        Some(WeekdayModifier::Next) => {
41            // From Sunday, the next Sunday is 7 days later.
42            if ref_weekday == weekday::SUNDAY {
43                return if weekday == weekday::SUNDAY { 7 } else { weekday };
44            }
45            // From Saturday, the next Saturday is 7 days later, the next Sunday is 8 days later.
46            if ref_weekday == weekday::SATURDAY {
47                if weekday == weekday::SATURDAY {
48                    return 7;
49                }
50                if weekday == weekday::SUNDAY {
51                    return 8;
52                }
53                return 1 + weekday;
54            }
55            // From weekdays: if the weekday already passed this week, count forward to it;
56            // otherwise count forward within this week, then add another 7 days.
57            if weekday < ref_weekday && weekday != weekday::SUNDAY {
58                get_days_forward_to_weekday(ref_date, weekday)
59            } else {
60                get_days_forward_to_weekday(ref_date, weekday) + 7
61            }
62        }
63        None => get_days_to_weekday_closest(ref_date, weekday),
64    }
65}
66
67/// `getDaysToWeekdayClosest(refDate, weekday)`.
68pub fn get_days_to_weekday_closest(ref_date: JsDate, weekday: i64) -> i64 {
69    let backward = get_backward_days_to_weekday(ref_date, weekday);
70    let forward = get_days_forward_to_weekday(ref_date, weekday);
71    if forward < -backward {
72        forward
73    } else {
74        backward
75    }
76}
77
78/// `getDaysForwardToWeekday(refDate, weekday)`.
79pub fn get_days_forward_to_weekday(ref_date: JsDate, weekday: i64) -> i64 {
80    let mut forward = weekday - ref_date.get_day();
81    if forward < 0 {
82        forward += 7;
83    }
84    forward
85}
86
87/// `getBackwardDaysToWeekday(refDate, weekday)`.
88pub fn get_backward_days_to_weekday(ref_date: JsDate, weekday: i64) -> i64 {
89    let mut backward = weekday - ref_date.get_day();
90    if backward >= 0 {
91        backward -= 7;
92    }
93    backward
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    // 2012-08-09 is a Thursday.
101    fn thu() -> JsDate {
102        JsDate::parse_iso("2012-08-09T00:00:00.000Z").unwrap()
103    }
104
105    #[test]
106    fn closest_and_modifiers() {
107        assert_eq!(get_days_to_weekday(thu(), weekday::MONDAY, None), -3);
108        assert_eq!(get_days_to_weekday(thu(), weekday::SUNDAY, None), 3);
109        assert_eq!(get_days_to_weekday(thu(), weekday::THURSDAY, None), 0);
110        assert_eq!(
111            get_days_to_weekday(thu(), weekday::FRIDAY, Some(WeekdayModifier::Last)),
112            -6
113        );
114        assert_eq!(
115            get_days_to_weekday(thu(), weekday::FRIDAY, Some(WeekdayModifier::Next)),
116            8
117        );
118        assert_eq!(
119            get_days_to_weekday(thu(), weekday::MONDAY, Some(WeekdayModifier::Next)),
120            4
121        );
122        assert_eq!(
123            get_days_to_weekday(thu(), weekday::MONDAY, Some(WeekdayModifier::This)),
124            4
125        );
126    }
127}