datetime_string/
datetime.rs

1//! Datetime-related utilities.
2
3use core::fmt;
4
5/// Year-month-mday validation error.
6#[derive(Debug, Clone, Copy)]
7pub(crate) enum DateError {
8    /// Month is out of range.
9    MonthOutOfRange,
10    /// Day of month is out of range.
11    MdayOutOfRange,
12}
13
14impl fmt::Display for DateError {
15    #[inline]
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        let msg = match self {
18            Self::MonthOutOfRange => "Month is out of range",
19            Self::MdayOutOfRange => "Day of month is out of range",
20        };
21        f.write_str(msg)
22    }
23}
24
25/// Returns true iff the given year is leap year.
26#[inline]
27pub(crate) fn is_leap_year(year: u16) -> bool {
28    (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))
29}
30
31/// Validates the given year, month, and day of month.
32///
33/// Note that this function takes 0-based month value and 1-based year and mday.
34pub(crate) fn validate_ym0d(year: u16, month0: u8, mday: u8) -> Result<(), DateError> {
35    /// Maximum day of month value for normal (non-leap) year.
36    const NORMAL_MDAY_MAX: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
37
38    if month0 >= 12 {
39        return Err(DateError::MonthOutOfRange);
40    }
41
42    // Check if `mday` is 29 and return earily, rather than checking if `month1`
43    // is 2, because `mday != 29` is much more likely than `month1 == 2`.
44    if mday != 29 {
45        return if mday <= NORMAL_MDAY_MAX[month0 as usize] {
46            Ok(())
47        } else {
48            Err(DateError::MdayOutOfRange)
49        };
50    }
51
52    // For 29th day of month, check if it is leap year.
53    let mday_max = if is_leap_year(year) { 29 } else { 28 };
54
55    if mday <= mday_max {
56        Ok(())
57    } else {
58        Err(DateError::MdayOutOfRange)
59    }
60}
61
62/// Validates the given year, month, and day of month.
63///
64/// Note that this function takes 1-based year, month, and mday.
65#[inline]
66pub(crate) fn validate_ym1d(year: u16, month1: u8, mday: u8) -> Result<(), DateError> {
67    validate_ym0d(year, month1.wrapping_sub(1), mday)
68}