prost_types/
datetime.rs

1//! A date/time type which exists primarily to convert [`Timestamp`]s into an RFC 3339 formatted
2//! string.
3
4use core::fmt;
5
6use crate::Duration;
7use crate::Timestamp;
8use crate::TimestampError;
9
10/// A point in time, represented as a date and time in the UTC timezone.
11#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
12pub(crate) struct DateTime {
13    /// The year.
14    pub(crate) year: i64,
15    /// The month of the year, from 1 to 12, inclusive.
16    pub(crate) month: u8,
17    /// The day of the month, from 1 to 31, inclusive.
18    pub(crate) day: u8,
19    /// The hour of the day, from 0 to 23, inclusive.
20    pub(crate) hour: u8,
21    /// The minute of the hour, from 0 to 59, inclusive.
22    pub(crate) minute: u8,
23    /// The second of the minute, from 0 to 59, inclusive.
24    pub(crate) second: u8,
25    /// The nanoseconds, from 0 to 999_999_999, inclusive.
26    pub(crate) nanos: u32,
27}
28
29impl DateTime {
30    /// The minimum representable [`Timestamp`] as a `DateTime`.
31    pub(crate) const MIN: DateTime = DateTime {
32        year: -292_277_022_657,
33        month: 1,
34        day: 27,
35        hour: 8,
36        minute: 29,
37        second: 52,
38        nanos: 0,
39    };
40
41    /// The maximum representable [`Timestamp`] as a `DateTime`.
42    pub(crate) const MAX: DateTime = DateTime {
43        year: 292_277_026_596,
44        month: 12,
45        day: 4,
46        hour: 15,
47        minute: 30,
48        second: 7,
49        nanos: 999_999_999,
50    };
51
52    /// Returns `true` if the `DateTime` is a valid calendar date.
53    pub(crate) fn is_valid(&self) -> bool {
54        self >= &DateTime::MIN
55            && self <= &DateTime::MAX
56            && self.month > 0
57            && self.month <= 12
58            && self.day > 0
59            && self.day <= days_in_month(self.year, self.month)
60            && self.hour < 24
61            && self.minute < 60
62            && self.second < 60
63            && self.nanos < 1_000_000_000
64    }
65}
66
67impl fmt::Display for DateTime {
68    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69        // Pad years to at least 4 digits.
70        if self.year > 9999 {
71            write!(f, "+{}", self.year)?;
72        } else if self.year < 0 {
73            write!(f, "{:05}", self.year)?;
74        } else {
75            write!(f, "{:04}", self.year)?;
76        };
77
78        write!(
79            f,
80            "-{:02}-{:02}T{:02}:{:02}:{:02}",
81            self.month, self.day, self.hour, self.minute, self.second,
82        )?;
83
84        // Format subseconds to either nothing, millis, micros, or nanos.
85        let nanos = self.nanos;
86        if nanos == 0 {
87            write!(f, "Z")
88        } else if nanos % 1_000_000 == 0 {
89            write!(f, ".{:03}Z", nanos / 1_000_000)
90        } else if nanos % 1_000 == 0 {
91            write!(f, ".{:06}Z", nanos / 1_000)
92        } else {
93            write!(f, ".{:09}Z", nanos)
94        }
95    }
96}
97
98impl From<Timestamp> for DateTime {
99    /// musl's [`__secs_to_tm`][1] converted to Rust via [c2rust][2] and then cleaned up by hand.
100    ///
101    /// All existing `strftime`-like APIs in Rust are unable to handle the full range of timestamps
102    /// representable by `Timestamp`, including `strftime` itself, since tm.tm_year is an int.
103    ///
104    /// [1]: http://git.musl-libc.org/cgit/musl/tree/src/time/__secs_to_tm.c
105    /// [2]: https://c2rust.com/
106    fn from(mut timestamp: Timestamp) -> DateTime {
107        timestamp.normalize();
108
109        let t = timestamp.seconds;
110        let nanos = timestamp.nanos;
111
112        // 2000-03-01 (mod 400 year, immediately after feb29
113        const LEAPOCH: i64 = 946_684_800 + 86400 * (31 + 29);
114        const DAYS_PER_400Y: i32 = 365 * 400 + 97;
115        const DAYS_PER_100Y: i32 = 365 * 100 + 24;
116        const DAYS_PER_4Y: i32 = 365 * 4 + 1;
117        const DAYS_IN_MONTH: [u8; 12] = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
118
119        // Note(dcb): this bit is rearranged slightly to avoid integer overflow.
120        let mut days: i64 = (t / 86_400) - (LEAPOCH / 86_400);
121        let mut remsecs: i32 = (t % 86_400) as i32;
122        if remsecs < 0i32 {
123            remsecs += 86_400;
124            days -= 1
125        }
126
127        let mut qc_cycles: i32 = (days / i64::from(DAYS_PER_400Y)) as i32;
128        let mut remdays: i32 = (days % i64::from(DAYS_PER_400Y)) as i32;
129        if remdays < 0 {
130            remdays += DAYS_PER_400Y;
131            qc_cycles -= 1;
132        }
133
134        let mut c_cycles: i32 = remdays / DAYS_PER_100Y;
135        if c_cycles == 4 {
136            c_cycles -= 1;
137        }
138        remdays -= c_cycles * DAYS_PER_100Y;
139
140        let mut q_cycles: i32 = remdays / DAYS_PER_4Y;
141        if q_cycles == 25 {
142            q_cycles -= 1;
143        }
144        remdays -= q_cycles * DAYS_PER_4Y;
145
146        let mut remyears: i32 = remdays / 365;
147        if remyears == 4 {
148            remyears -= 1;
149        }
150        remdays -= remyears * 365;
151
152        let mut years: i64 = i64::from(remyears)
153            + 4 * i64::from(q_cycles)
154            + 100 * i64::from(c_cycles)
155            + 400 * i64::from(qc_cycles);
156
157        let mut months: i32 = 0;
158        while i32::from(DAYS_IN_MONTH[months as usize]) <= remdays {
159            remdays -= i32::from(DAYS_IN_MONTH[months as usize]);
160            months += 1
161        }
162
163        if months >= 10 {
164            months -= 12;
165            years += 1;
166        }
167
168        let date_time = DateTime {
169            year: years + 2000,
170            month: (months + 3) as u8,
171            day: (remdays + 1) as u8,
172            hour: (remsecs / 3600) as u8,
173            minute: (remsecs / 60 % 60) as u8,
174            second: (remsecs % 60) as u8,
175            nanos: nanos as u32,
176        };
177        debug_assert!(date_time.is_valid());
178        date_time
179    }
180}
181
182/// Returns the number of days in the month.
183fn days_in_month(year: i64, month: u8) -> u8 {
184    const DAYS_IN_MONTH: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
185    let (_, is_leap) = year_to_seconds(year);
186    DAYS_IN_MONTH[usize::from(month - 1)] + u8::from(is_leap && month == 2)
187}
188
189macro_rules! ensure {
190    ($expr:expr) => {{
191        if !$expr {
192            return None;
193        }
194    }};
195}
196
197/// Parses a date in RFC 3339 format from ASCII string `b`, returning the year, month, day, and
198/// remaining input.
199///
200/// The date is not validated according to a calendar.
201fn parse_date(s: &str) -> Option<(i64, u8, u8, &str)> {
202    debug_assert!(s.is_ascii());
203
204    // Smallest valid date is YYYY-MM-DD.
205    ensure!(s.len() >= 10);
206
207    // Parse the year in one of three formats:
208    //  * +YYYY[Y]+
209    //  * -[Y]+
210    //  * YYYY
211    let (year, s) = match s.as_bytes()[0] {
212        b'+' => {
213            let (digits, s) = parse_digits(&s[1..]);
214            ensure!(digits.len() >= 5);
215            let date: i64 = digits.parse().ok()?;
216            (date, s)
217        }
218        b'-' => {
219            let (digits, s) = parse_digits(&s[1..]);
220            ensure!(digits.len() >= 4);
221            let date: i64 = digits.parse().ok()?;
222            (-date, s)
223        }
224        _ => {
225            // Parse a 4 digit numeric.
226            let (n1, s) = parse_two_digit_numeric(s)?;
227            let (n2, s) = parse_two_digit_numeric(s)?;
228            (i64::from(n1) * 100 + i64::from(n2), s)
229        }
230    };
231
232    let s = parse_char(s, b'-')?;
233    let (month, s) = parse_two_digit_numeric(s)?;
234    let s = parse_char(s, b'-')?;
235    let (day, s) = parse_two_digit_numeric(s)?;
236    Some((year, month, day, s))
237}
238
239/// Parses a time in RFC 3339 format from ASCII string `s`, returning the hour, minute, second, and
240/// nanos.
241///
242/// The date is not validated according to a calendar.
243fn parse_time(s: &str) -> Option<(u8, u8, u8, u32, &str)> {
244    debug_assert!(s.is_ascii());
245
246    let (hour, s) = parse_two_digit_numeric(s)?;
247    let s = parse_char(s, b':')?;
248    let (minute, s) = parse_two_digit_numeric(s)?;
249    let s = parse_char(s, b':')?;
250    let (second, s) = parse_two_digit_numeric(s)?;
251
252    let (nanos, s) = parse_nanos(s)?;
253
254    Some((hour, minute, second, nanos, s))
255}
256
257/// Parses an optional nanosecond time from ASCII string `s`, returning the nanos and remaining
258/// string.
259fn parse_nanos(s: &str) -> Option<(u32, &str)> {
260    debug_assert!(s.is_ascii());
261
262    // Parse the nanoseconds, if present.
263    let (nanos, s) = if let Some(s) = parse_char(s, b'.') {
264        let (mut digits, s) = parse_digits(s);
265        if digits.len() > 9 {
266            digits = digits.split_at(9).0;
267        }
268        let nanos = 10u32.pow(9 - digits.len() as u32) * digits.parse::<u32>().ok()?;
269        (nanos, s)
270    } else {
271        (0, s)
272    };
273
274    Some((nanos, s))
275}
276
277/// Parses a timezone offset in RFC 3339 format from ASCII string `s`, returning the offset hour,
278/// offset minute, and remaining input.
279fn parse_offset(s: &str) -> Option<(i8, i8, &str)> {
280    debug_assert!(s.is_ascii());
281
282    if s.is_empty() {
283        // If no timezone specified, assume UTC.
284        return Some((0, 0, s));
285    }
286
287    // Snowflake's timestamp format contains a space separator before the offset.
288    let s = parse_char(s, b' ').unwrap_or(s);
289
290    if let Some(s) = parse_char_ignore_case(s, b'Z') {
291        Some((0, 0, s))
292    } else {
293        let (is_positive, s) = if let Some(s) = parse_char(s, b'+') {
294            (true, s)
295        } else if let Some(s) = parse_char(s, b'-') {
296            (false, s)
297        } else {
298            return None;
299        };
300
301        let (hour, s) = parse_two_digit_numeric(s)?;
302
303        let (minute, s) = if s.is_empty() {
304            // No offset minutes are specified, e.g. +00 or +07.
305            (0, s)
306        } else {
307            // Optional colon separator between the hour and minute digits.
308            let s = parse_char(s, b':').unwrap_or(s);
309            let (minute, s) = parse_two_digit_numeric(s)?;
310            (minute, s)
311        };
312
313        ensure!(hour < 24 && minute < 60);
314
315        let hour = hour as i8;
316        let minute = minute as i8;
317
318        if is_positive {
319            Some((hour, minute, s))
320        } else {
321            Some((-hour, -minute, s))
322        }
323    }
324}
325
326/// Parses a two-digit base-10 number from ASCII string `s`, returning the number and the remaining
327/// string.
328fn parse_two_digit_numeric(s: &str) -> Option<(u8, &str)> {
329    debug_assert!(s.is_ascii());
330    if s.len() < 2 {
331        return None;
332    }
333    if s.starts_with('+') {
334        return None;
335    }
336    let (digits, s) = s.split_at(2);
337    Some((digits.parse().ok()?, s))
338}
339
340/// Splits ASCII string `s` at the first occurrence of a non-digit character.
341fn parse_digits(s: &str) -> (&str, &str) {
342    debug_assert!(s.is_ascii());
343
344    let idx = s
345        .as_bytes()
346        .iter()
347        .position(|c| !c.is_ascii_digit())
348        .unwrap_or(s.len());
349    s.split_at(idx)
350}
351
352/// Attempts to parse ASCII character `c` from ASCII string `s`, returning the remaining string. If
353/// the character can not be parsed, returns `None`.
354fn parse_char(s: &str, c: u8) -> Option<&str> {
355    debug_assert!(s.is_ascii());
356
357    ensure!(*s.as_bytes().first()? == c);
358    Some(&s[1..])
359}
360
361/// Attempts to parse ASCII character `c` from ASCII string `s`, ignoring ASCII case, returning the
362/// remaining string. If the character can not be parsed, returns `None`.
363fn parse_char_ignore_case(s: &str, c: u8) -> Option<&str> {
364    debug_assert!(s.is_ascii());
365
366    ensure!(s.as_bytes().first()?.eq_ignore_ascii_case(&c));
367    Some(&s[1..])
368}
369
370/// Returns the offset in seconds from the Unix epoch of the date time.
371///
372/// This is musl's [`__tm_to_secs`][1] converted to Rust via [c2rust[2] and then cleaned up by
373/// hand.
374///
375/// [1]: https://git.musl-libc.org/cgit/musl/tree/src/time/__tm_to_secs.c
376/// [2]: https://c2rust.com/
377fn date_time_to_seconds(tm: &DateTime) -> i64 {
378    let (start_of_year, is_leap) = year_to_seconds(tm.year);
379
380    let seconds_within_year = month_to_seconds(tm.month, is_leap)
381        + 86400 * u32::from(tm.day - 1)
382        + 3600 * u32::from(tm.hour)
383        + 60 * u32::from(tm.minute)
384        + u32::from(tm.second);
385
386    (start_of_year + i128::from(seconds_within_year)) as i64
387}
388
389/// Returns the number of seconds in the year prior to the start of the provided month.
390///
391/// This is musl's [`__month_to_secs`][1] converted to Rust via c2rust and then cleaned up by hand.
392///
393/// [1]: https://git.musl-libc.org/cgit/musl/tree/src/time/__month_to_secs.c
394fn month_to_seconds(month: u8, is_leap: bool) -> u32 {
395    const SECS_THROUGH_MONTH: [u32; 12] = [
396        0,
397        31 * 86400,
398        59 * 86400,
399        90 * 86400,
400        120 * 86400,
401        151 * 86400,
402        181 * 86400,
403        212 * 86400,
404        243 * 86400,
405        273 * 86400,
406        304 * 86400,
407        334 * 86400,
408    ];
409    let t = SECS_THROUGH_MONTH[usize::from(month - 1)];
410    if is_leap && month > 2 {
411        t + 86400
412    } else {
413        t
414    }
415}
416
417/// Returns the offset in seconds from the Unix epoch of the start of a year.
418///
419/// musl's [`__year_to_secs`][1] converted to Rust via c2rust and then cleaned up by hand.
420///
421/// Returns an i128 because the start of the earliest supported year underflows i64.
422///
423/// [1]: https://git.musl-libc.org/cgit/musl/tree/src/time/__year_to_secs.c
424pub(crate) fn year_to_seconds(year: i64) -> (i128, bool) {
425    let is_leap;
426    let year = year - 1900;
427
428    // Fast path for years 1901 - 2038.
429    if (1..=138).contains(&year) {
430        let mut leaps: i64 = (year - 68) >> 2;
431        if (year - 68).trailing_zeros() >= 2 {
432            leaps -= 1;
433            is_leap = true;
434        } else {
435            is_leap = false;
436        }
437        return (
438            i128::from(31_536_000 * (year - 70) + 86400 * leaps),
439            is_leap,
440        );
441    }
442
443    let centuries: i64;
444    let mut leaps: i64;
445
446    let mut cycles: i64 = (year - 100) / 400;
447    let mut rem: i64 = (year - 100) % 400;
448
449    if rem < 0 {
450        cycles -= 1;
451        rem += 400
452    }
453    if rem == 0 {
454        is_leap = true;
455        centuries = 0;
456        leaps = 0;
457    } else {
458        if rem >= 200 {
459            if rem >= 300 {
460                centuries = 3;
461                rem -= 300;
462            } else {
463                centuries = 2;
464                rem -= 200;
465            }
466        } else if rem >= 100 {
467            centuries = 1;
468            rem -= 100;
469        } else {
470            centuries = 0;
471        }
472        if rem == 0 {
473            is_leap = false;
474            leaps = 0;
475        } else {
476            leaps = rem / 4;
477            rem %= 4;
478            is_leap = rem == 0;
479        }
480    }
481    leaps += 97 * cycles + 24 * centuries - i64::from(is_leap);
482
483    (
484        i128::from((year - 100) * 31_536_000) + i128::from(leaps * 86400 + 946_684_800 + 86400),
485        is_leap,
486    )
487}
488
489/// Parses a timestamp in RFC 3339 format from `s`.
490pub(crate) fn parse_timestamp(s: &str) -> Option<Timestamp> {
491    // Check that the string is ASCII, since subsequent parsing steps use byte-level indexing.
492    ensure!(s.is_ascii());
493
494    let (year, month, day, s) = parse_date(s)?;
495
496    if s.is_empty() {
497        // The string only contained a date.
498        let date_time = DateTime {
499            year,
500            month,
501            day,
502            ..DateTime::default()
503        };
504
505        return Timestamp::try_from(date_time).ok();
506    }
507
508    // Accept either 'T' or ' ' as delimiter between date and time.
509    let s = parse_char_ignore_case(s, b'T').or_else(|| parse_char(s, b' '))?;
510    let (hour, minute, mut second, nanos, s) = parse_time(s)?;
511    let (offset_hour, offset_minute, s) = parse_offset(s)?;
512
513    ensure!(s.is_empty());
514
515    // Detect whether the timestamp falls in a leap second. If this is the case, roll it back
516    // to the previous second. To be maximally conservative, this should be checking that the
517    // timestamp is the last second in the UTC day (23:59:60), and even potentially checking
518    // that it's the final day of the UTC month, however these checks are non-trivial because
519    // at this point we have, in effect, a local date time, since the offset has not been
520    // applied.
521    if second == 60 {
522        second = 59;
523    }
524
525    let date_time = DateTime {
526        year,
527        month,
528        day,
529        hour,
530        minute,
531        second,
532        nanos,
533    };
534
535    let Timestamp { seconds, nanos } = Timestamp::try_from(date_time).ok()?;
536
537    let seconds =
538        seconds.checked_sub(i64::from(offset_hour) * 3600 + i64::from(offset_minute) * 60)?;
539
540    Some(Timestamp { seconds, nanos })
541}
542
543/// Parse a duration in the [Protobuf JSON encoding spec format][1].
544///
545/// [1]: https://developers.google.com/protocol-buffers/docs/proto3#json
546pub(crate) fn parse_duration(s: &str) -> Option<Duration> {
547    // Check that the string is ASCII, since subsequent parsing steps use byte-level indexing.
548    ensure!(s.is_ascii());
549
550    let (is_negative, s) = match parse_char(s, b'-') {
551        Some(s) => (true, s),
552        None => (false, s),
553    };
554
555    let (digits, s) = parse_digits(s);
556    let seconds = digits.parse::<i64>().ok()?;
557
558    let (nanos, s) = parse_nanos(s)?;
559
560    let s = parse_char(s, b's')?;
561    ensure!(s.is_empty());
562    ensure!(nanos < crate::NANOS_PER_SECOND as u32);
563
564    // If the duration is negative, also flip the nanos sign.
565    let (seconds, nanos) = if is_negative {
566        (-seconds, -(nanos as i32))
567    } else {
568        (seconds, nanos as i32)
569    };
570
571    Some(Duration { seconds, nanos })
572}
573
574impl TryFrom<DateTime> for Timestamp {
575    type Error = TimestampError;
576
577    fn try_from(date_time: DateTime) -> Result<Timestamp, TimestampError> {
578        if !date_time.is_valid() {
579            return Err(TimestampError::InvalidDateTime);
580        }
581        let seconds = date_time_to_seconds(&date_time);
582        let nanos = date_time.nanos;
583        Ok(Timestamp {
584            seconds,
585            nanos: nanos as i32,
586        })
587    }
588}
589
590#[cfg(test)]
591mod tests {
592    use super::*;
593    use proptest::prelude::*;
594    use prost::alloc::format;
595
596    #[test]
597    fn test_min_max() {
598        assert_eq!(
599            DateTime::MIN,
600            DateTime::from(Timestamp {
601                seconds: i64::MIN,
602                nanos: 0
603            }),
604        );
605        assert_eq!(
606            DateTime::MAX,
607            DateTime::from(Timestamp {
608                seconds: i64::MAX,
609                nanos: 999_999_999
610            }),
611        );
612    }
613
614    #[cfg(feature = "std")]
615    #[test]
616    fn test_datetime_from_timestamp() {
617        let case = |expected: &str, secs: i64, nanos: i32| {
618            let timestamp = Timestamp {
619                seconds: secs,
620                nanos,
621            };
622            assert_eq!(
623                expected,
624                format!("{}", DateTime::from(timestamp)),
625                "timestamp: {:?}",
626                timestamp
627            );
628        };
629
630        // Mostly generated with:
631        //  - date -jur <secs> +"%Y-%m-%dT%H:%M:%S.000000000Z"
632        //  - http://unixtimestamp.50x.eu/
633
634        case("1970-01-01T00:00:00Z", 0, 0);
635
636        case("1970-01-01T00:00:00.000000001Z", 0, 1);
637        case("1970-01-01T00:00:00.123450Z", 0, 123_450_000);
638        case("1970-01-01T00:00:00.050Z", 0, 50_000_000);
639        case("1970-01-01T00:00:01.000000001Z", 1, 1);
640        case("1970-01-01T00:01:01.000000001Z", 60 + 1, 1);
641        case("1970-01-01T01:01:01.000000001Z", 60 * 60 + 60 + 1, 1);
642        case(
643            "1970-01-02T01:01:01.000000001Z",
644            24 * 60 * 60 + 60 * 60 + 60 + 1,
645            1,
646        );
647
648        case("1969-12-31T23:59:59Z", -1, 0);
649        case("1969-12-31T23:59:59.000001Z", -1, 1_000);
650        case("1969-12-31T23:59:59.500Z", -1, 500_000_000);
651        case("1969-12-31T23:58:59.000001Z", -60 - 1, 1_000);
652        case("1969-12-31T22:58:59.000001Z", -60 * 60 - 60 - 1, 1_000);
653        case(
654            "1969-12-30T22:58:59.000000001Z",
655            -24 * 60 * 60 - 60 * 60 - 60 - 1,
656            1,
657        );
658
659        case("2038-01-19T03:14:07Z", i32::MAX as i64, 0);
660        case("2038-01-19T03:14:08Z", i32::MAX as i64 + 1, 0);
661        case("1901-12-13T20:45:52Z", i32::MIN as i64, 0);
662        case("1901-12-13T20:45:51Z", i32::MIN as i64 - 1, 0);
663
664        // Skipping these tests on windows as std::time::SystemTime range is low
665        // on Windows compared with that of Unix which can cause the following
666        // high date value tests to panic
667        #[cfg(not(target_os = "windows"))]
668        {
669            case("+292277026596-12-04T15:30:07Z", i64::MAX, 0);
670            case("+292277026596-12-04T15:30:06Z", i64::MAX - 1, 0);
671            case("-292277022657-01-27T08:29:53Z", i64::MIN + 1, 0);
672        }
673
674        case("1900-01-01T00:00:00Z", -2_208_988_800, 0);
675        case("1899-12-31T23:59:59Z", -2_208_988_801, 0);
676        case("0000-01-01T00:00:00Z", -62_167_219_200, 0);
677        case("-0001-12-31T23:59:59Z", -62_167_219_201, 0);
678
679        case("1234-05-06T07:08:09Z", -23_215_049_511, 0);
680        case("-1234-05-06T07:08:09Z", -101_097_651_111, 0);
681        case("2345-06-07T08:09:01Z", 11_847_456_541, 0);
682        case("-2345-06-07T08:09:01Z", -136_154_620_259, 0);
683    }
684
685    #[test]
686    fn test_parse_timestamp() {
687        // RFC 3339 Section 5.8 Examples
688        assert_eq!(
689            "1985-04-12T23:20:50.52Z".parse::<Timestamp>(),
690            Timestamp::date_time_nanos(1985, 4, 12, 23, 20, 50, 520_000_000),
691        );
692        assert_eq!(
693            "1996-12-19T16:39:57-08:00".parse::<Timestamp>(),
694            Timestamp::date_time(1996, 12, 20, 0, 39, 57),
695        );
696        assert_eq!(
697            "1996-12-19T16:39:57-08:00".parse::<Timestamp>(),
698            Timestamp::date_time(1996, 12, 20, 0, 39, 57),
699        );
700        assert_eq!(
701            "1990-12-31T23:59:60Z".parse::<Timestamp>(),
702            Timestamp::date_time(1990, 12, 31, 23, 59, 59),
703        );
704        assert_eq!(
705            "1990-12-31T15:59:60-08:00".parse::<Timestamp>(),
706            Timestamp::date_time(1990, 12, 31, 23, 59, 59),
707        );
708        assert_eq!(
709            "1937-01-01T12:00:27.87+00:20".parse::<Timestamp>(),
710            Timestamp::date_time_nanos(1937, 1, 1, 11, 40, 27, 870_000_000),
711        );
712
713        // Date
714        assert_eq!(
715            "1937-01-01".parse::<Timestamp>(),
716            Timestamp::date(1937, 1, 1),
717        );
718
719        // Negative year
720        assert_eq!(
721            "-0008-01-01".parse::<Timestamp>(),
722            Timestamp::date(-8, 1, 1),
723        );
724
725        // Plus year
726        assert_eq!(
727            "+19370-01-01".parse::<Timestamp>(),
728            Timestamp::date(19370, 1, 1),
729        );
730
731        // Full nanos
732        assert_eq!(
733            "2020-02-03T01:02:03.123456789Z".parse::<Timestamp>(),
734            Timestamp::date_time_nanos(2020, 2, 3, 1, 2, 3, 123_456_789),
735        );
736
737        // Leap day
738        assert_eq!(
739            "2020-02-29T01:02:03.00Z".parse::<Timestamp>(),
740            Timestamp::try_from(DateTime {
741                year: 2020,
742                month: 2,
743                day: 29,
744                hour: 1,
745                minute: 2,
746                second: 3,
747                nanos: 0,
748            })
749        );
750
751        // Test extensions to RFC 3339.
752        // ' ' instead of 'T' as date/time separator.
753        assert_eq!(
754            "1985-04-12 23:20:50.52Z".parse::<Timestamp>(),
755            Timestamp::date_time_nanos(1985, 4, 12, 23, 20, 50, 520_000_000),
756        );
757
758        // No time zone specified.
759        assert_eq!(
760            "1985-04-12T23:20:50.52".parse::<Timestamp>(),
761            Timestamp::date_time_nanos(1985, 4, 12, 23, 20, 50, 520_000_000),
762        );
763
764        // Offset without minutes specified.
765        assert_eq!(
766            "1996-12-19T16:39:57-08".parse::<Timestamp>(),
767            Timestamp::date_time(1996, 12, 20, 0, 39, 57),
768        );
769
770        // Snowflake stage style.
771        assert_eq!(
772            "2015-09-12 00:47:19.591 Z".parse::<Timestamp>(),
773            Timestamp::date_time_nanos(2015, 9, 12, 0, 47, 19, 591_000_000),
774        );
775        assert_eq!(
776            "2020-06-15 00:01:02.123 +0800".parse::<Timestamp>(),
777            Timestamp::date_time_nanos(2020, 6, 14, 16, 1, 2, 123_000_000),
778        );
779
780        // Regression tests
781        assert_eq!(
782            "-11111111-z".parse::<Timestamp>(),
783            Err(crate::TimestampError::ParseFailure),
784        );
785        assert_eq!(
786            "1900-01-10".parse::<Timestamp>(),
787            Ok(Timestamp {
788                seconds: -2208211200,
789                nanos: 0
790            }),
791        );
792        // Leading '+' in two-digit numbers
793        assert_eq!(
794            "19+1-+2-+3T+4:+5:+6Z".parse::<Timestamp>(),
795            Err(crate::TimestampError::ParseFailure),
796        );
797
798        // Very long seconds fraction
799        assert_eq!(
800            "1343-08-16 18:33:44.1666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666660404z".parse::<Timestamp>(),
801            Timestamp::date_time_nanos(1343, 8, 16, 18, 33, 44, 166_666_666),
802        );
803    }
804
805    #[test]
806    fn test_parse_duration() {
807        let case = |s: &str, seconds: i64, nanos: i32| {
808            assert_eq!(
809                s.parse::<Duration>().unwrap(),
810                Duration { seconds, nanos },
811                "duration: {}",
812                s
813            );
814        };
815
816        case("0s", 0, 0);
817        case("0.0s", 0, 0);
818        case("0.000s", 0, 0);
819
820        case("-0s", 0, 0);
821        case("-0.0s", 0, 0);
822        case("-0.000s", 0, 0);
823
824        case("-0s", 0, 0);
825        case("-0.0s", 0, 0);
826        case("-0.000s", 0, 0);
827
828        case("0.05s", 0, 50_000_000);
829        case("0.050s", 0, 50_000_000);
830
831        case("-0.05s", 0, -50_000_000);
832        case("-0.050s", 0, -50_000_000);
833
834        case("1s", 1, 0);
835        case("1.0s", 1, 0);
836        case("1.000s", 1, 0);
837
838        case("-1s", -1, 0);
839        case("-1.0s", -1, 0);
840        case("-1.000s", -1, 0);
841
842        case("15s", 15, 0);
843        case("15.1s", 15, 100_000_000);
844        case("15.100s", 15, 100_000_000);
845
846        case("-15s", -15, 0);
847        case("-15.1s", -15, -100_000_000);
848        case("-15.100s", -15, -100_000_000);
849
850        case("100.000000009s", 100, 9);
851        case("-100.000000009s", -100, -9);
852    }
853
854    #[test]
855    fn test_parse_non_ascii() {
856        assert!("2021️⃣-06-15 00:01:02.123 +0800"
857            .parse::<Timestamp>()
858            .is_err());
859
860        assert!("1️⃣s".parse::<Duration>().is_err());
861    }
862
863    #[test]
864    fn check_invalid_datetimes() {
865        assert_eq!(
866            Timestamp::try_from(DateTime {
867                year: i64::from_le_bytes([178, 2, 0, 0, 0, 0, 0, 128]),
868                month: 2,
869                day: 2,
870                hour: 8,
871                minute: 58,
872                second: 8,
873                nanos: u32::from_le_bytes([0, 0, 0, 50]),
874            }),
875            Err(TimestampError::InvalidDateTime)
876        );
877        assert_eq!(
878            Timestamp::try_from(DateTime {
879                year: i64::from_le_bytes([132, 7, 0, 0, 0, 0, 0, 128]),
880                month: 2,
881                day: 2,
882                hour: 8,
883                minute: 58,
884                second: 8,
885                nanos: u32::from_le_bytes([0, 0, 0, 50]),
886            }),
887            Err(TimestampError::InvalidDateTime)
888        );
889        assert_eq!(
890            Timestamp::try_from(DateTime {
891                year: i64::from_le_bytes([80, 96, 32, 240, 99, 0, 32, 180]),
892                month: 1,
893                day: 18,
894                hour: 19,
895                minute: 26,
896                second: 8,
897                nanos: u32::from_le_bytes([0, 0, 0, 50]),
898            }),
899            Err(TimestampError::InvalidDateTime)
900        );
901        assert_eq!(
902            Timestamp::try_from(DateTime {
903                year: DateTime::MIN.year - 1,
904                month: 0,
905                day: 0,
906                hour: 0,
907                minute: 0,
908                second: 0,
909                nanos: 0,
910            }),
911            Err(TimestampError::InvalidDateTime)
912        );
913        assert_eq!(
914            Timestamp::try_from(DateTime {
915                year: i64::MIN,
916                month: 0,
917                day: 0,
918                hour: 0,
919                minute: 0,
920                second: 0,
921                nanos: 0,
922            }),
923            Err(TimestampError::InvalidDateTime)
924        );
925        assert_eq!(
926            Timestamp::try_from(DateTime {
927                year: DateTime::MAX.year + 1,
928                month: u8::MAX,
929                day: u8::MAX,
930                hour: u8::MAX,
931                minute: u8::MAX,
932                second: u8::MAX,
933                nanos: u32::MAX,
934            }),
935            Err(TimestampError::InvalidDateTime)
936        );
937        assert_eq!(
938            Timestamp::try_from(DateTime {
939                year: i64::MAX,
940                month: u8::MAX,
941                day: u8::MAX,
942                hour: u8::MAX,
943                minute: u8::MAX,
944                second: u8::MAX,
945                nanos: u32::MAX,
946            }),
947            Err(TimestampError::InvalidDateTime)
948        );
949    }
950
951    proptest! {
952        #[cfg(feature = "std")]
953        #[test]
954        fn check_timestamp_parse_to_string_roundtrip(
955            system_time in std::time::SystemTime::arbitrary(),
956        ) {
957
958            let ts = Timestamp::from(system_time);
959
960            assert_eq!(
961                ts,
962                ts.to_string().parse::<Timestamp>().unwrap(),
963            )
964        }
965
966        #[cfg(feature = "std")]
967        #[test]
968        fn check_duration_parse_to_string_roundtrip(
969            duration in core::time::Duration::arbitrary(),
970        ) {
971            let duration = match Duration::try_from(duration) {
972                Ok(duration) => duration,
973                Err(_) => return Err(TestCaseError::reject("duration out of range")),
974            };
975
976            prop_assert_eq!(
977                &duration,
978                &duration.to_string().parse::<Duration>().unwrap(),
979                "{}", duration.to_string()
980            );
981        }
982
983        #[test]
984        fn check_timestamp_roundtrip_with_date_time(
985            seconds in i64::arbitrary(),
986            nanos in i32::arbitrary(),
987        ) {
988            let timestamp = Timestamp { seconds, nanos };
989            let date_time = DateTime::from(timestamp);
990            let roundtrip = Timestamp::try_from(date_time).unwrap();
991
992            prop_assert_eq!(timestamp.normalized(), roundtrip);
993        }
994
995        #[test]
996        fn check_date_time_roundtrip_with_timestamp(
997            year in i64::arbitrary(),
998            month in u8::arbitrary(),
999            day in u8::arbitrary(),
1000            hour in u8::arbitrary(),
1001            minute in u8::arbitrary(),
1002            second in u8::arbitrary(),
1003            nanos in u32::arbitrary(),
1004        ) {
1005            let date_time = DateTime {
1006                year,
1007                month,
1008                day,
1009                hour,
1010                minute,
1011                second,
1012                nanos
1013            };
1014
1015            if date_time.is_valid() {
1016                let timestamp = Timestamp::try_from(date_time).unwrap();
1017                let roundtrip = DateTime::from(timestamp);
1018
1019                prop_assert_eq!(date_time, roundtrip);
1020            } else {
1021                prop_assert_eq!(Timestamp::try_from(date_time), Err(TimestampError::InvalidDateTime));
1022            }
1023        }
1024    }
1025}