1use chrono::{Datelike, Duration, Local, NaiveDate};
4use std::fmt::Display;
5use std::sync::LazyLock;
6
7#[derive(Clone, Copy, Debug)]
9pub struct Date {
10 pub year: i32,
12 pub month: u32,
14 pub day: u32,
16}
17
18impl Default for Date {
19 fn default() -> Self {
20 Self {
21 year: 2024,
22 month: 1,
23 day: 1,
24 }
25 }
26}
27
28impl Date {
29 #[must_use]
31 pub fn today() -> Self {
32 let today = Local::now().naive_local().date();
33 today.into()
34 }
35
36 #[must_use]
38 pub const fn from_ymd(year: i32, month: u32, day: u32) -> Self {
39 Self { year, month, day }
40 }
41}
42
43impl Display for Date {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
46 }
47}
48
49impl From<Date> for NaiveDate {
50 fn from(date: Date) -> Self {
51 Self::from_ymd_opt(date.year, date.month, date.day)
52 .expect("Year, Month or Day doesnt Exist")
53 }
54}
55
56impl From<NaiveDate> for Date {
57 fn from(date: NaiveDate) -> Self {
58 Self::from_ymd(date.year(), date.month(), date.day())
59 }
60}
61
62#[must_use]
66pub fn pred_month(date: NaiveDate) -> NaiveDate {
67 let (year, month) = if date.month() == 1 {
68 (date.year() - 1, 12)
69 } else {
70 (date.year(), date.month() - 1)
71 };
72
73 let day = date.day().min(num_days_of_month(year, month));
74
75 NaiveDate::from_ymd_opt(year, month, day).expect("Year, Month or Day doesnt Exist")
76}
77
78#[must_use]
82pub fn succ_month(date: NaiveDate) -> NaiveDate {
83 let (year, month) = if date.month() == 12 {
84 (date.year() + 1, 1)
85 } else {
86 (date.year(), date.month() + 1)
87 };
88
89 let day = date.day().min(num_days_of_month(year, month));
90
91 NaiveDate::from_ymd_opt(year, month, day).expect("Year, Month or Day doesnt Exist")
92}
93
94#[must_use]
98pub fn pred_year(date: NaiveDate) -> NaiveDate {
99 let year = date.year() - 1;
100 let day = date.day().min(num_days_of_month(year, date.month()));
101
102 NaiveDate::from_ymd_opt(year, date.month(), day).expect("Year, Month or Day doesnt Exist")
103}
104
105#[must_use]
109pub fn succ_year(date: NaiveDate) -> NaiveDate {
110 let year = date.year() + 1;
111 let day = date.day().min(num_days_of_month(year, date.month()));
112
113 NaiveDate::from_ymd_opt(year, date.month(), day).expect("Year, Month or Day doesnt Exist")
114}
115
116#[must_use]
118pub fn pred_week(date: NaiveDate) -> NaiveDate {
119 date - Duration::days(7)
120}
121
122#[must_use]
124pub fn succ_week(date: NaiveDate) -> NaiveDate {
125 date + Duration::days(7)
126}
127
128#[must_use]
130pub fn pred_day(date: NaiveDate) -> NaiveDate {
131 date - Duration::days(1)
132}
133
134#[must_use]
136pub fn succ_day(date: NaiveDate) -> NaiveDate {
137 date + Duration::days(1)
138}
139
140#[derive(Debug, PartialEq, Eq)]
143pub enum IsInMonth {
144 Previous,
146
147 Same,
149
150 Next,
152}
153
154#[must_use]
159pub fn position_to_day(x: usize, y: usize, year: i32, month: u32) -> (usize, IsInMonth) {
160 let (x, y) = (x as isize, y as isize);
161 let first_day =
162 NaiveDate::from_ymd_opt(year, month, 1).expect("Year, Month or Day doesnt Exist");
163 let day_of_week = first_day.weekday().num_days_from_monday() as isize;
164 let day_of_week = if day_of_week == 0 { 7 } else { day_of_week };
165
166 let day = (x + 7 * y) + 1 - day_of_week;
167
168 if day < 1 {
169 let last_month = first_day.pred_opt().unwrap_or_default();
170 (
171 (num_days_of_month(last_month.year(), last_month.month()) as isize + day) as usize,
172 IsInMonth::Previous,
173 )
174 } else if day > num_days_of_month(year, month) as isize {
175 (
176 (day - num_days_of_month(year, month) as isize) as usize,
177 IsInMonth::Next,
178 )
179 } else {
180 (day as usize, IsInMonth::Same)
181 }
182}
183
184const fn is_leap_year(year: i32) -> bool {
186 let mod4 = year % 4 == 0;
187 let mod100 = year % 100 == 0;
188 let mod400 = year % 400 == 0;
189
190 mod4 && (!mod100 || mod400)
191}
192
193const fn num_days_of_month(year: i32, month: u32) -> u32 {
195 match month {
196 4 | 6 | 9 | 11 => 30,
197 2 => {
198 if is_leap_year(year) {
199 29
200 } else {
201 28
202 }
203 }
204 _ => 31,
205 }
206}
207
208#[must_use]
210pub fn date_as_string(date: NaiveDate) -> String {
211 date.format("%Y %B").to_string()
212}
213
214pub static MAX_MONTH_STR_LEN: LazyLock<usize> = LazyLock::new(|| {
216 let months = [
217 NaiveDate::from_ymd_opt(0, 1, 1).expect("Year, Month or Day doesnt Exist"),
218 NaiveDate::from_ymd_opt(0, 2, 1).expect("Year, Month or Day doesnt Exist"),
219 NaiveDate::from_ymd_opt(0, 3, 1).expect("Year, Month or Day doesnt Exist"),
220 NaiveDate::from_ymd_opt(0, 4, 1).expect("Year, Month or Day doesnt Exist"),
221 NaiveDate::from_ymd_opt(0, 5, 1).expect("Year, Month or Day doesnt Exist"),
222 NaiveDate::from_ymd_opt(0, 6, 1).expect("Year, Month or Day doesnt Exist"),
223 NaiveDate::from_ymd_opt(0, 7, 1).expect("Year, Month or Day doesnt Exist"),
224 NaiveDate::from_ymd_opt(0, 8, 1).expect("Year, Month or Day doesnt Exist"),
225 NaiveDate::from_ymd_opt(0, 9, 1).expect("Year, Month or Day doesnt Exist"),
226 NaiveDate::from_ymd_opt(0, 10, 1).expect("Year, Month or Day doesnt Exist"),
227 NaiveDate::from_ymd_opt(0, 11, 1).expect("Year, Month or Day doesnt Exist"),
228 NaiveDate::from_ymd_opt(0, 12, 1).expect("Year, Month or Day doesnt Exist"),
229 ];
230
231 months
232 .iter()
233 .map(|m| {
234 let d = date_as_string(*m);
235 let (_, s) = d.split_once(' ').expect("Date must contain space");
236 s.len()
237 })
238 .max()
239 .expect("There should be a maximum element")
240});
241
242pub static WEEKDAY_LABELS: LazyLock<Vec<String>> = LazyLock::new(|| {
245 let days = [
246 NaiveDate::from_ymd_opt(2020, 6, 1).expect("Year, Month or Day doesnt Exist"),
248 NaiveDate::from_ymd_opt(2020, 6, 2).expect("Year, Month or Day doesnt Exist"),
250 NaiveDate::from_ymd_opt(2020, 6, 3).expect("Year, Month or Day doesnt Exist"),
252 NaiveDate::from_ymd_opt(2020, 6, 4).expect("Year, Month or Day doesnt Exist"),
254 NaiveDate::from_ymd_opt(2020, 6, 5).expect("Year, Month or Day doesnt Exist"),
256 NaiveDate::from_ymd_opt(2020, 6, 6).expect("Year, Month or Day doesnt Exist"),
258 NaiveDate::from_ymd_opt(2020, 6, 7).expect("Year, Month or Day doesnt Exist"),
260 ];
261
262 days.iter()
263 .map(|d| d.format("%a").to_string())
264 .map(|s| s[0..2].to_owned())
265 .collect()
266});
267
268#[cfg(test)]
269mod tests {
270 use chrono::NaiveDate;
271
272 use super::{
273 IsInMonth, is_leap_year, num_days_of_month, position_to_day, pred_month, pred_year,
274 succ_month, succ_year,
275 };
276
277 #[test]
278 fn pred_month_test() {
279 let date = NaiveDate::from_ymd_opt(2020, 5, 6).expect("Year, Month or Day doesnt Exist");
280 let result = pred_month(date);
281 let expected =
282 NaiveDate::from_ymd_opt(2020, 4, 6).expect("Year, Month or Day doesnt Exist");
283 assert_eq!(result, expected);
284
285 let date = NaiveDate::from_ymd_opt(2020, 1, 24).expect("Year, Month or Day doesnt Exist");
286 let result = pred_month(date);
287 let expected =
288 NaiveDate::from_ymd_opt(2019, 12, 24).expect("Year, Month or Day doesnt Exist");
289 assert_eq!(result, expected);
290
291 let date = NaiveDate::from_ymd_opt(2020, 3, 31).expect("Year, Month or Day doesnt Exist");
292 let result = pred_month(date);
293 let expected =
294 NaiveDate::from_ymd_opt(2020, 2, 29).expect("Year, Month or Day doesnt Exist");
295 assert_eq!(result, expected);
296 }
297
298 #[test]
299 fn succ_month_test() {
300 let date = NaiveDate::from_ymd_opt(2020, 5, 6).expect("Year, Month or Day doesnt Exist");
301 let result = succ_month(date);
302 let expected =
303 NaiveDate::from_ymd_opt(2020, 6, 6).expect("Year, Month or Day doesnt Exist");
304 assert_eq!(result, expected);
305
306 let date = NaiveDate::from_ymd_opt(2019, 12, 24).expect("Year, Month or Day doesnt Exist");
307 let result = succ_month(date);
308 let expected =
309 NaiveDate::from_ymd_opt(2020, 1, 24).expect("Year, Month or Day doesnt Exist");
310 assert_eq!(result, expected);
311
312 let date = NaiveDate::from_ymd_opt(2020, 1, 31).expect("Year, Month or Day doesnt Exist");
313 let result = succ_month(date);
314 let expected =
315 NaiveDate::from_ymd_opt(2020, 2, 29).expect("Year, Month or Day doesnt Exist");
316 assert_eq!(result, expected);
317 }
318
319 #[test]
320 fn pred_year_test() {
321 let date = NaiveDate::from_ymd_opt(2020, 5, 6).expect("Year, Month or Day doesnt Exist");
322 let result = pred_year(date);
323 let expected =
324 NaiveDate::from_ymd_opt(2019, 5, 6).expect("Year, Month or Day doesnt Exist");
325 assert_eq!(result, expected);
326
327 let date = NaiveDate::from_ymd_opt(2020, 2, 29).expect("Year, Month or Day doesnt Exist");
328 let result = pred_year(date);
329 let expected =
330 NaiveDate::from_ymd_opt(2019, 2, 28).expect("Year, Month or Day doesnt Exist");
331 assert_eq!(result, expected);
332
333 let date = NaiveDate::from_ymd_opt(2021, 2, 28).expect("Year, Month or Day doesnt Exist");
334 let result = pred_year(date);
335 let expected =
336 NaiveDate::from_ymd_opt(2020, 2, 28).expect("Year, Month or Day doesnt Exist");
337 assert_eq!(result, expected);
338 }
339
340 #[test]
341 fn succ_year_test() {
342 let date = NaiveDate::from_ymd_opt(2020, 5, 6).expect("Year, Month or Day doesnt Exist");
343 let result = succ_year(date);
344 let expected =
345 NaiveDate::from_ymd_opt(2021, 5, 6).expect("Year, Month or Day doesnt Exist");
346 assert_eq!(result, expected);
347
348 let date = NaiveDate::from_ymd_opt(2020, 2, 29).expect("Year, Month or Day doesnt Exist");
349 let result = succ_year(date);
350 let expected =
351 NaiveDate::from_ymd_opt(2021, 2, 28).expect("Year, Month or Day doesnt Exist");
352 assert_eq!(result, expected);
353
354 let date = NaiveDate::from_ymd_opt(2019, 2, 28).expect("Year, Month or Day doesnt Exist");
355 let result = succ_year(date);
356 let expected =
357 NaiveDate::from_ymd_opt(2020, 2, 28).expect("Year, Month or Day doesnt Exist");
358 assert_eq!(result, expected);
359 }
360
361 #[allow(clippy::shadow_unrelated)]
362 #[test]
363 fn position_to_day_test() {
364 let (day, is_in_month) = position_to_day(0, 0, 2020, 12);
365 assert_eq!(day, 30);
366 assert_eq!(is_in_month, IsInMonth::Previous);
367
368 let (day, is_in_month) = position_to_day(1, 0, 2020, 12);
369 assert_eq!(day, 1);
370 assert_eq!(is_in_month, IsInMonth::Same);
371
372 let (day, is_in_month) = position_to_day(3, 4, 2020, 12);
373 assert_eq!(day, 31);
374 assert_eq!(is_in_month, IsInMonth::Same);
375
376 let (day, is_in_month) = position_to_day(6, 5, 2020, 12);
377 assert_eq!(day, 10);
378 assert_eq!(is_in_month, IsInMonth::Next);
379
380 let (day, is_in_month) = position_to_day(0, 0, 2020, 11);
381 assert_eq!(day, 26);
382 assert_eq!(is_in_month, IsInMonth::Previous);
383
384 let (day, is_in_month) = position_to_day(6, 0, 2020, 11);
385 assert_eq!(day, 1);
386 assert_eq!(is_in_month, IsInMonth::Same);
387
388 let (day, is_in_month) = position_to_day(0, 5, 2020, 11);
389 assert_eq!(day, 30);
390 assert_eq!(is_in_month, IsInMonth::Same);
391
392 let (day, is_in_month) = position_to_day(6, 5, 2020, 11);
393 assert_eq!(day, 6);
394 assert_eq!(is_in_month, IsInMonth::Next);
395
396 let (day, is_in_month) = position_to_day(0, 0, 2021, 2);
397 assert_eq!(day, 25);
398 assert_eq!(is_in_month, IsInMonth::Previous);
399
400 let (day, is_in_month) = position_to_day(0, 1, 2021, 2);
401 assert_eq!(day, 1);
402 assert_eq!(is_in_month, IsInMonth::Same);
403
404 let (day, is_in_month) = position_to_day(6, 4, 2021, 2);
405 assert_eq!(day, 28);
406 assert_eq!(is_in_month, IsInMonth::Same);
407
408 let (day, is_in_month) = position_to_day(0, 5, 2021, 2);
409 assert_eq!(day, 1);
410 assert_eq!(is_in_month, IsInMonth::Next);
411 }
412
413 #[test]
414 fn is_leap_year_test() {
415 assert!(is_leap_year(2020));
416 assert!(!is_leap_year(2019));
417 assert!(!is_leap_year(2021));
418 assert!(is_leap_year(2000));
419 assert!(!is_leap_year(1000));
420 }
421
422 #[test]
423 fn num_days_of_month_test() {
424 assert_eq!(num_days_of_month(2020, 1), 31);
425 assert_eq!(num_days_of_month(2020, 2), 29);
426 assert_eq!(num_days_of_month(2019, 2), 28);
427 assert_eq!(num_days_of_month(2020, 3), 31);
428 assert_eq!(num_days_of_month(2020, 4), 30);
429 assert_eq!(num_days_of_month(2020, 5), 31);
430 assert_eq!(num_days_of_month(2020, 6), 30);
431 assert_eq!(num_days_of_month(2020, 7), 31);
432 assert_eq!(num_days_of_month(2020, 8), 31);
433 assert_eq!(num_days_of_month(2020, 9), 30);
434 assert_eq!(num_days_of_month(2020, 10), 31);
435 assert_eq!(num_days_of_month(2020, 11), 30);
436 assert_eq!(num_days_of_month(2020, 12), 31);
437 }
438}