iced_aw/core/
date.rs

1//! Helper functions for calculating dates
2
3use chrono::{Datelike, Duration, Local, NaiveDate};
4use std::fmt::Display;
5use std::sync::LazyLock;
6
7/// The date value
8#[derive(Clone, Copy, Debug)]
9pub struct Date {
10    /// The year value of the date.
11    pub year: i32,
12    /// The month value of the date (1 - 12).
13    pub month: u32,
14    /// The day value of the date (1 - 31).
15    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    /// Creates a new date from the current timestamp.
30    #[must_use]
31    pub fn today() -> Self {
32        let today = Local::now().naive_local().date();
33        today.into()
34    }
35
36    /// Creates a new date.
37    #[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/// # Panics
63/// Creates a date with the previous month based on the given date.
64/// panics if year, month or day doesnt exist.
65#[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/// # Panics
79/// Creates a date with the next month based on given date.
80/// panics if year, month or day doesnt exist.
81#[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/// # Panics
95/// Creates a date with the previous year based on the given date.
96// panics if year, month or day doesnt exist.
97#[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/// # Panics
106/// Creates a date with the next year based on the given date.
107// panics if year, month or day doesnt exist.
108#[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/// Calculates a date with the previous week based on the given date.
117#[must_use]
118pub fn pred_week(date: NaiveDate) -> NaiveDate {
119    date - Duration::days(7)
120}
121
122/// Calculates a date with the next week based on the given date.
123#[must_use]
124pub fn succ_week(date: NaiveDate) -> NaiveDate {
125    date + Duration::days(7)
126}
127
128/// Calculates a date with the previous day based on the given date.
129#[must_use]
130pub fn pred_day(date: NaiveDate) -> NaiveDate {
131    date - Duration::days(1)
132}
133
134/// Calculates a date with the next day based on the given date.
135#[must_use]
136pub fn succ_day(date: NaiveDate) -> NaiveDate {
137    date + Duration::days(1)
138}
139
140/// Specifies if the calculated day lays in the previous, same or next month of
141/// the date.
142#[derive(Debug, PartialEq, Eq)]
143pub enum IsInMonth {
144    /// The day lays in the previous month.
145    Previous,
146
147    /// The day lays in the same month.
148    Same,
149
150    /// The day lays in the next month.
151    Next,
152}
153
154/// # Panics
155/// Calculates the day number at the given position in the calendar table based
156/// on the given year and month.
157/// panics if year, month or day does not exist.
158#[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
184/// Checks if the given year is a leap year.
185const 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
193/// Gets the number of days the given month in the year has.
194const 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/// Gets the string representation of the date of the given month and date.
209#[must_use]
210pub fn date_as_string(date: NaiveDate) -> String {
211    date.format("%Y %B").to_string()
212}
213
214/// Gets the length of the longest month name.
215pub 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
242/// Gets the labels of the weekdays containing the first two characters of
243/// the weekdays.
244pub static WEEKDAY_LABELS: LazyLock<Vec<String>> = LazyLock::new(|| {
245    let days = [
246        // Monday
247        NaiveDate::from_ymd_opt(2020, 6, 1).expect("Year, Month or Day doesnt Exist"),
248        // Tuesday
249        NaiveDate::from_ymd_opt(2020, 6, 2).expect("Year, Month or Day doesnt Exist"),
250        // Wednesday
251        NaiveDate::from_ymd_opt(2020, 6, 3).expect("Year, Month or Day doesnt Exist"),
252        // Thursday
253        NaiveDate::from_ymd_opt(2020, 6, 4).expect("Year, Month or Day doesnt Exist"),
254        // Friday
255        NaiveDate::from_ymd_opt(2020, 6, 5).expect("Year, Month or Day doesnt Exist"),
256        // Saturday
257        NaiveDate::from_ymd_opt(2020, 6, 6).expect("Year, Month or Day doesnt Exist"),
258        // Sunday
259        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}