| 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 | |