version_check/
date.rs

1use std::fmt;
2
3/// Release date including year, month, and day.
4// Internal storage is: y[31..9] | m[8..5] | d[5...0].
5#[derive(Debug, PartialEq, Eq, Copy, Clone, PartialOrd, Ord)]
6pub struct Date(u32);
7
8impl Date {
9    /// Reads the release date of the running compiler. If it cannot be
10    /// determined (see the [top-level documentation](crate)), returns `None`.
11    ///
12    /// # Example
13    ///
14    /// ```rust
15    /// use version_check::Date;
16    ///
17    /// match Date::read() {
18    ///     Some(d) => format!("The release date is: {}", d),
19    ///     None => format!("Failed to read the release date.")
20    /// };
21    /// ```
22    pub fn read() -> Option<Date> {
23        ::get_version_and_date()
24            .and_then(|(_, date)| date)
25            .and_then(|date| Date::parse(&date))
26    }
27
28    /// Parse a release date of the form `%Y-%m-%d`. Returns `None` if `date` is
29    /// not in `%Y-%m-%d` format.
30    ///
31    /// # Example
32    ///
33    /// ```rust
34    /// use version_check::Date;
35    ///
36    /// let date = Date::parse("2016-04-20").unwrap();
37    ///
38    /// assert!(date.at_least("2016-01-10"));
39    /// assert!(date.at_most("2016-04-20"));
40    /// assert!(date.exactly("2016-04-20"));
41    ///
42    /// assert!(Date::parse("2021-12-31").unwrap().exactly("2021-12-31"));
43    ///
44    /// assert!(Date::parse("March 13, 2018").is_none());
45    /// assert!(Date::parse("1-2-3-4-5").is_none());
46    /// assert!(Date::parse("2020-300-23120").is_none());
47    /// assert!(Date::parse("2020-12-12 1").is_none());
48    /// assert!(Date::parse("2020-10").is_none());
49    /// assert!(Date::parse("2020").is_none());
50    /// ```
51    pub fn parse(date: &str) -> Option<Date> {
52        let mut ymd = [0u16; 3];
53        for (i, split) in date.split('-').map(|s| s.parse::<u16>()).enumerate() {
54            ymd[i] = match (i, split) {
55                (3, _) | (_, Err(_)) => return None,
56                (_, Ok(v)) => v,
57            };
58        }
59
60        let (year, month, day) = (ymd[0], ymd[1], ymd[2]);
61        if year == 0 || month == 0 || month > 12 || day == 0 || day > 31 {
62            return None;
63        }
64
65        Some(Date::from_ymd(year, month as u8, day as u8))
66    }
67
68    /// Creates a `Date` from `(year, month, day)` date components.
69    ///
70    /// Does not check the validity of `year`, `month`, or `day`, but `year` is
71    /// truncated to 23 bits (% 8,388,608), `month` to 4 bits (% 16), and `day`
72    /// to 5 bits (% 32).
73    ///
74    /// # Example
75    ///
76    /// ```rust
77    /// use version_check::Date;
78    ///
79    /// assert!(Date::from_ymd(2021, 7, 30).exactly("2021-07-30"));
80    /// assert!(Date::from_ymd(2010, 3, 23).exactly("2010-03-23"));
81    /// assert!(Date::from_ymd(2090, 1, 31).exactly("2090-01-31"));
82    ///
83    /// // Truncation: 33 % 32 == 0x21 & 0x1F == 1.
84    /// assert!(Date::from_ymd(2090, 1, 33).exactly("2090-01-01"));
85    /// ```
86    pub fn from_ymd(year: u16, month: u8, day: u8) -> Date {
87        let year = (year as u32) << 9;
88        let month = ((month as u32) & 0xF) << 5;
89        let day = (day as u32) & 0x1F;
90        Date(year | month | day)
91    }
92
93    /// Return the original (YYYY, MM, DD).
94    fn to_ymd(&self) -> (u16, u8, u8) {
95        let y = self.0 >> 9;
96        let m = (self.0 >> 5) & 0xF;
97        let d = self.0 & 0x1F;
98        (y as u16, m as u8, d as u8)
99    }
100
101    /// Returns `true` if `self` occurs on or after `date`.
102    ///
103    /// If `date` occurs before `self`, or if `date` is not in `%Y-%m-%d`
104    /// format, returns `false`.
105    ///
106    /// # Example
107    ///
108    /// ```rust
109    /// use version_check::Date;
110    ///
111    /// let date = Date::parse("2020-01-01").unwrap();
112    ///
113    /// assert!(date.at_least("2019-12-31"));
114    /// assert!(date.at_least("2020-01-01"));
115    /// assert!(date.at_least("2014-04-31"));
116    ///
117    /// assert!(!date.at_least("2020-01-02"));
118    /// assert!(!date.at_least("2024-08-18"));
119    /// ```
120    pub fn at_least(&self, date: &str) -> bool {
121        Date::parse(date)
122            .map(|date| self >= &date)
123            .unwrap_or(false)
124    }
125
126    /// Returns `true` if `self` occurs on or before `date`.
127    ///
128    /// If `date` occurs after `self`, or if `date` is not in `%Y-%m-%d`
129    /// format, returns `false`.
130    ///
131    /// # Example
132    ///
133    /// ```rust
134    /// use version_check::Date;
135    ///
136    /// let date = Date::parse("2020-01-01").unwrap();
137    ///
138    /// assert!(date.at_most("2020-01-01"));
139    /// assert!(date.at_most("2020-01-02"));
140    /// assert!(date.at_most("2024-08-18"));
141    ///
142    /// assert!(!date.at_most("2019-12-31"));
143    /// assert!(!date.at_most("2014-04-31"));
144    /// ```
145    pub fn at_most(&self, date: &str) -> bool {
146        Date::parse(date)
147            .map(|date| self <= &date)
148            .unwrap_or(false)
149    }
150
151    /// Returns `true` if `self` occurs exactly on `date`.
152    ///
153    /// If `date` is not exactly `self`, or if `date` is not in `%Y-%m-%d`
154    /// format, returns `false`.
155    ///
156    /// # Example
157    ///
158    /// ```rust
159    /// use version_check::Date;
160    ///
161    /// let date = Date::parse("2020-01-01").unwrap();
162    ///
163    /// assert!(date.exactly("2020-01-01"));
164    ///
165    /// assert!(!date.exactly("2019-12-31"));
166    /// assert!(!date.exactly("2014-04-31"));
167    /// assert!(!date.exactly("2020-01-02"));
168    /// assert!(!date.exactly("2024-08-18"));
169    /// ```
170    pub fn exactly(&self, date: &str) -> bool {
171        Date::parse(date)
172            .map(|date| self == &date)
173            .unwrap_or(false)
174    }
175}
176
177impl fmt::Display for Date {
178    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
179        let (y, m, d) = self.to_ymd();
180        write!(f, "{}-{:02}-{:02}", y, m, d)
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::Date;
187
188    macro_rules! reflexive_display {
189        ($string:expr) => (
190            assert_eq!(Date::parse($string).unwrap().to_string(), $string);
191        )
192    }
193
194    #[test]
195    fn display() {
196        reflexive_display!("2019-05-08");
197        reflexive_display!("2000-01-01");
198        reflexive_display!("2000-12-31");
199        reflexive_display!("2090-12-31");
200        reflexive_display!("1999-02-19");
201        reflexive_display!("9999-12-31");
202    }
203}