humantime/
date.rs

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 fmt::Display for Rfc3339Timestamp {
244    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
245        use self::Precision::*;
246
247        let dur = self.0.duration_since(UNIX_EPOCH)
248            .expect("all times should be after the epoch");
249        let secs_since_epoch = dur.as_secs();
250        let nanos = dur.subsec_nanos();
251
252        if secs_since_epoch >= 253_402_300_800 { // year 9999
253            return Err(fmt::Error);
254        }
255
256        /* 2000-03-01 (mod 400 year, immediately after feb29 */
257        const LEAPOCH: i64 = 11017;
258        const DAYS_PER_400Y: i64 = 365*400 + 97;
259        const DAYS_PER_100Y: i64 = 365*100 + 24;
260        const DAYS_PER_4Y: i64 = 365*4 + 1;
261
262        let days = (secs_since_epoch / 86400) as i64 - LEAPOCH;
263        let secs_of_day = secs_since_epoch % 86400;
264
265        let mut qc_cycles = days / DAYS_PER_400Y;
266        let mut remdays = days % DAYS_PER_400Y;
267
268        if remdays < 0 {
269            remdays += DAYS_PER_400Y;
270            qc_cycles -= 1;
271        }
272
273        let mut c_cycles = remdays / DAYS_PER_100Y;
274        if c_cycles == 4 { c_cycles -= 1; }
275        remdays -= c_cycles * DAYS_PER_100Y;
276
277        let mut q_cycles = remdays / DAYS_PER_4Y;
278        if q_cycles == 25 { q_cycles -= 1; }
279        remdays -= q_cycles * DAYS_PER_4Y;
280
281        let mut remyears = remdays / 365;
282        if remyears == 4 { remyears -= 1; }
283        remdays -= remyears * 365;
284
285        let mut year = 2000 +
286            remyears + 4*q_cycles + 100*c_cycles + 400*qc_cycles;
287
288        let months = [31,30,31,30,31,31,30,31,30,31,31,29];
289        let mut mon = 0;
290        for mon_len in months.iter() {
291            mon += 1;
292            if remdays < *mon_len {
293                break;
294            }
295            remdays -= *mon_len;
296        }
297        let mday = remdays+1;
298        let mon = if mon + 2 > 12 {
299            year += 1;
300            mon - 10
301        } else {
302            mon + 2
303        };
304
305        let mut buf: [u8; 30] = [
306            // Too long to write as: b"0000-00-00T00:00:00.000000000Z"
307            b'0', b'0', b'0', b'0', b'-', b'0', b'0', b'-', b'0', b'0', b'T',
308            b'0', b'0', b':', b'0', b'0', b':', b'0', b'0',
309            b'.', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'Z',
310        ];
311        buf[0] = b'0' + (year / 1000) as u8;
312        buf[1] = b'0' + (year / 100 % 10) as u8;
313        buf[2] = b'0' + (year / 10 % 10) as u8;
314        buf[3] = b'0' + (year % 10) as u8;
315        buf[5] = b'0' + (mon / 10) as u8;
316        buf[6] = b'0' + (mon % 10) as u8;
317        buf[8] = b'0' + (mday / 10) as u8;
318        buf[9] = b'0' + (mday % 10) as u8;
319        buf[11] = b'0' + (secs_of_day / 3600 / 10) as u8;
320        buf[12] = b'0' + (secs_of_day / 3600 % 10) as u8;
321        buf[14] = b'0' + (secs_of_day / 60 / 10 % 6) as u8;
322        buf[15] = b'0' + (secs_of_day / 60 % 10) as u8;
323        buf[17] = b'0' + (secs_of_day / 10 % 6) as u8;
324        buf[18] = b'0' + (secs_of_day % 10) as u8;
325
326        let offset = if self.1 == Seconds || nanos == 0 && self.1 == Smart {
327            buf[19] = b'Z';
328            19
329        } else if self.1 == Millis {
330            buf[20] = b'0' + (nanos / 100_000_000) as u8;
331            buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
332            buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
333            buf[23] = b'Z';
334            23
335        } else if self.1 == Micros {
336            buf[20] = b'0' + (nanos / 100_000_000) as u8;
337            buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
338            buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
339            buf[23] = b'0' + (nanos / 100_000 % 10) as u8;
340            buf[24] = b'0' + (nanos / 10_000 % 10) as u8;
341            buf[25] = b'0' + (nanos / 1_000 % 10) as u8;
342            buf[26] = b'Z';
343            26
344        } else {
345            buf[20] = b'0' + (nanos / 100_000_000) as u8;
346            buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
347            buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
348            buf[23] = b'0' + (nanos / 100_000 % 10) as u8;
349            buf[24] = b'0' + (nanos / 10_000 % 10) as u8;
350            buf[25] = b'0' + (nanos / 1_000 % 10) as u8;
351            buf[26] = b'0' + (nanos / 100 % 10) as u8;
352            buf[27] = b'0' + (nanos / 10 % 10) as u8;
353            buf[28] = b'0' + (nanos / 1 % 10) as u8;
354            // 29th is 'Z'
355            29
356        };
357
358        // we know our chars are all ascii
359        f.write_str(str::from_utf8(&buf[..=offset]).expect("Conversion to utf8 failed"))
360    }
361}
362
363#[cfg(test)]
364mod test {
365    use std::str::from_utf8;
366    use std::time::{UNIX_EPOCH, SystemTime, Duration};
367
368    use rand::Rng;
369
370    use super::{parse_rfc3339, parse_rfc3339_weak, format_rfc3339};
371    use super::{format_rfc3339_millis, format_rfc3339_micros};
372    use super::{format_rfc3339_nanos};
373    use super::max;
374
375    fn from_sec(sec: u64) -> (String, SystemTime) {
376        let s = time::at_utc(time::Timespec { sec: sec as i64, nsec: 0 })
377                  .rfc3339().to_string();
378        let time = UNIX_EPOCH + Duration::new(sec, 0);
379        (s, time)
380    }
381
382    #[test]
383    #[cfg(all(target_pointer_width="32", target_os="linux"))]
384    fn year_after_2038_fails_gracefully() {
385        // next second
386        assert_eq!(parse_rfc3339("2038-01-19T03:14:08Z").unwrap_err(),
387                   super::Error::OutOfRange);
388        assert_eq!(parse_rfc3339("9999-12-31T23:59:59Z").unwrap_err(),
389                   super::Error::OutOfRange);
390    }
391
392    #[test]
393    fn smoke_tests_parse() {
394        assert_eq!(parse_rfc3339("1970-01-01T00:00:00Z").unwrap(),
395                   UNIX_EPOCH + Duration::new(0, 0));
396        assert_eq!(parse_rfc3339("1970-01-01T00:00:01Z").unwrap(),
397                   UNIX_EPOCH + Duration::new(1, 0));
398        assert_eq!(parse_rfc3339("2018-02-13T23:08:32Z").unwrap(),
399                   UNIX_EPOCH + Duration::new(1_518_563_312, 0));
400        assert_eq!(parse_rfc3339("2012-01-01T00:00:00Z").unwrap(),
401                   UNIX_EPOCH + Duration::new(1_325_376_000, 0));
402    }
403
404    #[test]
405    fn smoke_tests_format() {
406        assert_eq!(
407            format_rfc3339(UNIX_EPOCH + Duration::new(0, 0)).to_string(),
408            "1970-01-01T00:00:00Z");
409        assert_eq!(
410            format_rfc3339(UNIX_EPOCH + Duration::new(1, 0)).to_string(),
411            "1970-01-01T00:00:01Z");
412        assert_eq!(
413            format_rfc3339(UNIX_EPOCH + Duration::new(1_518_563_312, 0)).to_string(),
414            "2018-02-13T23:08:32Z");
415        assert_eq!(
416            format_rfc3339(UNIX_EPOCH + Duration::new(1_325_376_000, 0)).to_string(),
417            "2012-01-01T00:00:00Z");
418    }
419
420    #[test]
421    fn smoke_tests_format_millis() {
422        assert_eq!(
423            format_rfc3339_millis(UNIX_EPOCH +
424                Duration::new(0, 0)).to_string(),
425            "1970-01-01T00:00:00.000Z");
426        assert_eq!(
427            format_rfc3339_millis(UNIX_EPOCH +
428                Duration::new(1_518_563_312, 123_000_000)).to_string(),
429            "2018-02-13T23:08:32.123Z");
430    }
431
432    #[test]
433    fn smoke_tests_format_micros() {
434        assert_eq!(
435            format_rfc3339_micros(UNIX_EPOCH +
436                Duration::new(0, 0)).to_string(),
437            "1970-01-01T00:00:00.000000Z");
438        assert_eq!(
439            format_rfc3339_micros(UNIX_EPOCH +
440                Duration::new(1_518_563_312, 123_000_000)).to_string(),
441            "2018-02-13T23:08:32.123000Z");
442        assert_eq!(
443            format_rfc3339_micros(UNIX_EPOCH +
444                Duration::new(1_518_563_312, 456_123_000)).to_string(),
445            "2018-02-13T23:08:32.456123Z");
446    }
447
448    #[test]
449    fn smoke_tests_format_nanos() {
450        assert_eq!(
451            format_rfc3339_nanos(UNIX_EPOCH +
452                Duration::new(0, 0)).to_string(),
453            "1970-01-01T00:00:00.000000000Z");
454        assert_eq!(
455            format_rfc3339_nanos(UNIX_EPOCH +
456                Duration::new(1_518_563_312, 123_000_000)).to_string(),
457            "2018-02-13T23:08:32.123000000Z");
458        assert_eq!(
459            format_rfc3339_nanos(UNIX_EPOCH +
460                Duration::new(1_518_563_312, 789_456_123)).to_string(),
461            "2018-02-13T23:08:32.789456123Z");
462    }
463
464    #[test]
465    fn upper_bound() {
466        let max = UNIX_EPOCH + Duration::new(max::SECONDS, 0);
467        assert_eq!(parse_rfc3339(&max::TIMESTAMP).unwrap(), max);
468        assert_eq!(format_rfc3339(max).to_string(), max::TIMESTAMP);
469    }
470
471    #[test]
472    fn leap_second() {
473        assert_eq!(parse_rfc3339("2016-12-31T23:59:60Z").unwrap(),
474                   UNIX_EPOCH + Duration::new(1_483_228_799, 0));
475    }
476
477    #[test]
478    fn first_731_days() {
479        let year_start = 0;  // 1970
480        for day in 0..= 365 * 2 {  // scan leap year and non-leap year
481            let (s, time) = from_sec(year_start + day * 86400);
482            assert_eq!(parse_rfc3339(&s).unwrap(), time);
483            assert_eq!(format_rfc3339(time).to_string(), s);
484        }
485    }
486
487    #[test]
488    fn the_731_consecutive_days() {
489        let year_start = 1_325_376_000;  // 2012
490        for day in 0..= 365 * 2 {  // scan leap year and non-leap year
491            let (s, time) = from_sec(year_start + day * 86400);
492            assert_eq!(parse_rfc3339(&s).unwrap(), time);
493            assert_eq!(format_rfc3339(time).to_string(), s);
494        }
495    }
496
497    #[test]
498    fn all_86400_seconds() {
499        let day_start = 1_325_376_000;
500        for second in 0..86400 {  // scan leap year and non-leap year
501            let (s, time) = from_sec(day_start + second);
502            assert_eq!(parse_rfc3339(&s).unwrap(), time);
503            assert_eq!(format_rfc3339(time).to_string(), s);
504        }
505    }
506
507    #[test]
508    fn random_past() {
509        let upper = SystemTime::now().duration_since(UNIX_EPOCH).unwrap()
510            .as_secs();
511        for _ in 0..10000 {
512            let sec = rand::thread_rng().gen_range(0, upper);
513            let (s, time) = from_sec(sec);
514            assert_eq!(parse_rfc3339(&s).unwrap(), time);
515            assert_eq!(format_rfc3339(time).to_string(), s);
516        }
517    }
518
519    #[test]
520    fn random_wide_range() {
521        for _ in 0..100_000 {
522            let sec = rand::thread_rng().gen_range(0, max::SECONDS);
523            let (s, time) = from_sec(sec);
524            assert_eq!(parse_rfc3339(&s).unwrap(), time);
525            assert_eq!(format_rfc3339(time).to_string(), s);
526        }
527    }
528
529    #[test]
530    fn milliseconds() {
531        assert_eq!(parse_rfc3339("1970-01-01T00:00:00.123Z").unwrap(),
532                   UNIX_EPOCH + Duration::new(0, 123_000_000));
533        assert_eq!(format_rfc3339(UNIX_EPOCH + Duration::new(0, 123_000_000))
534            .to_string(), "1970-01-01T00:00:00.123000000Z");
535    }
536
537    #[test]
538    #[should_panic(expected="OutOfRange")]
539    fn zero_month() {
540        parse_rfc3339("1970-00-01T00:00:00Z").unwrap();
541    }
542
543    #[test]
544    #[should_panic(expected="OutOfRange")]
545    fn big_month() {
546        parse_rfc3339("1970-32-01T00:00:00Z").unwrap();
547    }
548
549    #[test]
550    #[should_panic(expected="OutOfRange")]
551    fn zero_day() {
552        parse_rfc3339("1970-01-00T00:00:00Z").unwrap();
553    }
554
555    #[test]
556    #[should_panic(expected="OutOfRange")]
557    fn big_day() {
558        parse_rfc3339("1970-12-35T00:00:00Z").unwrap();
559    }
560
561    #[test]
562    #[should_panic(expected="OutOfRange")]
563    fn big_day2() {
564        parse_rfc3339("1970-02-30T00:00:00Z").unwrap();
565    }
566
567    #[test]
568    #[should_panic(expected="OutOfRange")]
569    fn big_second() {
570        parse_rfc3339("1970-12-30T00:00:78Z").unwrap();
571    }
572
573    #[test]
574    #[should_panic(expected="OutOfRange")]
575    fn big_minute() {
576        parse_rfc3339("1970-12-30T00:78:00Z").unwrap();
577    }
578
579    #[test]
580    #[should_panic(expected="OutOfRange")]
581    fn big_hour() {
582        parse_rfc3339("1970-12-30T24:00:00Z").unwrap();
583    }
584
585    #[test]
586    fn break_data() {
587        for pos in 0.."2016-12-31T23:59:60Z".len() {
588            let mut s = b"2016-12-31T23:59:60Z".to_vec();
589            s[pos] = b'x';
590            parse_rfc3339(from_utf8(&s).unwrap()).unwrap_err();
591        }
592    }
593
594    #[test]
595    fn weak_smoke_tests() {
596        assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00").unwrap(),
597                   UNIX_EPOCH + Duration::new(0, 0));
598        parse_rfc3339("1970-01-01 00:00:00").unwrap_err();
599
600        assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00.000123").unwrap(),
601                   UNIX_EPOCH + Duration::new(0, 123_000));
602        parse_rfc3339("1970-01-01 00:00:00.000123").unwrap_err();
603
604        assert_eq!(parse_rfc3339_weak("1970-01-01T00:00:00.000123").unwrap(),
605                   UNIX_EPOCH + Duration::new(0, 123_000));
606        parse_rfc3339("1970-01-01T00:00:00.000123").unwrap_err();
607
608        assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00.000123Z").unwrap(),
609                   UNIX_EPOCH + Duration::new(0, 123_000));
610        parse_rfc3339("1970-01-01 00:00:00.000123Z").unwrap_err();
611
612        assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00Z").unwrap(),
613                   UNIX_EPOCH + Duration::new(0, 0));
614        parse_rfc3339("1970-01-01 00:00:00Z").unwrap_err();
615    }
616}