1 | use 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)] |
6 | pub struct Date(u32); |
7 | |
8 | impl 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 | |
177 | impl 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)] |
185 | mod 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 | |