1use std::error::Error as StdError; 2use std::fmt; 3use std::str; 4use std::time::{SystemTime, Duration, UNIX_EPOCH}; 5 6#[cfg(target_os="cloudabi")] 7mod max { 8 pub const SECONDS: u64 = ::std::u64::MAX / 1_000_000_000; 9 #[allow(unused)] 10 pub const TIMESTAMP: &'static str = "2554-07-21T23:34:33Z"; 11} 12#[cfg(all( 13 target_pointer_width="32", 14 not(target_os="cloudabi"), 15 not(target_os="windows"), 16 not(all(target_arch="wasm32", not(target_os="emscripten"))) 17))] 18mod max { 19 pub const SECONDS: u64 = ::std::i32::MAX as u64; 20 #[allow(unused)] 21 pub const TIMESTAMP: &'static str = "2038-01-19T03:14:07Z"; 22} 23 24#[cfg(any( 25 target_pointer_width="64", 26 target_os="windows", 27 all(target_arch="wasm32", not(target_os="emscripten")), 28))] 29mod max { 30 pub const SECONDS: u64 = 253_402_300_800-1; // last second of year 9999 31 #[allow(unused)] 32 pub const TIMESTAMP: &str = "9999-12-31T23:59:59Z"; 33} 34 35/// Error parsing datetime (timestamp) 36#[derive(Debug, PartialEq, Clone, Copy)] 37pub enum Error { 38 /// Numeric component is out of range 39 OutOfRange, 40 /// Bad character where digit is expected 41 InvalidDigit, 42 /// Other formatting errors 43 InvalidFormat, 44} 45 46impl StdError for Error {} 47 48impl fmt::Display for Error { 49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 50 match self { 51 Error::OutOfRange => write!(f, "numeric component is out of range"), 52 Error::InvalidDigit => write!(f, "bad character where digit is expected"), 53 Error::InvalidFormat => write!(f, "timestamp format is invalid"), 54 } 55 } 56} 57 58#[derive(Debug, Clone, PartialEq, Eq)] 59enum Precision { 60 Smart, 61 Seconds, 62 Millis, 63 Micros, 64 Nanos, 65} 66 67/// A wrapper type that allows you to Display a SystemTime 68#[derive(Debug, Clone)] 69pub struct Rfc3339Timestamp(SystemTime, Precision); 70 71#[inline] 72fn two_digits(b1: u8, b2: u8) -> Result<u64, Error> { 73 if b1 < b'0' || b2 < b'0' || b1 > b'9' || b2 > b'9' { 74 return Err(Error::InvalidDigit); 75 } 76 Ok(((b1 - b'0')*10 + (b2 - b'0')) as u64) 77} 78 79/// Parse RFC3339 timestamp `2018-02-14T00:28:07Z` 80/// 81/// Supported feature: any precision of fractional 82/// digits `2018-02-14T00:28:07.133Z`. 83/// 84/// Unsupported feature: localized timestamps. Only UTC is supported. 85pub fn parse_rfc3339(s: &str) -> Result<SystemTime, Error> { 86 if s.len() < "2018-02-14T00:28:07Z".len() { 87 return Err(Error::InvalidFormat); 88 } 89 let b = s.as_bytes(); 90 if b[10] != b'T' || b[b.len()-1] != b'Z' { 91 return Err(Error::InvalidFormat); 92 } 93 parse_rfc3339_weak(s) 94} 95 96/// Parse RFC3339-like timestamp `2018-02-14 00:28:07` 97/// 98/// Supported features: 99/// 100/// 1. Any precision of fractional digits `2018-02-14 00:28:07.133`. 101/// 2. Supports timestamp with or without either of `T` or `Z` 102/// 3. Anything valid for `parse_3339` is valid for this function 103/// 104/// Unsupported feature: localized timestamps. Only UTC is supported, even if 105/// `Z` is not specified. 106/// 107/// This function is intended to use for parsing human input. Whereas 108/// `parse_rfc3339` is for strings generated programmatically. 109pub fn parse_rfc3339_weak(s: &str) -> Result<SystemTime, Error> { 110 if s.len() < "2018-02-14T00:28:07".len() { 111 return Err(Error::InvalidFormat); 112 } 113 let b = s.as_bytes(); // for careless slicing 114 if b[4] != b'-' || b[7] != b'-' || (b[10] != b'T' && b[10] != b' ') || 115 b[13] != b':' || b[16] != b':' 116 { 117 return Err(Error::InvalidFormat); 118 } 119 let year = two_digits(b[0], b[1])? * 100 + two_digits(b[2], b[3])?; 120 let month = two_digits(b[5], b[6])?; 121 let day = two_digits(b[8], b[9])?; 122 let hour = two_digits(b[11], b[12])?; 123 let minute = two_digits(b[14], b[15])?; 124 let mut second = two_digits(b[17], b[18])?; 125 126 if year < 1970 || hour > 23 || minute > 59 || second > 60 { 127 return Err(Error::OutOfRange); 128 } 129 // TODO(tailhook) should we check that leaps second is only on midnight ? 130 if second == 60 { 131 second = 59 132 }; 133 let leap_years = ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 + 134 ((year - 1) - 1600) / 400; 135 let leap = is_leap_year(year); 136 let (mut ydays, mdays) = match month { 137 1 => (0, 31), 138 2 if leap => (31, 29), 139 2 => (31, 28), 140 3 => (59, 31), 141 4 => (90, 30), 142 5 => (120, 31), 143 6 => (151, 30), 144 7 => (181, 31), 145 8 => (212, 31), 146 9 => (243, 30), 147 10 => (273, 31), 148 11 => (304, 30), 149 12 => (334, 31), 150 _ => return Err(Error::OutOfRange), 151 }; 152 if day > mdays || day == 0 { 153 return Err(Error::OutOfRange); 154 } 155 ydays += day - 1; 156 if leap && month > 2 { 157 ydays += 1; 158 } 159 let days = (year - 1970) * 365 + leap_years + ydays; 160 161 let time = second + minute * 60 + hour * 3600; 162 163 let mut nanos = 0; 164 let mut mult = 100_000_000; 165 if b.get(19) == Some(&b'.') { 166 for idx in 20..b.len() { 167 if b[idx] == b'Z' { 168 if idx == b.len()-1 { 169 break; 170 } else { 171 return Err(Error::InvalidDigit); 172 } 173 } 174 if b[idx] < b'0' || b[idx] > b'9' { 175 return Err(Error::InvalidDigit); 176 } 177 nanos += mult * (b[idx] - b'0') as u32; 178 mult /= 10; 179 } 180 } else if b.len() != 19 && (b.len() > 20 || b[19] != b'Z') { 181 return Err(Error::InvalidFormat); 182 } 183 184 let total_seconds = time + days * 86400; 185 if total_seconds > max::SECONDS { 186 return Err(Error::OutOfRange); 187 } 188 189 Ok(UNIX_EPOCH + Duration::new(total_seconds, nanos)) 190} 191 192fn is_leap_year(y: u64) -> bool { 193 y % 4 == 0 && (y % 100 != 0 || y % 400 == 0) 194} 195 196/// Format an RFC3339 timestamp `2018-02-14T00:28:07Z` 197/// 198/// This function formats timestamp with smart precision: i.e. if it has no 199/// fractional seconds, they aren't written at all. And up to nine digits if 200/// they are. 201/// 202/// The value is always UTC and ignores system timezone. 203pub fn format_rfc3339(system_time: SystemTime) -> Rfc3339Timestamp { 204 Rfc3339Timestamp(system_time, Precision::Smart) 205} 206 207/// Format an RFC3339 timestamp `2018-02-14T00:28:07Z` 208/// 209/// This format always shows timestamp without fractional seconds. 210/// 211/// The value is always UTC and ignores system timezone. 212pub fn format_rfc3339_seconds(system_time: SystemTime) -> Rfc3339Timestamp { 213 Rfc3339Timestamp(system_time, Precision::Seconds) 214} 215 216/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000Z` 217/// 218/// This format always shows milliseconds even if millisecond value is zero. 219/// 220/// The value is always UTC and ignores system timezone. 221pub fn format_rfc3339_millis(system_time: SystemTime) -> Rfc3339Timestamp { 222 Rfc3339Timestamp(system_time, Precision::Millis) 223} 224 225/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000000Z` 226/// 227/// This format always shows microseconds even if microsecond value is zero. 228/// 229/// The value is always UTC and ignores system timezone. 230pub fn format_rfc3339_micros(system_time: SystemTime) -> Rfc3339Timestamp { 231 Rfc3339Timestamp(system_time, Precision::Micros) 232} 233 234/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000000000Z` 235/// 236/// This format always shows nanoseconds even if nanosecond value is zero. 237/// 238/// The value is always UTC and ignores system timezone. 239pub fn format_rfc3339_nanos(system_time: SystemTime) -> Rfc3339Timestamp { 240 Rfc3339Timestamp(system_time, Precision::Nanos) 241} 242 243impl Rfc3339Timestamp { 244 /// Returns a reference to the [`SystemTime`][] that is being formatted. 245 pub fn get_ref(&self) -> &SystemTime { 246 &self.0 247 } 248} 249 250impl fmt::Display for Rfc3339Timestamp { 251 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 252 use self::Precision::*; 253 254 let dur = self.0.duration_since(UNIX_EPOCH) 255 .expect("all times should be after the epoch"); 256 let secs_since_epoch = dur.as_secs(); 257 let nanos = dur.subsec_nanos(); 258 259 if secs_since_epoch >= 253_402_300_800 { // year 9999 260 return Err(fmt::Error); 261 } 262 263 /* 2000-03-01 (mod 400 year, immediately after feb29 */ 264 const LEAPOCH: i64 = 11017; 265 const DAYS_PER_400Y: i64 = 365*400 + 97; 266 const DAYS_PER_100Y: i64 = 365*100 + 24; 267 const DAYS_PER_4Y: i64 = 365*4 + 1; 268 269 let days = (secs_since_epoch / 86400) as i64 - LEAPOCH; 270 let secs_of_day = secs_since_epoch % 86400; 271 272 let mut qc_cycles = days / DAYS_PER_400Y; 273 let mut remdays = days % DAYS_PER_400Y; 274 275 if remdays < 0 { 276 remdays += DAYS_PER_400Y; 277 qc_cycles -= 1; 278 } 279 280 let mut c_cycles = remdays / DAYS_PER_100Y; 281 if c_cycles == 4 { c_cycles -= 1; } 282 remdays -= c_cycles * DAYS_PER_100Y; 283 284 let mut q_cycles = remdays / DAYS_PER_4Y; 285 if q_cycles == 25 { q_cycles -= 1; } 286 remdays -= q_cycles * DAYS_PER_4Y; 287 288 let mut remyears = remdays / 365; 289 if remyears == 4 { remyears -= 1; } 290 remdays -= remyears * 365; 291 292 let mut year = 2000 + 293 remyears + 4*q_cycles + 100*c_cycles + 400*qc_cycles; 294 295 let months = [31,30,31,30,31,31,30,31,30,31,31,29]; 296 let mut mon = 0; 297 for mon_len in months.iter() { 298 mon += 1; 299 if remdays < *mon_len { 300 break; 301 } 302 remdays -= *mon_len; 303 } 304 let mday = remdays+1; 305 let mon = if mon + 2 > 12 { 306 year += 1; 307 mon - 10 308 } else { 309 mon + 2 310 }; 311 312 let mut buf: [u8; 30] = [ 313 // Too long to write as: b"0000-00-00T00:00:00.000000000Z" 314 b'0', b'0', b'0', b'0', b'-', b'0', b'0', b'-', b'0', b'0', b'T', 315 b'0', b'0', b':', b'0', b'0', b':', b'0', b'0', 316 b'.', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'Z', 317 ]; 318 buf[0] = b'0' + (year / 1000) as u8; 319 buf[1] = b'0' + (year / 100 % 10) as u8; 320 buf[2] = b'0' + (year / 10 % 10) as u8; 321 buf[3] = b'0' + (year % 10) as u8; 322 buf[5] = b'0' + (mon / 10) as u8; 323 buf[6] = b'0' + (mon % 10) as u8; 324 buf[8] = b'0' + (mday / 10) as u8; 325 buf[9] = b'0' + (mday % 10) as u8; 326 buf[11] = b'0' + (secs_of_day / 3600 / 10) as u8; 327 buf[12] = b'0' + (secs_of_day / 3600 % 10) as u8; 328 buf[14] = b'0' + (secs_of_day / 60 / 10 % 6) as u8; 329 buf[15] = b'0' + (secs_of_day / 60 % 10) as u8; 330 buf[17] = b'0' + (secs_of_day / 10 % 6) as u8; 331 buf[18] = b'0' + (secs_of_day % 10) as u8; 332 333 let offset = if self.1 == Seconds || nanos == 0 && self.1 == Smart { 334 buf[19] = b'Z'; 335 19 336 } else if self.1 == Millis { 337 buf[20] = b'0' + (nanos / 100_000_000) as u8; 338 buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8; 339 buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8; 340 buf[23] = b'Z'; 341 23 342 } else if self.1 == Micros { 343 buf[20] = b'0' + (nanos / 100_000_000) as u8; 344 buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8; 345 buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8; 346 buf[23] = b'0' + (nanos / 100_000 % 10) as u8; 347 buf[24] = b'0' + (nanos / 10_000 % 10) as u8; 348 buf[25] = b'0' + (nanos / 1_000 % 10) as u8; 349 buf[26] = b'Z'; 350 26 351 } else { 352 buf[20] = b'0' + (nanos / 100_000_000) as u8; 353 buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8; 354 buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8; 355 buf[23] = b'0' + (nanos / 100_000 % 10) as u8; 356 buf[24] = b'0' + (nanos / 10_000 % 10) as u8; 357 buf[25] = b'0' + (nanos / 1_000 % 10) as u8; 358 buf[26] = b'0' + (nanos / 100 % 10) as u8; 359 buf[27] = b'0' + (nanos / 10 % 10) as u8; 360 buf[28] = b'0' + (nanos / 1 % 10) as u8; 361 // 29th is 'Z' 362 29 363 }; 364 365 // we know our chars are all ascii 366 f.write_str(str::from_utf8(&buf[..=offset]).expect("Conversion to utf8 failed")) 367 } 368} 369 370#[cfg(test)] 371mod test { 372 use std::str::from_utf8; 373 use std::time::{UNIX_EPOCH, SystemTime, Duration}; 374 375 use rand::Rng; 376 377 use super::{parse_rfc3339, parse_rfc3339_weak, format_rfc3339}; 378 use super::{format_rfc3339_millis, format_rfc3339_micros}; 379 use super::{format_rfc3339_nanos}; 380 use super::max; 381 382 fn from_sec(sec: u64) -> (String, SystemTime) { 383 let s = time::at_utc(time::Timespec { sec: sec as i64, nsec: 0 }) 384 .rfc3339().to_string(); 385 let time = UNIX_EPOCH + Duration::new(sec, 0); 386 (s, time) 387 } 388 389 #[test] 390 #[cfg(all(target_pointer_width="32", target_os="linux"))] 391 fn year_after_2038_fails_gracefully() { 392 // next second 393 assert_eq!(parse_rfc3339("2038-01-19T03:14:08Z").unwrap_err(), 394 super::Error::OutOfRange); 395 assert_eq!(parse_rfc3339("9999-12-31T23:59:59Z").unwrap_err(), 396 super::Error::OutOfRange); 397 } 398 399 #[test] 400 fn smoke_tests_parse() { 401 assert_eq!(parse_rfc3339("1970-01-01T00:00:00Z").unwrap(), 402 UNIX_EPOCH + Duration::new(0, 0)); 403 assert_eq!(parse_rfc3339("1970-01-01T00:00:01Z").unwrap(), 404 UNIX_EPOCH + Duration::new(1, 0)); 405 assert_eq!(parse_rfc3339("2018-02-13T23:08:32Z").unwrap(), 406 UNIX_EPOCH + Duration::new(1_518_563_312, 0)); 407 assert_eq!(parse_rfc3339("2012-01-01T00:00:00Z").unwrap(), 408 UNIX_EPOCH + Duration::new(1_325_376_000, 0)); 409 } 410 411 #[test] 412 fn smoke_tests_format() { 413 assert_eq!( 414 format_rfc3339(UNIX_EPOCH + Duration::new(0, 0)).to_string(), 415 "1970-01-01T00:00:00Z"); 416 assert_eq!( 417 format_rfc3339(UNIX_EPOCH + Duration::new(1, 0)).to_string(), 418 "1970-01-01T00:00:01Z"); 419 assert_eq!( 420 format_rfc3339(UNIX_EPOCH + Duration::new(1_518_563_312, 0)).to_string(), 421 "2018-02-13T23:08:32Z"); 422 assert_eq!( 423 format_rfc3339(UNIX_EPOCH + Duration::new(1_325_376_000, 0)).to_string(), 424 "2012-01-01T00:00:00Z"); 425 } 426 427 #[test] 428 fn smoke_tests_format_millis() { 429 assert_eq!( 430 format_rfc3339_millis(UNIX_EPOCH + 431 Duration::new(0, 0)).to_string(), 432 "1970-01-01T00:00:00.000Z"); 433 assert_eq!( 434 format_rfc3339_millis(UNIX_EPOCH + 435 Duration::new(1_518_563_312, 123_000_000)).to_string(), 436 "2018-02-13T23:08:32.123Z"); 437 } 438 439 #[test] 440 fn smoke_tests_format_micros() { 441 assert_eq!( 442 format_rfc3339_micros(UNIX_EPOCH + 443 Duration::new(0, 0)).to_string(), 444 "1970-01-01T00:00:00.000000Z"); 445 assert_eq!( 446 format_rfc3339_micros(UNIX_EPOCH + 447 Duration::new(1_518_563_312, 123_000_000)).to_string(), 448 "2018-02-13T23:08:32.123000Z"); 449 assert_eq!( 450 format_rfc3339_micros(UNIX_EPOCH + 451 Duration::new(1_518_563_312, 456_123_000)).to_string(), 452 "2018-02-13T23:08:32.456123Z"); 453 } 454 455 #[test] 456 fn smoke_tests_format_nanos() { 457 assert_eq!( 458 format_rfc3339_nanos(UNIX_EPOCH + 459 Duration::new(0, 0)).to_string(), 460 "1970-01-01T00:00:00.000000000Z"); 461 assert_eq!( 462 format_rfc3339_nanos(UNIX_EPOCH + 463 Duration::new(1_518_563_312, 123_000_000)).to_string(), 464 "2018-02-13T23:08:32.123000000Z"); 465 assert_eq!( 466 format_rfc3339_nanos(UNIX_EPOCH + 467 Duration::new(1_518_563_312, 789_456_123)).to_string(), 468 "2018-02-13T23:08:32.789456123Z"); 469 } 470 471 #[test] 472 fn upper_bound() { 473 let max = UNIX_EPOCH + Duration::new(max::SECONDS, 0); 474 assert_eq!(parse_rfc3339(&max::TIMESTAMP).unwrap(), max); 475 assert_eq!(format_rfc3339(max).to_string(), max::TIMESTAMP); 476 } 477 478 #[test] 479 fn leap_second() { 480 assert_eq!(parse_rfc3339("2016-12-31T23:59:60Z").unwrap(), 481 UNIX_EPOCH + Duration::new(1_483_228_799, 0)); 482 } 483 484 #[test] 485 fn first_731_days() { 486 let year_start = 0; // 1970 487 for day in 0..= 365 * 2 { // scan leap year and non-leap year 488 let (s, time) = from_sec(year_start + day * 86400); 489 assert_eq!(parse_rfc3339(&s).unwrap(), time); 490 assert_eq!(format_rfc3339(time).to_string(), s); 491 } 492 } 493 494 #[test] 495 fn the_731_consecutive_days() { 496 let year_start = 1_325_376_000; // 2012 497 for day in 0..= 365 * 2 { // scan leap year and non-leap year 498 let (s, time) = from_sec(year_start + day * 86400); 499 assert_eq!(parse_rfc3339(&s).unwrap(), time); 500 assert_eq!(format_rfc3339(time).to_string(), s); 501 } 502 } 503 504 #[test] 505 fn all_86400_seconds() { 506 let day_start = 1_325_376_000; 507 for second in 0..86400 { // scan leap year and non-leap year 508 let (s, time) = from_sec(day_start + second); 509 assert_eq!(parse_rfc3339(&s).unwrap(), time); 510 assert_eq!(format_rfc3339(time).to_string(), s); 511 } 512 } 513 514 #[test] 515 fn random_past() { 516 let upper = SystemTime::now().duration_since(UNIX_EPOCH).unwrap() 517 .as_secs(); 518 for _ in 0..10000 { 519 let sec = rand::thread_rng().gen_range(0, upper); 520 let (s, time) = from_sec(sec); 521 assert_eq!(parse_rfc3339(&s).unwrap(), time); 522 assert_eq!(format_rfc3339(time).to_string(), s); 523 } 524 } 525 526 #[test] 527 fn random_wide_range() { 528 for _ in 0..100_000 { 529 let sec = rand::thread_rng().gen_range(0, max::SECONDS); 530 let (s, time) = from_sec(sec); 531 assert_eq!(parse_rfc3339(&s).unwrap(), time); 532 assert_eq!(format_rfc3339(time).to_string(), s); 533 } 534 } 535 536 #[test] 537 fn milliseconds() { 538 assert_eq!(parse_rfc3339("1970-01-01T00:00:00.123Z").unwrap(), 539 UNIX_EPOCH + Duration::new(0, 123_000_000)); 540 assert_eq!(format_rfc3339(UNIX_EPOCH + Duration::new(0, 123_000_000)) 541 .to_string(), "1970-01-01T00:00:00.123000000Z"); 542 } 543 544 #[test] 545 #[should_panic(expected="OutOfRange")] 546 fn zero_month() { 547 parse_rfc3339("1970-00-01T00:00:00Z").unwrap(); 548 } 549 550 #[test] 551 #[should_panic(expected="OutOfRange")] 552 fn big_month() { 553 parse_rfc3339("1970-32-01T00:00:00Z").unwrap(); 554 } 555 556 #[test] 557 #[should_panic(expected="OutOfRange")] 558 fn zero_day() { 559 parse_rfc3339("1970-01-00T00:00:00Z").unwrap(); 560 } 561 562 #[test] 563 #[should_panic(expected="OutOfRange")] 564 fn big_day() { 565 parse_rfc3339("1970-12-35T00:00:00Z").unwrap(); 566 } 567 568 #[test] 569 #[should_panic(expected="OutOfRange")] 570 fn big_day2() { 571 parse_rfc3339("1970-02-30T00:00:00Z").unwrap(); 572 } 573 574 #[test] 575 #[should_panic(expected="OutOfRange")] 576 fn big_second() { 577 parse_rfc3339("1970-12-30T00:00:78Z").unwrap(); 578 } 579 580 #[test] 581 #[should_panic(expected="OutOfRange")] 582 fn big_minute() { 583 parse_rfc3339("1970-12-30T00:78:00Z").unwrap(); 584 } 585 586 #[test] 587 #[should_panic(expected="OutOfRange")] 588 fn big_hour() { 589 parse_rfc3339("1970-12-30T24:00:00Z").unwrap(); 590 } 591 592 #[test] 593 fn break_data() { 594 for pos in 0.."2016-12-31T23:59:60Z".len() { 595 let mut s = b"2016-12-31T23:59:60Z".to_vec(); 596 s[pos] = b'x'; 597 parse_rfc3339(from_utf8(&s).unwrap()).unwrap_err(); 598 } 599 } 600 601 #[test] 602 fn weak_smoke_tests() { 603 assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00").unwrap(), 604 UNIX_EPOCH + Duration::new(0, 0)); 605 parse_rfc3339("1970-01-01 00:00:00").unwrap_err(); 606 607 assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00.000123").unwrap(), 608 UNIX_EPOCH + Duration::new(0, 123_000)); 609 parse_rfc3339("1970-01-01 00:00:00.000123").unwrap_err(); 610 611 assert_eq!(parse_rfc3339_weak("1970-01-01T00:00:00.000123").unwrap(), 612 UNIX_EPOCH + Duration::new(0, 123_000)); 613 parse_rfc3339("1970-01-01T00:00:00.000123").unwrap_err(); 614 615 assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00.000123Z").unwrap(), 616 UNIX_EPOCH + Duration::new(0, 123_000)); 617 parse_rfc3339("1970-01-01 00:00:00.000123Z").unwrap_err(); 618 619 assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00Z").unwrap(), 620 UNIX_EPOCH + Duration::new(0, 0)); 621 parse_rfc3339("1970-01-01 00:00:00Z").unwrap_err(); 622 } 623} 624