1 use std::fmt; 2 3 /// A date/time type which exists primarily to convert `SystemTime` timestamps into an ISO 8601 4 /// formatted string. 5 /// 6 /// Yes, this exists. Before you have a heart attack, understand that the meat of this is musl's 7 /// [`__secs_to_tm`][1] converted to Rust via [c2rust][2] and then cleaned up by hand. All existing 8 /// `strftime`-like APIs I found were unable to handle the full range of timestamps representable 9 /// by `SystemTime`, including `strftime` itself, since tm.tm_year is an int. 10 /// 11 /// TODO: figure out how to properly attribute the MIT licensed musl project. 12 /// 13 /// [1] http://git.musl-libc.org/cgit/musl/tree/src/time/__secs_to_tm.c 14 /// [2] https://c2rust.com/ 15 /// 16 /// This is directly copy-pasted from https://github.com/danburkert/kudu-rs/blob/c9660067e5f4c1a54143f169b5eeb49446f82e54/src/timestamp.rs#L5-L18 17 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 18 pub(crate) struct DateTime { 19 year: i64, 20 month: u8, 21 day: u8, 22 hour: u8, 23 minute: u8, 24 second: u8, 25 nanos: u32, 26 } 27 28 impl fmt::Display for DateTime { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 30 if self.year > 9999 { 31 write!(f, "+{}", self.year)?; 32 } else if self.year < 0 { 33 write!(f, "{:05}", self.year)?; 34 } else { 35 write!(f, "{:04}", self.year)?; 36 } 37 38 write!( 39 f, 40 "-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z", 41 self.month, 42 self.day, 43 self.hour, 44 self.minute, 45 self.second, 46 self.nanos / 1_000 47 ) 48 } 49 } 50 51 impl From<std::time::SystemTime> for DateTime { from(timestamp: std::time::SystemTime) -> DateTime52 fn from(timestamp: std::time::SystemTime) -> DateTime { 53 let (t, nanos) = match timestamp.duration_since(std::time::UNIX_EPOCH) { 54 Ok(duration) => { 55 debug_assert!(duration.as_secs() <= std::i64::MAX as u64); 56 (duration.as_secs() as i64, duration.subsec_nanos()) 57 } 58 Err(error) => { 59 let duration = error.duration(); 60 debug_assert!(duration.as_secs() <= std::i64::MAX as u64); 61 let (secs, nanos) = (duration.as_secs() as i64, duration.subsec_nanos()); 62 if nanos == 0 { 63 (-secs, 0) 64 } else { 65 (-secs - 1, 1_000_000_000 - nanos) 66 } 67 } 68 }; 69 70 // 2000-03-01 (mod 400 year, immediately after feb29 71 const LEAPOCH: i64 = 946_684_800 + 86400 * (31 + 29); 72 const DAYS_PER_400Y: i32 = 365 * 400 + 97; 73 const DAYS_PER_100Y: i32 = 365 * 100 + 24; 74 const DAYS_PER_4Y: i32 = 365 * 4 + 1; 75 static DAYS_IN_MONTH: [i8; 12] = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29]; 76 77 // Note(dcb): this bit is rearranged slightly to avoid integer overflow. 78 let mut days: i64 = (t / 86_400) - (LEAPOCH / 86_400); 79 let mut remsecs: i32 = (t % 86_400) as i32; 80 if remsecs < 0i32 { 81 remsecs += 86_400; 82 days -= 1 83 } 84 85 let mut qc_cycles: i32 = (days / i64::from(DAYS_PER_400Y)) as i32; 86 let mut remdays: i32 = (days % i64::from(DAYS_PER_400Y)) as i32; 87 if remdays < 0 { 88 remdays += DAYS_PER_400Y; 89 qc_cycles -= 1; 90 } 91 92 let mut c_cycles: i32 = remdays / DAYS_PER_100Y; 93 if c_cycles == 4 { 94 c_cycles -= 1; 95 } 96 remdays -= c_cycles * DAYS_PER_100Y; 97 98 let mut q_cycles: i32 = remdays / DAYS_PER_4Y; 99 if q_cycles == 25 { 100 q_cycles -= 1; 101 } 102 remdays -= q_cycles * DAYS_PER_4Y; 103 104 let mut remyears: i32 = remdays / 365; 105 if remyears == 4 { 106 remyears -= 1; 107 } 108 remdays -= remyears * 365; 109 110 let mut years: i64 = i64::from(remyears) 111 + 4 * i64::from(q_cycles) 112 + 100 * i64::from(c_cycles) 113 + 400 * i64::from(qc_cycles); 114 115 let mut months: i32 = 0; 116 while i32::from(DAYS_IN_MONTH[months as usize]) <= remdays { 117 remdays -= i32::from(DAYS_IN_MONTH[months as usize]); 118 months += 1 119 } 120 121 if months >= 10 { 122 months -= 12; 123 years += 1; 124 } 125 126 DateTime { 127 year: years + 2000, 128 month: (months + 3) as u8, 129 day: (remdays + 1) as u8, 130 hour: (remsecs / 3600) as u8, 131 minute: (remsecs / 60 % 60) as u8, 132 second: (remsecs % 60) as u8, 133 nanos, 134 } 135 } 136 } 137 138 #[cfg(test)] 139 mod tests { 140 use std::i32; 141 use std::time::{Duration, UNIX_EPOCH}; 142 143 use super::*; 144 145 #[test] test_datetime()146 fn test_datetime() { 147 let case = |expected: &str, secs: i64, micros: u32| { 148 let timestamp = if secs >= 0 { 149 UNIX_EPOCH + Duration::new(secs as u64, micros * 1_000) 150 } else { 151 (UNIX_EPOCH - Duration::new(!secs as u64 + 1, 0)) + Duration::new(0, micros * 1_000) 152 }; 153 assert_eq!( 154 expected, 155 format!("{}", DateTime::from(timestamp)), 156 "secs: {}, micros: {}", 157 secs, 158 micros 159 ) 160 }; 161 162 // Mostly generated with: 163 // - date -jur <secs> +"%Y-%m-%dT%H:%M:%S.000000Z" 164 // - http://unixtimestamp.50x.eu/ 165 166 case("1970-01-01T00:00:00.000000Z", 0, 0); 167 168 case("1970-01-01T00:00:00.000001Z", 0, 1); 169 case("1970-01-01T00:00:00.500000Z", 0, 500_000); 170 case("1970-01-01T00:00:01.000001Z", 1, 1); 171 case("1970-01-01T00:01:01.000001Z", 60 + 1, 1); 172 case("1970-01-01T01:01:01.000001Z", 60 * 60 + 60 + 1, 1); 173 case( 174 "1970-01-02T01:01:01.000001Z", 175 24 * 60 * 60 + 60 * 60 + 60 + 1, 176 1, 177 ); 178 179 case("1969-12-31T23:59:59.000000Z", -1, 0); 180 case("1969-12-31T23:59:59.000001Z", -1, 1); 181 case("1969-12-31T23:59:59.500000Z", -1, 500_000); 182 case("1969-12-31T23:58:59.000001Z", -60 - 1, 1); 183 case("1969-12-31T22:58:59.000001Z", -60 * 60 - 60 - 1, 1); 184 case( 185 "1969-12-30T22:58:59.000001Z", 186 -24 * 60 * 60 - 60 * 60 - 60 - 1, 187 1, 188 ); 189 190 case("2038-01-19T03:14:07.000000Z", std::i32::MAX as i64, 0); 191 case("2038-01-19T03:14:08.000000Z", std::i32::MAX as i64 + 1, 0); 192 case("1901-12-13T20:45:52.000000Z", i32::MIN as i64, 0); 193 case("1901-12-13T20:45:51.000000Z", i32::MIN as i64 - 1, 0); 194 195 // Skipping these tests on windows as std::time::SysteTime range is low 196 // on Windows compared with that of Unix which can cause the following 197 // high date value tests to panic 198 #[cfg(not(target_os = "windows"))] 199 { 200 case("+292277026596-12-04T15:30:07.000000Z", std::i64::MAX, 0); 201 case("+292277026596-12-04T15:30:06.000000Z", std::i64::MAX - 1, 0); 202 case("-292277022657-01-27T08:29:53.000000Z", i64::MIN + 1, 0); 203 } 204 205 case("1900-01-01T00:00:00.000000Z", -2208988800, 0); 206 case("1899-12-31T23:59:59.000000Z", -2208988801, 0); 207 case("0000-01-01T00:00:00.000000Z", -62167219200, 0); 208 case("-0001-12-31T23:59:59.000000Z", -62167219201, 0); 209 210 case("1234-05-06T07:08:09.000000Z", -23215049511, 0); 211 case("-1234-05-06T07:08:09.000000Z", -101097651111, 0); 212 case("2345-06-07T08:09:01.000000Z", 11847456541, 0); 213 case("-2345-06-07T08:09:01.000000Z", -136154620259, 0); 214 } 215 } 216