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 features:
80/// - Any precision of fractional digits `2018-02-14T00:28:07.133Z`.
81/// - The UTC timezone can be indicated with `Z` or `+00:00`.
82///
83/// Unsupported feature: localized timestamps. Only UTC is supported.
84pub 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
95/// Parse RFC3339-like timestamp `2018-02-14 00:28:07`
96///
97/// Supported features:
98///
99/// 1. Any precision of fractional digits `2018-02-14 00:28:07.133`.
100/// 2. Supports timestamp with or without either of `T`, `Z` or `+00:00`.
101/// 3. Anything valid for [`parse_rfc3339`](parse_rfc3339) is valid for this function
102///
103/// Unsupported feature: localized timestamps. Only UTC is supported, even if
104/// `Z` is not specified.
105///
106/// This function is intended to use for parsing human input. Whereas
107/// `parse_rfc3339` is for strings generated programmatically.
108pub 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(); // for careless slicing
113    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    // TODO(tailhook) should we check that leaps second is only on midnight ?
132    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                // start of "+00:00", which must be at the end
178                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
203/// Format an RFC3339 timestamp `2018-02-14T00:28:07Z`
204///
205/// This function formats timestamp with smart precision: i.e. if it has no
206/// fractional seconds, they aren't written at all. And up to nine digits if
207/// they are.
208///
209/// The value is always UTC and ignores system timezone.
210pub fn format_rfc3339(system_time: SystemTime) -> Rfc3339Timestamp {
211    Rfc3339Timestamp(system_time, Precision::Smart)
212}
213
214/// Format an RFC3339 timestamp `2018-02-14T00:28:07Z`
215///
216/// This format always shows timestamp without fractional seconds.
217///
218/// The value is always UTC and ignores system timezone.
219pub fn format_rfc3339_seconds(system_time: SystemTime) -> Rfc3339Timestamp {
220    Rfc3339Timestamp(system_time, Precision::Seconds)
221}
222
223/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000Z`
224///
225/// This format always shows milliseconds even if millisecond value is zero.
226///
227/// The value is always UTC and ignores system timezone.
228pub fn format_rfc3339_millis(system_time: SystemTime) -> Rfc3339Timestamp {
229    Rfc3339Timestamp(system_time, Precision::Millis)
230}
231
232/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000000Z`
233///
234/// This format always shows microseconds even if microsecond value is zero.
235///
236/// The value is always UTC and ignores system timezone.
237pub fn format_rfc3339_micros(system_time: SystemTime) -> Rfc3339Timestamp {
238    Rfc3339Timestamp(system_time, Precision::Micros)
239}
240
241/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000000000Z`
242///
243/// This format always shows nanoseconds even if nanosecond value is zero.
244///
245/// The value is always UTC and ignores system timezone.
246pub fn format_rfc3339_nanos(system_time: SystemTime) -> Rfc3339Timestamp {
247    Rfc3339Timestamp(system_time, Precision::Nanos)
248}
249
250impl Rfc3339Timestamp {
251    /// Returns a reference to the [`SystemTime`][] that is being formatted.
252    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            // year 9999
270            return Err(fmt::Error);
271        }
272
273        /* 2000-03-01 (mod 400 year, immediately after feb29 */
274        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            // 29th is 'Z'
374            29
375        };
376
377        // we know our chars are all ascii
378        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        // next second
409        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")] // Not sure what is up with Windows rounding?
508        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; // 1970
533        for day in 0..=365 * 2 {
534            // scan leap year and non-leap year
535            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; // 2012
544        for day in 0..=365 * 2 {
545            // scan leap year and non-leap year
546            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            // scan leap year and non-leap year
557            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        // invalid
709        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        // invalid
735        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}