Skip to main content

deser_core/ext/
datetime.rs

1use alloc::format;
2use alloc::string::ToString;
3use core::fmt;
4use core::str::FromStr;
5
6use crate::error::Error;
7use crate::event::Atom;
8use crate::ext::Extension;
9use crate::ext::known::{WellKnown, floor, impl_well_known, invalid, out_of_range, round};
10
11/// A calendar date (`1979-05-27`).
12///
13/// See [`Datetime`].
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct Date {
16    /// The year (`0` to `9999`).
17    pub year: u16,
18    /// The month (`1` to `12`).
19    pub month: u8,
20    /// The day of the month (`1` to `31`).
21    pub day: u8,
22}
23
24/// A time of the day (`07:32:00.999`).
25///
26/// See [`Datetime`].
27#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub struct Time {
29    /// The hour (`0` to `23`).
30    pub hour: u8,
31    /// The minute (`0` to `59`).
32    pub minute: u8,
33    /// The second (`0` to `60`, `60` for leap seconds).
34    pub second: u8,
35    /// The fraction of the second in nanoseconds.
36    pub nanosecond: u32,
37}
38
39/// The offset of an offset date-time.
40///
41/// See [`Datetime`].
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub enum Offset {
44    /// UTC, written as `Z`.
45    Z,
46    /// An offset from UTC in minutes (`-07:00` is `-420`).
47    Custom {
48        /// The offset in minutes.
49        minutes: i16,
50    },
51}
52
53/// A date, a time of the day, or both with an optional offset.
54///
55/// This is a well-known extension type (see [`ext`](crate::ext)) for the
56/// date and time values of [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339):
57///
58/// | Kind                                      | `date`  | `time`  | `offset` |
59/// |-------------------------------------------|---------|---------|----------|
60/// | offset date-time (`1979-05-27T07:32:00Z`) | `Some`  | `Some`  | `Some`   |
61/// | local date-time (`1979-05-27T07:32:00`)   | `Some`  | `Some`  | `None`   |
62/// | local date (`1979-05-27`)                 | `Some`  | `None`  | `None`   |
63/// | local time (`07:32:00`)                   | `None`  | `Some`  | `None`   |
64///
65/// The fallback is the RFC 3339 representation as string.
66///
67/// Parsing is lenient: the separator between date and time can be `T`, `t`
68/// or a space, `Z` can be lowercase and seconds can be omitted.  Formatting
69/// always produces RFC 3339.  Digits of the fraction beyond nanoseconds are
70/// truncated.
71///
72/// ```
73/// use deser::ext::Datetime;
74///
75/// let dt: Datetime = "1979-05-27 07:32:00.5-07:00".parse().unwrap();
76/// assert_eq!(dt.to_string(), "1979-05-27T07:32:00.5-07:00");
77/// ```
78///
79/// With the `jiff`, `chrono` and `time` features the date and time types of
80/// these crates serialize as [`Datetime`] (or [`Timestamp`]).
81#[derive(Clone, Copy, PartialEq, Eq, Hash)]
82pub struct Datetime {
83    /// The date.  `None` for local times.
84    pub date: Option<Date>,
85    /// The time.  `None` for local dates.
86    pub time: Option<Time>,
87    /// The offset.  Only set for offset date-times.
88    pub offset: Option<Offset>,
89}
90
91/// An instant in time.
92///
93/// This is a well-known extension type (see [`ext`](crate::ext)) for
94/// points in time independent of a time zone, such as Unix timestamps.  It
95/// holds the time since the Unix epoch (`1970-01-01T00:00:00Z`), ignoring
96/// leap seconds.  The nanoseconds are always positive: `-0.5` seconds are
97/// `seconds: -1` and `nanosecond: 500_000_000`.
98///
99/// The fallback is the RFC 3339 representation in UTC as string.  Instants
100/// outside of the years 0 to 9999 are written with an expanded year as in
101/// ISO 8601 (`+10000-01-01T00:00:00Z`).
102///
103/// ```
104/// use deser::ext::Timestamp;
105///
106/// let ts = Timestamp { seconds: 296638320, nanosecond: 0 };
107/// assert_eq!(ts.to_string(), "1979-05-27T07:32:00Z");
108/// assert_eq!("1979-05-27T00:32:00-07:00".parse::<Timestamp>().unwrap(), ts);
109/// ```
110///
111/// [`std::time::SystemTime`] serializes as [`Timestamp`], as do the
112/// timestamp types of `jiff`, `chrono` and `time` with the respective
113/// features.
114#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
115pub struct Timestamp {
116    /// The seconds since the Unix epoch.
117    pub seconds: i64,
118    /// The fraction of the second in nanoseconds (`0` to `999_999_999`).
119    pub nanosecond: u32,
120}
121
122impl Date {
123    /// Returns `true` if this is a valid calendar date.
124    pub fn is_valid(&self) -> bool {
125        self.year <= 9999
126            && (1..=12).contains(&self.month)
127            && self.day >= 1
128            && self.day <= days_in_month(i64::from(self.year), self.month)
129    }
130}
131
132impl Time {
133    /// Returns `true` if this is a valid time.
134    pub fn is_valid(&self) -> bool {
135        self.hour <= 23 && self.minute <= 59 && self.second <= 60 && self.nanosecond < 1_000_000_000
136    }
137}
138
139impl Offset {
140    /// Returns the offset in minutes.
141    pub fn minutes(&self) -> i16 {
142        match *self {
143            Offset::Z => 0,
144            Offset::Custom { minutes } => minutes,
145        }
146    }
147
148    /// Returns `true` if the offset is less than 24 hours.
149    pub fn is_valid(&self) -> bool {
150        self.minutes().unsigned_abs() < 24 * 60
151    }
152}
153
154impl Datetime {
155    /// Returns `true` if the value is a valid date-time.
156    ///
157    /// The fields are public, so a [`Datetime`] can be constructed that
158    /// is invalid (for instance an offset without date and time or February
159    /// 30th).
160    pub fn is_valid(&self) -> bool {
161        match (self.date, self.time, self.offset) {
162            (None, None, _) => false,
163            (date, time, Some(offset)) => {
164                date.is_some_and(|x| x.is_valid())
165                    && time.is_some_and(|x| x.is_valid())
166                    && offset.is_valid()
167            }
168            (date, time, None) => {
169                date.is_none_or(|x| x.is_valid()) && time.is_none_or(|x| x.is_valid())
170            }
171        }
172    }
173
174    /// Returns a human readable name for the kind of date-time.
175    ///
176    /// This is one of `"offset date-time"`, `"local date-time"`,
177    /// `"local date"` and `"local time"`.
178    pub fn kind(&self) -> &'static str {
179        match (self.date, self.time, self.offset) {
180            (Some(_), Some(_), Some(_)) => "offset date-time",
181            (Some(_), Some(_), None) => "local date-time",
182            (Some(_), None, _) => "local date",
183            (None, Some(_), _) => "local time",
184            (None, None, _) => "invalid date-time",
185        }
186    }
187
188    /// Creates an error for a date-time of the wrong kind.
189    pub(crate) fn unexpected_kind(&self, expected: &str) -> Error {
190        invalid(format!("unexpected {}, expected {}", self.kind(), expected))
191    }
192
193    /// Returns the date if this is a local date.
194    #[cfg(any(feature = "chrono", feature = "time", feature = "jiff"))]
195    pub(crate) fn expect_local_date(&self) -> Result<Date, Error> {
196        match (self.date, self.time) {
197            (Some(date), None) => Ok(date),
198            _ => Err(self.unexpected_kind("local date")),
199        }
200    }
201
202    /// Returns the time if this is a local time.
203    #[cfg(any(feature = "chrono", feature = "time", feature = "jiff"))]
204    pub(crate) fn expect_local_time(&self) -> Result<Time, Error> {
205        match (self.date, self.time) {
206            (None, Some(time)) => Ok(time),
207            _ => Err(self.unexpected_kind("local time")),
208        }
209    }
210
211    /// Returns date and time if this is a local date-time.
212    #[cfg(any(feature = "chrono", feature = "time", feature = "jiff"))]
213    pub(crate) fn expect_local_datetime(&self) -> Result<(Date, Time), Error> {
214        match (self.date, self.time, self.offset) {
215            (Some(date), Some(time), None) => Ok((date, time)),
216            _ => Err(self.unexpected_kind("local date-time")),
217        }
218    }
219
220    /// Returns date, time and offset if this is an offset date-time.
221    pub(crate) fn expect_offset_datetime(&self) -> Result<(Date, Time, Offset), Error> {
222        match (self.date, self.time, self.offset) {
223            (Some(date), Some(time), Some(offset)) => Ok((date, time, offset)),
224            _ => Err(self.unexpected_kind("offset date-time")),
225        }
226    }
227}
228
229impl Timestamp {
230    /// Creates a timestamp from seconds and nanoseconds that can be out of
231    /// range or negative.
232    #[cfg(feature = "std")]
233    pub(crate) fn normalized(seconds: i64, nanosecond: i64) -> Option<Timestamp> {
234        let seconds = seconds.checked_add(nanosecond.div_euclid(1_000_000_000))?;
235        Some(Timestamp {
236            seconds,
237            nanosecond: nanosecond.rem_euclid(1_000_000_000) as u32,
238        })
239    }
240
241    /// Returns the time since the epoch in seconds as float.
242    pub fn as_secs_f64(&self) -> f64 {
243        self.seconds as f64 + f64::from(self.nanosecond) / 1e9
244    }
245
246    /// Converts the timestamp into an offset date-time in UTC.
247    ///
248    /// Returns `None` if the year is outside of `0` to `9999`.
249    pub fn to_datetime(&self) -> Option<Datetime> {
250        let (year, date, time) = self.to_civil();
251        Some(Datetime {
252            date: Some(Date {
253                year: u16::try_from(year).ok().filter(|&x| x <= 9999)?,
254                month: date.0,
255                day: date.1,
256            }),
257            time: Some(time),
258            offset: Some(Offset::Z),
259        })
260    }
261
262    /// Converts the timestamp into the year, month and day and time in UTC.
263    fn to_civil(self) -> (i64, (u8, u8), Time) {
264        let days = self.seconds.div_euclid(86400);
265        let secs = self.seconds.rem_euclid(86400);
266        let (year, month, day) = civil_from_days(days);
267        (
268            year,
269            (month, day),
270            Time {
271                hour: (secs / 3600) as u8,
272                minute: (secs / 60 % 60) as u8,
273                second: (secs % 60) as u8,
274                nanosecond: self.nanosecond,
275            },
276        )
277    }
278
279    /// Creates a timestamp from a civil date-time with an offset.
280    fn from_civil(year: i64, date: Date, time: Time, offset: Offset) -> Option<Timestamp> {
281        let days = days_from_civil(year, date.month, date.day);
282        let seconds = days.checked_mul(86400)?.checked_add(
283            i64::from(time.hour) * 3600
284                    + i64::from(time.minute) * 60
285                    // leap seconds are clamped
286                    + i64::from(time.second.min(59))
287                - i64::from(offset.minutes()) * 60,
288        )?;
289        Some(Timestamp {
290            seconds,
291            nanosecond: time.nanosecond,
292        })
293    }
294}
295
296impl TryFrom<Datetime> for Timestamp {
297    type Error = Error;
298
299    /// Converts an offset date-time into a timestamp.
300    ///
301    /// Leap seconds are clamped to the previous second.
302    fn try_from(value: Datetime) -> Result<Timestamp, Error> {
303        let (date, time, offset) = value.expect_offset_datetime()?;
304        if !value.is_valid() {
305            return Err(invalid("invalid date-time"));
306        }
307        Timestamp::from_civil(i64::from(date.year), date, time, offset)
308            .ok_or_else(|| out_of_range("date-time out of range"))
309    }
310}
311
312impl TryFrom<Timestamp> for Datetime {
313    type Error = Error;
314
315    /// Converts a timestamp into an offset date-time in UTC.
316    fn try_from(value: Timestamp) -> Result<Datetime, Error> {
317        value
318            .to_datetime()
319            .ok_or_else(|| out_of_range("timestamp out of range for date-time"))
320    }
321}
322
323impl From<Date> for Datetime {
324    fn from(date: Date) -> Datetime {
325        Datetime {
326            date: Some(date),
327            time: None,
328            offset: None,
329        }
330    }
331}
332
333impl From<Time> for Datetime {
334    fn from(time: Time) -> Datetime {
335        Datetime {
336            date: None,
337            time: Some(time),
338            offset: None,
339        }
340    }
341}
342
343pub(crate) fn is_leap_year(year: i64) -> bool {
344    year.rem_euclid(4) == 0 && (year.rem_euclid(100) != 0 || year.rem_euclid(400) == 0)
345}
346
347pub(crate) fn days_in_month(year: i64, month: u8) -> u8 {
348    match month {
349        2 if is_leap_year(year) => 29,
350        2 => 28,
351        4 | 6 | 9 | 11 => 30,
352        _ => 31,
353    }
354}
355
356/// Returns the days since the Unix epoch for a proleptic Gregorian date.
357///
358/// See <http://howardhinnant.github.io/date_algorithms.html>.
359pub(crate) fn days_from_civil(year: i64, month: u8, day: u8) -> i64 {
360    let (month, day) = (i64::from(month), i64::from(day));
361    let year = if month <= 2 { year - 1 } else { year };
362    let era = year.div_euclid(400);
363    let yoe = year - era * 400;
364    let doy = (153 * (if month > 2 { month - 3 } else { month + 9 }) + 2) / 5 + day - 1;
365    let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
366    era * 146097 + doe - 719468
367}
368
369/// Returns the proleptic Gregorian date for days since the Unix epoch.
370pub(crate) fn civil_from_days(days: i64) -> (i64, u8, u8) {
371    let z = days + 719468;
372    let era = z.div_euclid(146097);
373    let doe = z - era * 146097;
374    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
375    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
376    let mp = (5 * doy + 2) / 153;
377    let day = (doy - (153 * mp + 2) / 5 + 1) as u8;
378    let month = if mp < 10 { mp + 3 } else { mp - 9 } as u8;
379    let year = yoe + era * 400 + i64::from(month <= 2);
380    (year, month, day)
381}
382
383impl fmt::Display for Date {
384    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385        write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
386    }
387}
388
389impl fmt::Display for Time {
390    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391        write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)?;
392        write_fraction(f, self.nanosecond)
393    }
394}
395
396/// Writes the fraction of a second without trailing zeros.
397pub(crate) fn write_fraction(f: &mut fmt::Formatter<'_>, nanosecond: u32) -> fmt::Result {
398    if nanosecond != 0 {
399        let mut digits = 9;
400        let mut value = nanosecond;
401        while value.is_multiple_of(10) {
402            value /= 10;
403            digits -= 1;
404        }
405        write!(f, ".{:0width$}", value, width = digits)?;
406    }
407    Ok(())
408}
409
410impl fmt::Display for Offset {
411    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412        match *self {
413            Offset::Z => f.write_str("Z"),
414            Offset::Custom { minutes } => {
415                let sign = if minutes < 0 { '-' } else { '+' };
416                let minutes = minutes.unsigned_abs();
417                write!(f, "{}{:02}:{:02}", sign, minutes / 60, minutes % 60)
418            }
419        }
420    }
421}
422
423impl fmt::Display for Datetime {
424    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425        if let Some(ref date) = self.date {
426            write!(f, "{}", date)?;
427            if self.time.is_some() {
428                f.write_str("T")?;
429            }
430        }
431        if let Some(ref time) = self.time {
432            write!(f, "{}", time)?;
433        }
434        if let Some(ref offset) = self.offset {
435            write!(f, "{}", offset)?;
436        }
437        Ok(())
438    }
439}
440
441impl fmt::Debug for Datetime {
442    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
443        write!(f, "Datetime({})", self)
444    }
445}
446
447impl fmt::Display for Timestamp {
448    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
449        let (year, (month, day), time) = self.to_civil();
450        if (0..=9999).contains(&year) {
451            write!(f, "{:04}", year)?;
452        } else {
453            // expanded year representation of ISO 8601
454            write!(
455                f,
456                "{}{:06}",
457                if year < 0 { '-' } else { '+' },
458                year.unsigned_abs()
459            )?;
460        }
461        write!(f, "-{:02}-{:02}T{}Z", month, day, time)
462    }
463}
464
465impl FromStr for Datetime {
466    type Err = Error;
467
468    fn from_str(s: &str) -> Result<Datetime, Error> {
469        let (year, dt) = parse_datetime(s, false)?;
470        debug_assert!(year.is_none());
471        Ok(dt)
472    }
473}
474
475impl FromStr for Timestamp {
476    type Err = Error;
477
478    /// Parses an offset date-time.
479    ///
480    /// Years outside of 0 to 9999 can be given in the expanded
481    /// representation of ISO 8601 (a sign and at least six digits).
482    fn from_str(s: &str) -> Result<Timestamp, Error> {
483        match parse_datetime(s, true)? {
484            (Some(year), dt) => {
485                let (date, time, offset) = dt.expect_offset_datetime()?;
486                Timestamp::from_civil(year, date, time, offset)
487                    .ok_or_else(|| out_of_range("timestamp out of range"))
488            }
489            (None, dt) => Timestamp::try_from(dt),
490        }
491    }
492}
493
494/// Parses a date-time.
495///
496/// If `expanded_years` is set, years with a sign and at least six digits
497/// are accepted.  These are returned separately, the date of the returned
498/// value then has the year `2000`.
499fn parse_datetime(s: &str, expanded_years: bool) -> Result<(Option<i64>, Datetime), Error> {
500    let bytes = s.as_bytes();
501    let mut pos = 0;
502    let mut rv = Datetime {
503        date: None,
504        time: None,
505        offset: None,
506    };
507    let mut expanded_year = None;
508
509    let has_date = if expanded_years && matches!(bytes.first(), Some(b'+' | b'-')) {
510        let digits = bytes[1..].iter().take_while(|x| x.is_ascii_digit()).count();
511        if digits < 6 {
512            return Err(invalid("invalid expanded year"));
513        }
514        let year: i64 = s[1..1 + digits]
515            .parse()
516            .map_err(|_| out_of_range("year out of range"))?;
517        expanded_year = Some(if bytes[0] == b'-' { -year } else { year });
518        pos = 1 + digits;
519        true
520    } else {
521        bytes.get(4) == Some(&b'-')
522    };
523
524    if has_date {
525        let year = match expanded_year {
526            Some(_) => 2000,
527            None => digits(bytes, &mut pos, 4)? as u16,
528        };
529        expect(bytes, &mut pos, b'-')?;
530        let month = digits(bytes, &mut pos, 2)? as u8;
531        expect(bytes, &mut pos, b'-')?;
532        let day = digits(bytes, &mut pos, 2)? as u8;
533        let date = Date { year, month, day };
534        let valid_day = match expanded_year {
535            Some(year) => {
536                (1..=12).contains(&month) && day >= 1 && day <= days_in_month(year, month)
537            }
538            None => date.is_valid(),
539        };
540        if !valid_day {
541            return Err(invalid("invalid date"));
542        }
543        rv.date = Some(date);
544        match bytes.get(pos) {
545            Some(b'T' | b't' | b' ') => pos += 1,
546            None => return Ok((expanded_year, rv)),
547            _ => return Err(invalid("invalid date-time")),
548        }
549    }
550
551    let mut time = Time {
552        hour: digits(bytes, &mut pos, 2)? as u8,
553        minute: {
554            expect(bytes, &mut pos, b':')?;
555            digits(bytes, &mut pos, 2)? as u8
556        },
557        second: 0,
558        nanosecond: 0,
559    };
560    if bytes.get(pos) == Some(&b':') {
561        pos += 1;
562        time.second = digits(bytes, &mut pos, 2)? as u8;
563        if bytes.get(pos) == Some(&b'.') {
564            pos += 1;
565            let start = pos;
566            let mut nanos = 0u32;
567            while let Some(&c) = bytes.get(pos).filter(|x| x.is_ascii_digit()) {
568                // digits beyond nanoseconds are truncated
569                if pos - start < 9 {
570                    nanos = nanos * 10 + u32::from(c - b'0');
571                }
572                pos += 1;
573            }
574            if pos == start {
575                return Err(invalid("expected digits after decimal point"));
576            }
577            for _ in (pos - start)..9 {
578                nanos *= 10;
579            }
580            time.nanosecond = nanos;
581        }
582    }
583    if !time.is_valid() {
584        return Err(invalid("invalid time"));
585    }
586    rv.time = Some(time);
587
588    if rv.date.is_some() {
589        match bytes.get(pos) {
590            Some(b'Z' | b'z') => {
591                pos += 1;
592                rv.offset = Some(Offset::Z);
593            }
594            Some(&sign @ (b'+' | b'-')) => {
595                pos += 1;
596                let hours = digits(bytes, &mut pos, 2)?;
597                expect(bytes, &mut pos, b':')?;
598                let minutes = digits(bytes, &mut pos, 2)?;
599                if hours > 23 || minutes > 59 {
600                    return Err(invalid("invalid offset"));
601                }
602                let minutes = (hours * 60 + minutes) as i16;
603                rv.offset = Some(Offset::Custom {
604                    minutes: if sign == b'-' { -minutes } else { minutes },
605                });
606            }
607            _ => {}
608        }
609    }
610
611    if pos != bytes.len() {
612        return Err(invalid("unexpected characters after date-time"));
613    }
614    Ok((expanded_year, rv))
615}
616
617fn digits(bytes: &[u8], pos: &mut usize, n: usize) -> Result<u32, Error> {
618    let mut rv = 0;
619    for _ in 0..n {
620        match bytes.get(*pos) {
621            Some(&c) if c.is_ascii_digit() => rv = rv * 10 + u32::from(c - b'0'),
622            _ => return Err(invalid("invalid date-time")),
623        }
624        *pos += 1;
625    }
626    Ok(rv)
627}
628
629fn expect(bytes: &[u8], pos: &mut usize, c: u8) -> Result<(), Error> {
630    if bytes.get(*pos) == Some(&c) {
631        *pos += 1;
632        Ok(())
633    } else {
634        Err(invalid("invalid date-time"))
635    }
636}
637
638impl Extension for Datetime {
639    fn name(&self) -> &str {
640        "datetime"
641    }
642
643    fn fallback(&self) -> Atom<'_> {
644        Atom::Str(self.to_string().into())
645    }
646}
647
648impl WellKnown for Datetime {
649    const EXPECTING: &'static str = "datetime";
650
651    /// Accepts date-times, timestamps and strings.
652    fn from_atom(atom: &Atom) -> Result<Option<Datetime>, Error> {
653        Ok(Some(match *atom {
654            Atom::Ext(ref ext) => {
655                if let Some(value) = ext.downcast_ref::<Datetime>() {
656                    *value
657                } else if let Some(value) = ext.downcast_ref::<Timestamp>() {
658                    Datetime::try_from(*value)?
659                } else {
660                    return Ok(None);
661                }
662            }
663            Atom::Str(ref value) => value.parse()?,
664            _ => return Ok(None),
665        }))
666    }
667}
668
669impl_well_known!(Datetime);
670
671impl Extension for Timestamp {
672    fn name(&self) -> &str {
673        "timestamp"
674    }
675
676    fn fallback(&self) -> Atom<'_> {
677        Atom::Str(self.to_string().into())
678    }
679}
680
681impl WellKnown for Timestamp {
682    const EXPECTING: &'static str = "timestamp";
683
684    /// Accepts timestamps, offset date-times, strings and numbers (seconds
685    /// since the epoch).
686    fn from_atom(atom: &Atom) -> Result<Option<Timestamp>, Error> {
687        Ok(Some(match *atom {
688            Atom::Ext(ref ext) => {
689                if let Some(value) = ext.downcast_ref::<Timestamp>() {
690                    *value
691                } else if let Some(value) = ext.downcast_ref::<Datetime>() {
692                    Timestamp::try_from(*value)?
693                } else {
694                    return Ok(None);
695                }
696            }
697            Atom::Str(ref value) => value.parse()?,
698            Atom::U64(value) => Timestamp {
699                seconds: i64::try_from(value)
700                    .map_err(|_| out_of_range("timestamp out of range"))?,
701                nanosecond: 0,
702            },
703            Atom::I64(value) => Timestamp {
704                seconds: value,
705                nanosecond: 0,
706            },
707            Atom::F64(value) => {
708                if !value.is_finite() || value.abs() >= 9.2e18 {
709                    return Err(out_of_range("timestamp out of range"));
710                }
711                let seconds = floor(value);
712                Timestamp {
713                    seconds: seconds as i64,
714                    nanosecond: (round((value - seconds) * 1e9) as u32).min(999_999_999),
715                }
716            }
717            _ => return Ok(None),
718        }))
719    }
720}
721
722impl_well_known!(Timestamp);
723
724#[test]
725fn test_civil_roundtrip() {
726    for days in [
727        -1_000_000i64,
728        -719468,
729        -1,
730        0,
731        1,
732        11016,
733        2_932_896,
734        10_000_000,
735    ] {
736        let (y, m, d) = civil_from_days(days);
737        assert_eq!(days_from_civil(y, m, d), days);
738    }
739    assert_eq!(civil_from_days(0), (1970, 1, 1));
740    assert_eq!(days_from_civil(2000, 3, 1), 11017);
741}
742
743#[test]
744fn test_timestamp_format() {
745    let ts = Timestamp {
746        seconds: -1,
747        nanosecond: 500_000_000,
748    };
749    assert_eq!(ts.to_string(), "1969-12-31T23:59:59.5Z");
750    assert_eq!(ts.to_string().parse::<Timestamp>().unwrap(), ts);
751    let far = Timestamp {
752        seconds: 253402300800,
753        nanosecond: 0,
754    };
755    assert_eq!(far.to_string(), "+010000-01-01T00:00:00Z");
756    assert_eq!(far.to_string().parse::<Timestamp>().unwrap(), far);
757    let before = Timestamp {
758        seconds: -62167219201,
759        nanosecond: 0,
760    };
761    assert_eq!(before.to_string(), "-000001-12-31T23:59:59Z");
762    assert_eq!(before.to_string().parse::<Timestamp>().unwrap(), before);
763    assert!("1979-05-27T07:32:00".parse::<Timestamp>().is_err());
764    assert!("+10000-01-01T00:00:00Z".parse::<Timestamp>().is_err());
765}