1use std::error::Error as StdError;
2use std::fmt;
3use std::str;
4use std::time::{Duration, SystemTime, UNIX_EPOCH};
5
6#[cfg(all(
7 target_pointer_width = "32",
8 not(target_os = "windows"),
9 not(all(target_arch = "wasm32", not(target_os = "emscripten")))
10))]
11mod max {
12 pub(super) const SECONDS: u64 = ::std::i32::MAX as u64;
13 #[allow(unused)]
14 pub(super) const TIMESTAMP: &'static str = "2038-01-19T03:14:07Z";
15}
16
17#[cfg(any(
18 target_pointer_width = "64",
19 target_os = "windows",
20 all(target_arch = "wasm32", not(target_os = "emscripten")),
21))]
22mod max {
23 pub(super) const SECONDS: u64 = 253_402_300_800 - 1; #[allow(unused)]
25 pub(super) const TIMESTAMP: &str = "9999-12-31T23:59:59Z";
26}
27
28#[derive(Debug, PartialEq, Clone, Copy)]
30pub enum Error {
31 OutOfRange,
33 InvalidDigit,
35 InvalidFormat,
37}
38
39impl StdError for Error {}
40
41impl fmt::Display for Error {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 match self {
44 Error::OutOfRange => write!(f, "numeric component is out of range"),
45 Error::InvalidDigit => write!(f, "bad character where digit is expected"),
46 Error::InvalidFormat => write!(f, "timestamp format is invalid"),
47 }
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52enum Precision {
53 Smart,
54 Seconds,
55 Millis,
56 Micros,
57 Nanos,
58}
59
60#[derive(Debug, Clone)]
62pub struct Rfc3339Timestamp(SystemTime, Precision);
63
64#[inline]
65fn two_digits(b1: u8, b2: u8) -> Result<u64, Error> {
67 fn two_digits_inner(a: char, b: char) -> Option<u64> {
68 let a = a.to_digit(10)?;
69 let b = b.to_digit(10)?;
70
71 Some((a * 10 + b) as u64)
72 }
73
74 two_digits_inner(b1 as char, b2 as char).ok_or(Error::InvalidDigit)
75}
76
77pub fn parse_rfc3339(s: &str) -> Result<SystemTime, Error> {
85 if s.len() < "2018-02-14T00:28:07Z".len() {
86 return Err(Error::InvalidFormat);
87 }
88 let b = s.as_bytes();
89 if b[10] != b'T' || (b.last() != Some(&b'Z') && !s.ends_with("+00:00")) {
90 return Err(Error::InvalidFormat);
91 }
92 parse_rfc3339_weak(s)
93}
94
95pub fn parse_rfc3339_weak(s: &str) -> Result<SystemTime, Error> {
109 if s.len() < "2018-02-14T00:28:07".len() {
110 return Err(Error::InvalidFormat);
111 }
112 let b = s.as_bytes(); if b[4] != b'-'
114 || b[7] != b'-'
115 || (b[10] != b'T' && b[10] != b' ')
116 || b[13] != b':'
117 || b[16] != b':'
118 {
119 return Err(Error::InvalidFormat);
120 }
121 let year = two_digits(b[0], b[1])? * 100 + two_digits(b[2], b[3])?;
122 let month = two_digits(b[5], b[6])?;
123 let day = two_digits(b[8], b[9])?;
124 let hour = two_digits(b[11], b[12])?;
125 let minute = two_digits(b[14], b[15])?;
126 let mut second = two_digits(b[17], b[18])?;
127
128 if year < 1970 || hour > 23 || minute > 59 || second > 60 {
129 return Err(Error::OutOfRange);
130 }
131 if second == 60 {
133 second = 59;
134 }
135
136 let leap = is_leap_year(year);
137 let (mut ydays, mdays) = match month {
138 1 => (0, 31),
139 2 if leap => (31, 29),
140 2 => (31, 28),
141 3 => (59, 31),
142 4 => (90, 30),
143 5 => (120, 31),
144 6 => (151, 30),
145 7 => (181, 31),
146 8 => (212, 31),
147 9 => (243, 30),
148 10 => (273, 31),
149 11 => (304, 30),
150 12 => (334, 31),
151 _ => return Err(Error::OutOfRange),
152 };
153 if day > mdays || day == 0 {
154 return Err(Error::OutOfRange);
155 }
156 ydays += day - 1;
157 if leap && month > 2 {
158 ydays += 1;
159 }
160
161 let leap_years =
162 ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 + ((year - 1) - 1600) / 400;
163 let days = (year - 1970) * 365 + leap_years + ydays;
164
165 let time = second + minute * 60 + hour * 3600;
166
167 let mut nanos = 0;
168 let mut mult = 100_000_000;
169 if b.get(19) == Some(&b'.') {
170 for idx in 20..b.len() {
171 if b[idx] == b'Z' {
172 if idx == b.len() - 1 {
173 break;
174 }
175 return Err(Error::InvalidDigit);
176 } else if b[idx] == b'+' {
177 if idx == b.len() - 6 {
179 break;
180 }
181 return Err(Error::InvalidDigit);
182 }
183
184 nanos += mult * (b[idx] as char).to_digit(10).ok_or(Error::InvalidDigit)?;
185 mult /= 10;
186 }
187 } else if b.len() != 19 && (b.len() > 25 || (b[19] != b'Z' && (&b[19..] != b"+00:00"))) {
188 return Err(Error::InvalidFormat);
189 }
190
191 let total_seconds = time + days * 86400;
192 if total_seconds > max::SECONDS {
193 return Err(Error::OutOfRange);
194 }
195
196 Ok(UNIX_EPOCH + Duration::new(total_seconds, nanos))
197}
198
199fn is_leap_year(y: u64) -> bool {
200 y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)
201}
202
203pub fn format_rfc3339(system_time: SystemTime) -> Rfc3339Timestamp {
211 Rfc3339Timestamp(system_time, Precision::Smart)
212}
213
214pub fn format_rfc3339_seconds(system_time: SystemTime) -> Rfc3339Timestamp {
220 Rfc3339Timestamp(system_time, Precision::Seconds)
221}
222
223pub fn format_rfc3339_millis(system_time: SystemTime) -> Rfc3339Timestamp {
229 Rfc3339Timestamp(system_time, Precision::Millis)
230}
231
232pub fn format_rfc3339_micros(system_time: SystemTime) -> Rfc3339Timestamp {
238 Rfc3339Timestamp(system_time, Precision::Micros)
239}
240
241pub fn format_rfc3339_nanos(system_time: SystemTime) -> Rfc3339Timestamp {
247 Rfc3339Timestamp(system_time, Precision::Nanos)
248}
249
250impl Rfc3339Timestamp {
251 pub fn get_ref(&self) -> &SystemTime {
253 &self.0
254 }
255}
256
257impl fmt::Display for Rfc3339Timestamp {
258 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
259 use self::Precision::*;
260
261 let dur = self
262 .0
263 .duration_since(UNIX_EPOCH)
264 .expect("all times should be after the epoch");
265 let secs_since_epoch = dur.as_secs();
266 let nanos = dur.subsec_nanos();
267
268 if secs_since_epoch >= 253_402_300_800 {
269 return Err(fmt::Error);
271 }
272
273 const LEAPOCH: i64 = 11017;
275 const DAYS_PER_400Y: i64 = 365 * 400 + 97;
276 const DAYS_PER_100Y: i64 = 365 * 100 + 24;
277 const DAYS_PER_4Y: i64 = 365 * 4 + 1;
278
279 let days = (secs_since_epoch / 86400) as i64 - LEAPOCH;
280 let secs_of_day = secs_since_epoch % 86400;
281
282 let mut qc_cycles = days / DAYS_PER_400Y;
283 let mut remdays = days % DAYS_PER_400Y;
284
285 if remdays < 0 {
286 remdays += DAYS_PER_400Y;
287 qc_cycles -= 1;
288 }
289
290 let mut c_cycles = remdays / DAYS_PER_100Y;
291 if c_cycles == 4 {
292 c_cycles -= 1;
293 }
294 remdays -= c_cycles * DAYS_PER_100Y;
295
296 let mut q_cycles = remdays / DAYS_PER_4Y;
297 if q_cycles == 25 {
298 q_cycles -= 1;
299 }
300 remdays -= q_cycles * DAYS_PER_4Y;
301
302 let mut remyears = remdays / 365;
303 if remyears == 4 {
304 remyears -= 1;
305 }
306 remdays -= remyears * 365;
307
308 let mut year = 2000 + remyears + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles;
309
310 let months = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
311 let mut mon = 0;
312 for mon_len in months.iter() {
313 mon += 1;
314 if remdays < *mon_len {
315 break;
316 }
317 remdays -= *mon_len;
318 }
319 let mday = remdays + 1;
320 let mon = if mon + 2 > 12 {
321 year += 1;
322 mon - 10
323 } else {
324 mon + 2
325 };
326
327 const BUF_INIT: [u8; 30] = *b"0000-00-00T00:00:00.000000000Z";
328
329 let mut buf: [u8; 30] = BUF_INIT;
330 buf[0] = b'0' + (year / 1000) as u8;
331 buf[1] = b'0' + (year / 100 % 10) as u8;
332 buf[2] = b'0' + (year / 10 % 10) as u8;
333 buf[3] = b'0' + (year % 10) as u8;
334 buf[5] = b'0' + (mon / 10) as u8;
335 buf[6] = b'0' + (mon % 10) as u8;
336 buf[8] = b'0' + (mday / 10) as u8;
337 buf[9] = b'0' + (mday % 10) as u8;
338 buf[11] = b'0' + (secs_of_day / 3600 / 10) as u8;
339 buf[12] = b'0' + (secs_of_day / 3600 % 10) as u8;
340 buf[14] = b'0' + (secs_of_day / 60 / 10 % 6) as u8;
341 buf[15] = b'0' + (secs_of_day / 60 % 10) as u8;
342 buf[17] = b'0' + (secs_of_day / 10 % 6) as u8;
343 buf[18] = b'0' + (secs_of_day % 10) as u8;
344
345 let offset = if self.1 == Seconds || nanos == 0 && self.1 == Smart {
346 buf[19] = b'Z';
347 19
348 } else if self.1 == Millis {
349 buf[20] = b'0' + (nanos / 100_000_000) as u8;
350 buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
351 buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
352 buf[23] = b'Z';
353 23
354 } else if self.1 == Micros {
355 buf[20] = b'0' + (nanos / 100_000_000) as u8;
356 buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
357 buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
358 buf[23] = b'0' + (nanos / 100_000 % 10) as u8;
359 buf[24] = b'0' + (nanos / 10_000 % 10) as u8;
360 buf[25] = b'0' + (nanos / 1_000 % 10) as u8;
361 buf[26] = b'Z';
362 26
363 } else {
364 buf[20] = b'0' + (nanos / 100_000_000) as u8;
365 buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
366 buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
367 buf[23] = b'0' + (nanos / 100_000 % 10) as u8;
368 buf[24] = b'0' + (nanos / 10_000 % 10) as u8;
369 buf[25] = b'0' + (nanos / 1_000 % 10) as u8;
370 buf[26] = b'0' + (nanos / 100 % 10) as u8;
371 buf[27] = b'0' + (nanos / 10 % 10) as u8;
372 buf[28] = b'0' + (nanos % 10) as u8;
373 29
375 };
376
377 f.write_str(str::from_utf8(&buf[..=offset]).expect("Conversion to utf8 failed"))
379 }
380}
381
382#[cfg(test)]
383mod test {
384 use std::str::from_utf8;
385 use std::time::{Duration, SystemTime, UNIX_EPOCH};
386
387 use rand::Rng;
388 use time::format_description::well_known::Rfc3339;
389 use time::UtcDateTime;
390
391 use super::format_rfc3339_nanos;
392 use super::max;
393 use super::{format_rfc3339, parse_rfc3339, parse_rfc3339_weak};
394 use super::{format_rfc3339_micros, format_rfc3339_millis};
395
396 fn from_sec(sec: u64) -> (String, SystemTime) {
397 let s = UtcDateTime::from_unix_timestamp(sec as i64)
398 .unwrap()
399 .format(&Rfc3339)
400 .unwrap();
401 let time = UNIX_EPOCH + Duration::new(sec, 0);
402 (s, time)
403 }
404
405 #[test]
406 #[cfg(all(target_pointer_width = "32", target_os = "linux"))]
407 fn year_after_2038_fails_gracefully() {
408 assert_eq!(
410 parse_rfc3339("2038-01-19T03:14:08Z").unwrap_err(),
411 super::Error::OutOfRange
412 );
413 assert_eq!(
414 parse_rfc3339("9999-12-31T23:59:59Z").unwrap_err(),
415 super::Error::OutOfRange
416 );
417 }
418
419 #[test]
420 fn smoke_tests_parse() {
421 assert_eq!(
422 parse_rfc3339("1970-01-01T00:00:00Z").unwrap(),
423 UNIX_EPOCH + Duration::new(0, 0)
424 );
425 assert_eq!(
426 parse_rfc3339("1970-01-01T00:00:01Z").unwrap(),
427 UNIX_EPOCH + Duration::new(1, 0)
428 );
429 assert_eq!(
430 parse_rfc3339("2018-02-13T23:08:32Z").unwrap(),
431 UNIX_EPOCH + Duration::new(1_518_563_312, 0)
432 );
433 assert_eq!(
434 parse_rfc3339("2012-01-01T00:00:00Z").unwrap(),
435 UNIX_EPOCH + Duration::new(1_325_376_000, 0)
436 );
437 }
438
439 #[test]
440 fn smoke_tests_format() {
441 assert_eq!(
442 format_rfc3339(UNIX_EPOCH + Duration::new(0, 0)).to_string(),
443 "1970-01-01T00:00:00Z"
444 );
445 assert_eq!(
446 format_rfc3339(UNIX_EPOCH + Duration::new(1, 0)).to_string(),
447 "1970-01-01T00:00:01Z"
448 );
449 assert_eq!(
450 format_rfc3339(UNIX_EPOCH + Duration::new(1_518_563_312, 0)).to_string(),
451 "2018-02-13T23:08:32Z"
452 );
453 assert_eq!(
454 format_rfc3339(UNIX_EPOCH + Duration::new(1_325_376_000, 0)).to_string(),
455 "2012-01-01T00:00:00Z"
456 );
457 }
458
459 #[test]
460 fn smoke_tests_format_millis() {
461 assert_eq!(
462 format_rfc3339_millis(UNIX_EPOCH + Duration::new(0, 0)).to_string(),
463 "1970-01-01T00:00:00.000Z"
464 );
465 assert_eq!(
466 format_rfc3339_millis(UNIX_EPOCH + Duration::new(1_518_563_312, 123_000_000))
467 .to_string(),
468 "2018-02-13T23:08:32.123Z"
469 );
470 }
471
472 #[test]
473 fn smoke_tests_format_micros() {
474 assert_eq!(
475 format_rfc3339_micros(UNIX_EPOCH + Duration::new(0, 0)).to_string(),
476 "1970-01-01T00:00:00.000000Z"
477 );
478 assert_eq!(
479 format_rfc3339_micros(UNIX_EPOCH + Duration::new(1_518_563_312, 123_000_000))
480 .to_string(),
481 "2018-02-13T23:08:32.123000Z"
482 );
483 assert_eq!(
484 format_rfc3339_micros(UNIX_EPOCH + Duration::new(1_518_563_312, 456_123_000))
485 .to_string(),
486 "2018-02-13T23:08:32.456123Z"
487 );
488 }
489
490 #[test]
491 fn smoke_tests_format_nanos() {
492 assert_eq!(
493 format_rfc3339_nanos(UNIX_EPOCH + Duration::new(0, 0)).to_string(),
494 "1970-01-01T00:00:00.000000000Z"
495 );
496 assert_eq!(
497 format_rfc3339_nanos(UNIX_EPOCH + Duration::new(1_518_563_312, 123_000_000))
498 .to_string(),
499 "2018-02-13T23:08:32.123000000Z"
500 );
501 #[cfg(not(target_os = "windows"))]
502 assert_eq!(
503 format_rfc3339_nanos(UNIX_EPOCH + Duration::new(1_518_563_312, 789_456_123))
504 .to_string(),
505 "2018-02-13T23:08:32.789456123Z"
506 );
507 #[cfg(target_os = "windows")] assert_eq!(
509 format_rfc3339_nanos(UNIX_EPOCH + Duration::new(1_518_563_312, 789_456_123))
510 .to_string(),
511 "2018-02-13T23:08:32.789456100Z"
512 );
513 }
514
515 #[test]
516 fn upper_bound() {
517 let max = UNIX_EPOCH + Duration::new(max::SECONDS, 0);
518 assert_eq!(parse_rfc3339(max::TIMESTAMP).unwrap(), max);
519 assert_eq!(format_rfc3339(max).to_string(), max::TIMESTAMP);
520 }
521
522 #[test]
523 fn leap_second() {
524 assert_eq!(
525 parse_rfc3339("2016-12-31T23:59:60Z").unwrap(),
526 UNIX_EPOCH + Duration::new(1_483_228_799, 0)
527 );
528 }
529
530 #[test]
531 fn first_731_days() {
532 let year_start = 0; for day in 0..=365 * 2 {
534 let (s, time) = from_sec(year_start + day * 86400);
536 assert_eq!(parse_rfc3339(&s).unwrap(), time);
537 assert_eq!(format_rfc3339(time).to_string(), s);
538 }
539 }
540
541 #[test]
542 fn the_731_consecutive_days() {
543 let year_start = 1_325_376_000; for day in 0..=365 * 2 {
545 let (s, time) = from_sec(year_start + day * 86400);
547 assert_eq!(parse_rfc3339(&s).unwrap(), time);
548 assert_eq!(format_rfc3339(time).to_string(), s);
549 }
550 }
551
552 #[test]
553 fn all_86400_seconds() {
554 let day_start = 1_325_376_000;
555 for second in 0..86400 {
556 let (s, time) = from_sec(day_start + second);
558 assert_eq!(parse_rfc3339(&s).unwrap(), time);
559 assert_eq!(format_rfc3339(time).to_string(), s);
560 }
561 }
562
563 #[test]
564 fn random_past() {
565 let upper = SystemTime::now()
566 .duration_since(UNIX_EPOCH)
567 .unwrap()
568 .as_secs();
569 for _ in 0..10000 {
570 let sec = rand::rng().random_range(0..upper);
571 let (s, time) = from_sec(sec);
572 assert_eq!(parse_rfc3339(&s).unwrap(), time);
573 assert_eq!(format_rfc3339(time).to_string(), s);
574 }
575 }
576
577 #[test]
578 fn random_wide_range() {
579 for _ in 0..100_000 {
580 let sec = rand::rng().random_range(0..max::SECONDS);
581 let (s, time) = from_sec(sec);
582 assert_eq!(parse_rfc3339(&s).unwrap(), time);
583 assert_eq!(format_rfc3339(time).to_string(), s);
584 }
585 }
586
587 #[test]
588 fn milliseconds() {
589 assert_eq!(
590 parse_rfc3339("1970-01-01T00:00:00.123Z").unwrap(),
591 UNIX_EPOCH + Duration::new(0, 123_000_000)
592 );
593 assert_eq!(
594 format_rfc3339(UNIX_EPOCH + Duration::new(0, 123_000_000)).to_string(),
595 "1970-01-01T00:00:00.123000000Z"
596 );
597 }
598
599 #[test]
600 #[should_panic(expected = "OutOfRange")]
601 fn zero_month() {
602 parse_rfc3339("1970-00-01T00:00:00Z").unwrap();
603 }
604
605 #[test]
606 #[should_panic(expected = "OutOfRange")]
607 fn big_month() {
608 parse_rfc3339("1970-32-01T00:00:00Z").unwrap();
609 }
610
611 #[test]
612 #[should_panic(expected = "OutOfRange")]
613 fn zero_day() {
614 parse_rfc3339("1970-01-00T00:00:00Z").unwrap();
615 }
616
617 #[test]
618 #[should_panic(expected = "OutOfRange")]
619 fn big_day() {
620 parse_rfc3339("1970-12-35T00:00:00Z").unwrap();
621 }
622
623 #[test]
624 #[should_panic(expected = "OutOfRange")]
625 fn big_day2() {
626 parse_rfc3339("1970-02-30T00:00:00Z").unwrap();
627 }
628
629 #[test]
630 #[should_panic(expected = "OutOfRange")]
631 fn big_second() {
632 parse_rfc3339("1970-12-30T00:00:78Z").unwrap();
633 }
634
635 #[test]
636 #[should_panic(expected = "OutOfRange")]
637 fn big_minute() {
638 parse_rfc3339("1970-12-30T00:78:00Z").unwrap();
639 }
640
641 #[test]
642 #[should_panic(expected = "OutOfRange")]
643 fn big_hour() {
644 parse_rfc3339("1970-12-30T24:00:00Z").unwrap();
645 }
646
647 #[test]
648 fn break_data() {
649 for pos in 0.."2016-12-31T23:59:60Z".len() {
650 let mut s = b"2016-12-31T23:59:60Z".to_vec();
651 s[pos] = b'x';
652 parse_rfc3339(from_utf8(&s).unwrap()).unwrap_err();
653 }
654 }
655
656 #[test]
657 fn weak_smoke_tests() {
658 assert_eq!(
659 parse_rfc3339_weak("1970-01-01 00:00:00").unwrap(),
660 UNIX_EPOCH + Duration::new(0, 0)
661 );
662 parse_rfc3339("1970-01-01 00:00:00").unwrap_err();
663
664 assert_eq!(
665 parse_rfc3339_weak("1970-01-01 00:00:00.000123").unwrap(),
666 UNIX_EPOCH + Duration::new(0, 123_000)
667 );
668 parse_rfc3339("1970-01-01 00:00:00.000123").unwrap_err();
669
670 assert_eq!(
671 parse_rfc3339_weak("1970-01-01T00:00:00.000123").unwrap(),
672 UNIX_EPOCH + Duration::new(0, 123_000)
673 );
674 parse_rfc3339("1970-01-01T00:00:00.000123").unwrap_err();
675
676 assert_eq!(
677 parse_rfc3339_weak("1970-01-01 00:00:00.000123Z").unwrap(),
678 UNIX_EPOCH + Duration::new(0, 123_000)
679 );
680 parse_rfc3339("1970-01-01 00:00:00.000123Z").unwrap_err();
681
682 assert_eq!(
683 parse_rfc3339_weak("1970-01-01 00:00:00Z").unwrap(),
684 UNIX_EPOCH + Duration::new(0, 0)
685 );
686 parse_rfc3339("1970-01-01 00:00:00Z").unwrap_err();
687 }
688
689 #[test]
690 fn parse_offset_00() {
691 assert_eq!(
692 parse_rfc3339("1970-01-01T00:00:00+00:00").unwrap(),
693 UNIX_EPOCH + Duration::new(0, 0)
694 );
695 assert_eq!(
696 parse_rfc3339("1970-01-01T00:00:01+00:00").unwrap(),
697 UNIX_EPOCH + Duration::new(1, 0)
698 );
699 assert_eq!(
700 parse_rfc3339("2018-02-13T23:08:32+00:00").unwrap(),
701 UNIX_EPOCH + Duration::new(1_518_563_312, 0)
702 );
703 assert_eq!(
704 parse_rfc3339("2012-01-01T00:00:00+00:00").unwrap(),
705 UNIX_EPOCH + Duration::new(1_325_376_000, 0)
706 );
707
708 assert_eq!(
710 parse_rfc3339("2012-01-01T00:00:00 +00:00"),
711 Err(super::Error::InvalidFormat)
712 );
713 assert_eq!(
714 parse_rfc3339("2012-01-01T00:00:00+00"),
715 Err(super::Error::InvalidFormat)
716 );
717 }
718
719 #[test]
720 fn weak_parse_offset_00() {
721 assert_eq!(
722 parse_rfc3339_weak("1970-01-01 00:00:00+00:00").unwrap(),
723 UNIX_EPOCH + Duration::new(0, 0)
724 );
725 assert_eq!(
726 parse_rfc3339_weak("1970-01-01 00:00:00.000123+00:00").unwrap(),
727 UNIX_EPOCH + Duration::new(0, 123_000)
728 );
729 assert_eq!(
730 parse_rfc3339_weak("1970-01-01T00:00:00.000123+00:00").unwrap(),
731 UNIX_EPOCH + Duration::new(0, 123_000)
732 );
733
734 parse_rfc3339("2012-01-01T+00:00:00").unwrap_err();
736 parse_rfc3339("1970-01-01 00:00:00.00+0123").unwrap_err();
737 parse_rfc3339("1970-01-01 00:00:00.0000123+00:00").unwrap_err();
738 parse_rfc3339("1970-01-01 00:00:00.0000123+00:00abcd").unwrap_err();
739 parse_rfc3339("1970-01-01 00:00:00.0000123+02:00").unwrap_err();
740 parse_rfc3339("1970-01-01 00:00:00.0000123+00").unwrap_err();
741 parse_rfc3339("1970-01-01 00:00:00.0000123+").unwrap_err();
742 }
743}