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: u16, m: u8, d: u8) = 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}
204