Skip to main content

cbor_core/
date_time.rs

1use std::time::{Duration, SystemTime};
2
3use crate::iso3339::Timestamp;
4use crate::{Error, Result, Tag, Value};
5
6const LEAP_SECOND_DATES: [(u32, u8, u8); 27] = [
7    (1972, 6, 30),
8    (1972, 12, 31),
9    (1973, 12, 31),
10    (1974, 12, 31),
11    (1975, 12, 31),
12    (1976, 12, 31),
13    (1977, 12, 31),
14    (1978, 12, 31),
15    (1979, 12, 31),
16    (1981, 6, 30),
17    (1982, 6, 30),
18    (1983, 6, 30),
19    (1985, 6, 30),
20    (1987, 12, 31),
21    (1989, 12, 31),
22    (1990, 12, 31),
23    (1992, 6, 30),
24    (1993, 6, 30),
25    (1994, 6, 30),
26    (1995, 12, 31),
27    (1997, 6, 30),
28    (1998, 12, 31),
29    (2005, 12, 31),
30    (2008, 12, 31),
31    (2012, 6, 30),
32    (2015, 6, 30),
33    (2016, 12, 31),
34];
35
36/// Helper for validated date/time string construction.
37///
38/// Wraps an RFC 3339 (an ISO 8601 profile) UTC string suitable
39/// for CBOR tag 0. The string is validated on creation: the date
40/// must be within `0001-01-01T00:00:00Z` to `9999-12-31T23:59:59Z`,
41/// and follow RFC 3339 section 5.6 layout.
42///
43/// Whole-second timestamps omit the fractional part. Sub-second
44/// timestamps include only the necessary digits (1-9, no trailing
45/// zeros).
46///
47/// CBOR::Core references section 5.6 of RFC3339, which allows
48/// leap seconds (second == 60). So we accept leap seconds and
49/// validate, if the date is one of the currently known 27
50/// leap second dates.
51///
52/// However, trying to convert a date/time value containing a
53/// leap second to SystemTime will fail with `Err(InvalidEncoding)`.
54///
55/// Implements `TryFrom<SystemTime>` and `TryFrom<&str>`, so that
56/// [`Value::date_time`] can accept both through a single
57/// `TryInto<DateTime>` bound.
58#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
59pub struct DateTime(String);
60
61impl From<DateTime> for Value {
62    fn from(value: DateTime) -> Self {
63        Self::tag(Tag::DATE_TIME, value.0)
64    }
65}
66
67impl TryFrom<SystemTime> for DateTime {
68    type Error = Error;
69
70    fn try_from(value: SystemTime) -> Result<Self> {
71        if let Ok(time) = value.duration_since(SystemTime::UNIX_EPOCH)
72            && time > Duration::from_secs(253402300799)
73        {
74            return Err(Error::Overflow);
75        }
76
77        let ts = Timestamp::try_new(value).or(Err(Error::Overflow))?;
78        Ok(Self(ts.to_string()))
79    }
80}
81
82fn validate_date_time(s: &str) -> Result<()> {
83    let ts: Timestamp = s.parse().or(Err(Error::InvalidEncoding))?;
84
85    if ts.year > 9999 {
86        return Err(Error::Overflow);
87    }
88
89    if ts.second == 60 {
90        if ts.hour != 23 || ts.minute != 59 {
91            return Err(Error::InvalidEncoding);
92        }
93
94        if !LEAP_SECOND_DATES.contains(&(ts.year, ts.month, ts.day)) {
95            return Err(Error::InvalidEncoding);
96        }
97    }
98
99    if ts.year < 9999 || ts.month < 12 || ts.day < 31 || ts.hour < 23 || ts.minute < 59 || ts.second < 59 {
100        Ok(())
101    } else if ts.second > 59 || (ts.nano_seconds > 0 && ts.offset == 0) || ts.offset < 0 {
102        Err(Error::Overflow)
103    } else {
104        Ok(())
105    }
106}
107
108impl TryFrom<&str> for DateTime {
109    type Error = Error;
110
111    fn try_from(value: &str) -> Result<Self> {
112        validate_date_time(value)?;
113        Ok(Self(value.to_string()))
114    }
115}
116
117impl TryFrom<String> for DateTime {
118    type Error = Error;
119
120    fn try_from(value: String) -> Result<Self> {
121        validate_date_time(&value)?;
122        Ok(Self(value))
123    }
124}
125
126impl TryFrom<&String> for DateTime {
127    type Error = Error;
128
129    fn try_from(value: &String) -> Result<Self> {
130        validate_date_time(value)?;
131        Ok(Self(value.clone()))
132    }
133}
134
135impl TryFrom<Box<str>> for DateTime {
136    type Error = Error;
137
138    fn try_from(value: Box<str>) -> Result<Self> {
139        validate_date_time(value.as_ref())?;
140        Ok(Self(value.to_string()))
141    }
142}