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 /// ``` read() -> Option<Date>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 /// ``` parse(date: &str) -> Option<Date>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 /// ``` from_ymd(year: u16, month: u8, day: u8) -> Date86 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). to_ymd(&self) -> (u16, u8, u8)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 /// ``` at_least(&self, date: &str) -> bool120 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 /// ``` at_most(&self, date: &str) -> bool145 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 /// ``` exactly(&self, date: &str) -> bool170 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 { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result178 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)] 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] display()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