ex_cli/util/
calendar.rs

1use chrono::{DateTime, Datelike, NaiveDate, NaiveTime, TimeDelta, TimeZone, Utc};
2use std::ops::AddAssign;
3
4pub struct Calendar {
5    year: i32,
6    month: u32,
7    day: u32,
8    time: NaiveTime,
9}
10
11// noinspection RsLift
12impl Calendar {
13    pub fn from_time<Tz: TimeZone>(time: &DateTime<Utc>, zone: &Tz) -> Self {
14        let time = time.with_timezone(zone);
15        let date = time.date_naive();
16        let time = time.time();
17        Self { year: date.year(), month: date.month(), day: date.day(), time }
18    }
19
20    pub fn num_months_to(&self, calendar: &Self) -> Option<i64> {
21        let years = calendar.year as i64 - self.year as i64;
22        let mut months = calendar.month as i64 - self.month as i64 + years * 12;
23        if (calendar.day, calendar.time) < (self.day, self.time) {
24            months -= 1;
25        }
26        if months > 0 {
27            Some(months)
28        } else {
29            None
30        }
31    }
32
33    pub fn num_years_to(&self, calendar: &Self) -> Option<i64> {
34        let mut years = calendar.year as i64 - self.year as i64;
35        if (calendar.month, calendar.day, calendar.time) < (self.month, self.day, self.time) {
36            years -= 1;
37        }
38        if years > 0 {
39            Some(years)
40        } else {
41            None
42        }
43    }
44
45    pub fn subtract_month<Tz: TimeZone>(&mut self, count: i64, zone: &Tz) -> DateTime<Utc> {
46        for _ in 0..count {
47            self.month -= 1;
48            if self.month == 0 {
49                self.month = 12;
50                self.year -= 1;
51            }
52        }
53        self.create_time(zone)
54    }
55
56    pub fn subtract_year<Tz: TimeZone>(&mut self, count: i64, zone: &Tz) -> DateTime<Utc> {
57        for _ in 0..count {
58            self.year -= 1;
59        }
60        self.create_time(zone)
61    }
62
63    fn create_time<Tz: TimeZone>(&mut self, zone: &Tz) -> DateTime<Utc> {
64        loop {
65            if let Some(date) = NaiveDate::from_ymd_opt(self.year, self.month, self.day) {
66                let time = date.and_time(self.time);
67                let time = time.and_local_timezone(zone.clone());
68                if let Some(time) = time.latest() {
69                    return time.to_utc();
70                } else {
71                    self.increment_hour();
72                }
73            } else {
74                self.increment_day();
75            }
76        }
77    }
78
79    fn increment_hour(&mut self) {
80        self.time.add_assign(TimeDelta::hours(1));
81    }
82
83    fn increment_day(&mut self) {
84        self.day += 1;
85        if self.day > 31 {
86            self.day = 1;
87            self.month += 1;
88            if self.month > 12 {
89                self.month = 1;
90                self.year += 1;
91            }
92        }
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use crate::util::calendar::Calendar;
99    use chrono::{DateTime, Datelike, TimeZone, Timelike, Utc};
100    use chrono_tz::America;
101    use pretty_assertions::assert_eq;
102
103    #[test]
104    fn test_subtracts_month_with_carried_year() {
105        let func = |calendar: &mut Calendar| { calendar.subtract_month(1, &America::New_York) };
106        assert_calendar(func, 2024, 1, 29, 12, 0, 0, 2023, 12, 29, 12, 0, 0);
107        assert_calendar(func, 2024, 1, 30, 12, 0, 0, 2023, 12, 30, 12, 0, 0);
108        assert_calendar(func, 2024, 2, 1, 12, 0, 0, 2024, 1, 1, 12, 0, 0);
109        assert_calendar(func, 2024, 2, 2, 12, 0, 0, 2024, 1, 2, 12, 0, 0);
110    }
111
112    #[test]
113    fn test_subtracts_month_with_shorter_month() {
114        let func = |calendar: &mut Calendar| { calendar.subtract_month(10, &America::New_York) };
115        assert_calendar(func, 2023, 12, 26, 12, 0, 0, 2023, 2, 26, 12, 0, 0);
116        assert_calendar(func, 2023, 12, 27, 12, 0, 0, 2023, 2, 27, 12, 0, 0);
117        assert_calendar(func, 2023, 12, 28, 12, 0, 0, 2023, 2, 28, 12, 0, 0);
118        assert_calendar(func, 2023, 12, 29, 12, 0, 0, 2023, 3, 1, 12, 0, 0);
119        assert_calendar(func, 2023, 12, 30, 12, 0, 0, 2023, 3, 1, 12, 0, 0);
120        assert_calendar(func, 2023, 12, 31, 12, 0, 0, 2023, 3, 1, 12, 0, 0);
121        assert_calendar(func, 2024, 1, 1, 12, 0, 0, 2023, 3, 1, 12, 0, 0);
122        assert_calendar(func, 2024, 1, 2, 12, 0, 0, 2023, 3, 2, 12, 0, 0);
123        assert_calendar(func, 2024, 1, 3, 12, 0, 0, 2023, 3, 3, 12, 0, 0);
124    }
125
126    #[test]
127    fn test_subtracts_month_with_clocks_forward() {
128        let func = |calendar: &mut Calendar| { calendar.subtract_month(1, &America::New_York) };
129        assert_calendar(func, 2024, 4, 10, 4, 30, 0, 2024, 3, 10, 5, 30, 0); // 00:30 EDT => 00:30 EST
130        assert_calendar(func, 2024, 4, 10, 5, 30, 0, 2024, 3, 10, 6, 30, 0); // 01:30 EDT => 01:30 EST
131        assert_calendar(func, 2024, 4, 10, 6, 30, 0, 2024, 3, 10, 7, 30, 0); // 02:30 EDT => 03:30 EDT
132        assert_calendar(func, 2024, 4, 10, 7, 30, 0, 2024, 3, 10, 7, 30, 0); // 03:30 EDT => 03:30 EDT
133        assert_calendar(func, 2024, 4, 10, 8, 30, 0, 2024, 3, 10, 8, 30, 0); // 04:30 EDT => 04:30 EDT
134    }
135
136    #[test]
137    fn test_subtracts_month_with_clocks_backward() {
138        let func = |calendar: &mut Calendar| { calendar.subtract_month(1, &America::New_York) };
139        assert_calendar(func, 2024, 12, 3, 5, 30, 0, 2024, 11, 3, 4, 30, 0); // 00:30 EST => 00:30 EDT
140        assert_calendar(func, 2024, 12, 3, 6, 30, 0, 2024, 11, 3, 6, 30, 0); // 01:30 EST => 01:30 EDT
141        assert_calendar(func, 2024, 12, 3, 7, 30, 0, 2024, 11, 3, 7, 30, 0); // 02:30 EST => 02:30 EDT
142        assert_calendar(func, 2024, 12, 3, 8, 30, 0, 2024, 11, 3, 8, 30, 0); // 03:30 EST => 03:30 EST
143        assert_calendar(func, 2024, 12, 3, 9, 30, 0, 2024, 11, 3, 9, 30, 0); // 04:30 EST => 04:30 EST
144    }
145
146    fn assert_calendar<F>(
147        mut func: F,
148        initial_year: i32,
149        initial_month: u32,
150        initial_day: u32,
151        initial_hour: u32,
152        initial_min: u32,
153        initial_sec: u32,
154        expect_year: i32,
155        expect_month: u32,
156        expect_day: u32,
157        expect_hour: u32,
158        expect_min: u32,
159        expect_sec: u32,
160    ) where F: FnMut(&mut Calendar) -> DateTime<Utc> {
161        let time = Utc.with_ymd_and_hms(
162            initial_year,
163            initial_month,
164            initial_day,
165            initial_hour,
166            initial_min,
167            initial_sec,
168        ).unwrap();
169        let mut calendar = Calendar::from_time(&time, &America::New_York);
170        let time = func(&mut calendar);
171        assert_eq!(expect_year, time.year(), "year");
172        assert_eq!(expect_month, time.month(), "month");
173        assert_eq!(expect_day, time.day(), "day");
174        assert_eq!(expect_hour, time.hour(), "hour");
175        assert_eq!(expect_min, time.minute(), "minute");
176        assert_eq!(expect_sec, time.second(), "second");
177    }
178}