Skip to main content

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 crate::util::matcher::tests::*;
100    use crate::util::time::tests::create_time;
101    use chrono_tz::America;
102    use googletest::prelude::*;
103
104    #[gtest]
105    fn test_subtracts_month_with_carried_year() {
106        let func = |mut calendar: Calendar| { calendar.subtract_month(1, &America::New_York) };
107        expect_that!(func(create_calendar(2024, 1, 29, 12, 0, 0)), time_eq(2023, 12, 29, 12, 0, 0));
108        expect_that!(func(create_calendar(2024, 1, 30, 12, 0, 0)), time_eq(2023, 12, 30, 12, 0, 0));
109        expect_that!(func(create_calendar(2024, 2, 1, 12, 0, 0)), time_eq(2024, 1, 1, 12, 0, 0));
110        expect_that!(func(create_calendar(2024, 2, 2, 12, 0, 0)), time_eq(2024, 1, 2, 12, 0, 0));
111    }
112
113    #[gtest]
114    fn test_subtracts_month_with_shorter_month() {
115        let func = |mut calendar: Calendar| { calendar.subtract_month(10, &America::New_York) };
116        expect_that!(func(create_calendar(2023, 12, 26, 12, 0, 0)), time_eq(2023, 2, 26, 12, 0, 0));
117        expect_that!(func(create_calendar(2023, 12, 27, 12, 0, 0)), time_eq(2023, 2, 27, 12, 0, 0));
118        expect_that!(func(create_calendar(2023, 12, 28, 12, 0, 0)), time_eq(2023, 2, 28, 12, 0, 0));
119        expect_that!(func(create_calendar(2023, 12, 29, 12, 0, 0)), time_eq(2023, 3, 1, 12, 0, 0));
120        expect_that!(func(create_calendar(2023, 12, 30, 12, 0, 0)), time_eq(2023, 3, 1, 12, 0, 0));
121        expect_that!(func(create_calendar(2023, 12, 31, 12, 0, 0)), time_eq(2023, 3, 1, 12, 0, 0));
122        expect_that!(func(create_calendar(2024, 1, 1, 12, 0, 0)), time_eq(2023, 3, 1, 12, 0, 0));
123        expect_that!(func(create_calendar(2024, 1, 2, 12, 0, 0)), time_eq(2023, 3, 2, 12, 0, 0));
124        expect_that!(func(create_calendar(2024, 1, 3, 12, 0, 0)), time_eq(2023, 3, 3, 12, 0, 0));
125    }
126
127    #[gtest]
128    fn test_subtracts_month_with_clocks_forward() {
129        let func = |mut calendar: Calendar| { calendar.subtract_month(1, &America::New_York) };
130        expect_that!(func(create_calendar(2024, 4, 10, 4, 30, 0)), time_eq(2024, 3, 10, 5, 30, 0)); // 00:30 EDT => 00:30 EST
131        expect_that!(func(create_calendar(2024, 4, 10, 5, 30, 0)), time_eq(2024, 3, 10, 6, 30, 0)); // 01:30 EDT => 01:30 EST
132        expect_that!(func(create_calendar(2024, 4, 10, 6, 30, 0)), time_eq(2024, 3, 10, 7, 30, 0)); // 02:30 EDT => 03:30 EDT
133        expect_that!(func(create_calendar(2024, 4, 10, 7, 30, 0)), time_eq(2024, 3, 10, 7, 30, 0)); // 03:30 EDT => 03:30 EDT
134        expect_that!(func(create_calendar(2024, 4, 10, 8, 30, 0)), time_eq(2024, 3, 10, 8, 30, 0)); // 04:30 EDT => 04:30 EDT
135    }
136
137    #[gtest]
138    fn test_subtracts_month_with_clocks_backward() {
139        let func = |mut calendar: Calendar| { calendar.subtract_month(1, &America::New_York) };
140        expect_that!(func(create_calendar(2024, 12, 3, 5, 30, 0)), time_eq(2024, 11, 3, 4, 30, 0)); // 00:30 EST => 00:30 EDT
141        expect_that!(func(create_calendar(2024, 12, 3, 6, 30, 0)), time_eq(2024, 11, 3, 6, 30, 0)); // 01:30 EST => 01:30 EDT
142        expect_that!(func(create_calendar(2024, 12, 3, 7, 30, 0)), time_eq(2024, 11, 3, 7, 30, 0)); // 02:30 EST => 02:30 EDT
143        expect_that!(func(create_calendar(2024, 12, 3, 8, 30, 0)), time_eq(2024, 11, 3, 8, 30, 0)); // 03:30 EST => 03:30 EST
144        expect_that!(func(create_calendar(2024, 12, 3, 9, 30, 0)), time_eq(2024, 11, 3, 9, 30, 0)); // 04:30 EST => 04:30 EST
145    }
146
147    fn create_calendar(
148        year: i32,
149        month: u32,
150        day: u32,
151        hour: u32,
152        minute: u32,
153        second: u32,
154    ) -> Calendar {
155        let time = create_time(year, month, day, hour, minute, second).unwrap();
156        Calendar::from_time(&time, &America::New_York)
157    }
158}