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
11impl 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); assert_calendar(func, 2024, 4, 10, 5, 30, 0, 2024, 3, 10, 6, 30, 0); assert_calendar(func, 2024, 4, 10, 6, 30, 0, 2024, 3, 10, 7, 30, 0); assert_calendar(func, 2024, 4, 10, 7, 30, 0, 2024, 3, 10, 7, 30, 0); assert_calendar(func, 2024, 4, 10, 8, 30, 0, 2024, 3, 10, 8, 30, 0); }
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); assert_calendar(func, 2024, 12, 3, 6, 30, 0, 2024, 11, 3, 6, 30, 0); assert_calendar(func, 2024, 12, 3, 7, 30, 0, 2024, 11, 3, 7, 30, 0); assert_calendar(func, 2024, 12, 3, 8, 30, 0, 2024, 11, 3, 8, 30, 0); assert_calendar(func, 2024, 12, 3, 9, 30, 0, 2024, 11, 3, 9, 30, 0); }
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}