humantime/
date.rs

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