1use crate::{constants::TAG_EPOCH, Encoder, ItemKind, Literal, TaggedItem};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
4pub enum Precision {
5 Seconds,
6 Millis,
7 Micros,
8 Nanos,
9}
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct Timestamp {
14 unix_epoch: i64,
15 nanos: u32,
16 tz_sec_east: i32,
17}
18
19impl Timestamp {
20 pub fn new(unix_epoch: i64, nanos: u32, tz_sec_east: i32) -> Self {
21 Self {
22 unix_epoch,
23 nanos,
24 tz_sec_east,
25 }
26 }
27
28 #[cfg(feature = "rfc3339")]
29 pub(crate) fn from_string(item: TaggedItem<'_>) -> Option<Self> {
30 if let ItemKind::Str(s) = item.kind() {
31 chrono::DateTime::parse_from_rfc3339(s.as_cow().as_ref())
32 .map(Into::into)
33 .ok()
34 } else {
35 None
36 }
37 }
38
39 pub(crate) fn from_epoch(item: TaggedItem<'_>) -> Option<Self> {
40 match item.kind() {
41 ItemKind::Pos(t) => Some(Timestamp {
42 unix_epoch: t.min(i64::MAX as u64) as i64,
43 nanos: 0,
44 tz_sec_east: 0,
45 }),
46 ItemKind::Neg(t) => Some(Timestamp {
47 unix_epoch: -1 - t.min(i64::MAX as u64) as i64,
48 nanos: 0,
49 tz_sec_east: 0,
50 }),
51 ItemKind::Float(t) => {
52 let mut frac = t.fract();
53 if frac < 0.0 {
54 frac += 1.0;
55 }
56 let mut unix_epoch = t.min(i64::MAX as f64).floor() as i64;
57
58 let mut nanos = if t.abs() > 1e-3 * (1u64 << 52) as f64 {
59 (frac * 1e3).round() as u32 * 1_000_000
60 } else if t.abs() > 1e-6 * (1u64 << 52) as f64 {
61 (frac * 1e6).round() as u32 * 1_000
62 } else {
63 (frac * 1e9).round() as u32
64 };
65 if nanos > 999_999_999 {
66 nanos -= 1_000_000_000;
67 unix_epoch += 1;
68 }
69
70 Some(Timestamp {
71 unix_epoch,
72 nanos,
73 tz_sec_east: 0,
74 })
75 }
76 _ => None,
77 }
78 }
79
80 pub(crate) fn encode<E: Encoder>(self, encoder: E, precision: Precision) -> E::Output {
81 if precision == Precision::Seconds {
82 if self.unix_epoch() >= 0 {
83 encoder.write_pos(self.unix_epoch() as u64, [TAG_EPOCH])
84 } else {
85 encoder.write_neg((-1 - self.unix_epoch()) as u64, [TAG_EPOCH])
86 }
87 } else {
88 #[cfg(feature = "rfc3339")]
89 {
90 use crate::constants::TAG_ISO8601;
91 use chrono::{DateTime, FixedOffset};
92 use std::convert::TryFrom;
93
94 let mut this = self;
95 let as_epoch = match precision {
96 Precision::Seconds => unreachable!(),
97 Precision::Millis => {
98 this.nanos -= this.nanos % 1_000_000;
99 this.unix_epoch().abs() <= (1 << 52) / 1000
100 }
101 Precision::Micros => {
102 this.nanos -= this.nanos % 1_000;
103 this.unix_epoch().abs() <= (1 << 52) / 1_000_000
104 }
105 Precision::Nanos => this.unix_epoch().abs() <= (1 << 52) / 1_000_000_000,
106 };
107 if let (false, Ok(dt)) = (as_epoch, DateTime::<FixedOffset>::try_from(this)) {
108 let s = dt.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true);
109 encoder.write_str(s.as_str(), [TAG_ISO8601])
110 } else {
111 let v = this.unix_epoch() as f64 + this.nanos() as f64 / 1e9;
112 encoder.write_lit(Literal::L8(v.to_bits()), [TAG_EPOCH])
113 }
114 }
115 #[cfg(not(feature = "rfc3339"))]
116 {
117 let mut this = self;
118 match precision {
119 Precision::Millis => this.nanos -= this.nanos % 1_000_000,
120 Precision::Micros => this.nanos -= this.nanos % 1_000,
121 _ => {}
122 };
123 let v = this.unix_epoch() as f64 + this.nanos() as f64 / 1e9;
124 encoder.write_lit(Literal::L8(v.to_bits()), [TAG_EPOCH])
125 }
126 }
127 }
128
129 pub fn unix_epoch(&self) -> i64 {
131 self.unix_epoch
132 }
133
134 pub fn nanos(&self) -> u32 {
136 self.nanos
137 }
138
139 pub fn tz_sec_east(&self) -> i32 {
141 self.tz_sec_east
142 }
143}
144
145#[cfg(feature = "rfc3339")]
146mod rfc3339 {
147 use super::Timestamp;
148 use chrono::{DateTime, FixedOffset, Offset, TimeZone, Utc};
149 use std::convert::TryFrom;
150
151 impl TryFrom<Timestamp> for DateTime<FixedOffset> {
152 type Error = ();
153
154 fn try_from(t: Timestamp) -> Result<Self, Self::Error> {
155 Ok(FixedOffset::east_opt(t.tz_sec_east())
156 .ok_or(())?
157 .from_utc_datetime(
158 &DateTime::<Utc>::from_timestamp(t.unix_epoch(), t.nanos())
159 .ok_or(())?
160 .naive_utc(),
161 ))
162 }
163 }
164
165 impl<Tz: TimeZone> From<DateTime<Tz>> for Timestamp {
166 fn from(dt: DateTime<Tz>) -> Self {
167 Timestamp {
168 unix_epoch: dt.timestamp(),
169 nanos: dt.timestamp_subsec_nanos(),
170 tz_sec_east: dt.offset().fix().local_minus_utc(),
171 }
172 }
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::{
179 Precision::{self, *},
180 Timestamp,
181 };
182 use crate::{constants::TAG_EPOCH, CborBuilder, Literal, Writer};
183
184 #[test]
185 fn encode() {
186 fn e(t: i64, n: u32, tz: i32, p: Precision) -> String {
187 let t = Timestamp {
188 unix_epoch: t,
189 nanos: n,
190 tz_sec_east: tz,
191 };
192 t.encode(CborBuilder::new(), p).to_string()
193 }
194
195 assert_eq!(e(0, 0, 0, Seconds), "1(0)");
196 assert_eq!(e(0, 900_000_000, 0, Seconds), "1(0)");
197 assert_eq!(e(-1, 0, 0, Seconds), "1(-1)");
198 assert_eq!(e(-1, 900_000_000, 0, Seconds), "1(-1)");
199 assert_eq!(e(i64::MAX, 0, 0, Seconds), "1(9223372036854775807)");
200 assert_eq!(e(i64::MIN, 0, 0, Seconds), "1(-9223372036854775808)");
201
202 assert_eq!(e(0, 500_000_000, 0, Millis), "1(0.5)");
203 #[cfg(feature = "rfc3339")]
204 {
205 assert_eq!(
206 e((1 << 52) / 1_000_000, 123_456_789, 0, Micros),
207 "1(4503599627.123456)"
208 );
209 assert_eq!(
210 e((1 << 52) / 1_000_000 + 1, 123_456_789, 2700, Micros),
211 "0(\"2112-09-18T00:38:48.123456+00:45\")"
212 );
213 assert_eq!(
214 e((1 << 52) / 1_000_000 + 1, 123_000_000, 2700, Micros),
215 "0(\"2112-09-18T00:38:48.123+00:45\")"
216 );
217 assert_eq!(
218 e((1 << 52) / 1_000_000_000, 123_456_789, 0, Nanos),
219 "1(4503599.123456789)"
220 );
221 assert_eq!(
222 e((1 << 52) / 1_000_000_000 + 1, 123_456_789, -1800, Nanos),
223 "0(\"1970-02-22T02:30:00.123456789-00:30\")"
224 );
225 assert_eq!(
226 e((1 << 52) / 1_000_000_000 + 1, 123_000_000, -1800, Nanos),
227 "0(\"1970-02-22T02:30:00.123-00:30\")"
228 );
229 }
230 #[cfg(not(feature = "rfc3339"))]
231 {
232 assert_eq!(
233 e((1 << 52) / 1_000_000, 123_456_789, 0, Micros),
234 "1(4503599627.123456)"
235 );
236 assert_eq!(
237 e((1 << 52) / 1_000_000 + 1, 123_456_789, 2700, Micros),
238 "1(4503599628.123456)"
239 );
240 assert_eq!(
241 e((1 << 52) / 1_000_000_000, 123_456_789, 0, Nanos),
242 "1(4503599.123456789)"
243 );
244 assert_eq!(
245 e((1 << 52) / 1_000_000_000 + 1, 123_456_789, -1800, Nanos),
246 "1(4503600.123456789)"
247 );
248 }
249 }
250
251 #[test]
252 #[cfg(feature = "rfc3339")]
253 fn string() {
254 use crate::{constants::TAG_ISO8601, Writer};
255
256 let cbor = CborBuilder::new().write_str("2020-07-12T02:14:00.43-04:40", [TAG_ISO8601]);
257 assert_eq!(
258 Timestamp::from_string(cbor.tagged_item()).unwrap(),
259 Timestamp::new(1594536840, 430_000_000, -16800)
260 );
261 }
262
263 #[test]
264 fn epoch() {
265 let cbor = CborBuilder::new().write_pos(1594536840, [TAG_EPOCH]);
266 assert_eq!(
267 Timestamp::from_epoch(cbor.tagged_item()).unwrap(),
268 Timestamp::new(1594536840, 0, 0)
269 );
270
271 let cbor = CborBuilder::new().write_neg(1594536840, [TAG_EPOCH]);
272 assert_eq!(
273 Timestamp::from_epoch(cbor.tagged_item()).unwrap(),
274 Timestamp::new(-1594536841, 0, 0)
275 );
276
277 let cbor =
278 CborBuilder::new().write_lit(Literal::L8(1594536840.01_f64.to_bits()), [TAG_EPOCH]);
279 assert_eq!(
280 Timestamp::from_epoch(cbor.tagged_item()).unwrap(),
281 Timestamp::new(1594536840, 9_999_990, 0) );
283
284 let cbor =
285 CborBuilder::new().write_lit(Literal::L8(15945368400.01_f64.to_bits()), [TAG_EPOCH]);
286 assert_eq!(
287 Timestamp::from_epoch(cbor.tagged_item()).unwrap(),
288 Timestamp::new(15945368400, 10_000_000, 0) );
290
291 let cbor =
292 CborBuilder::new().write_lit(Literal::L8((-15945368400.01_f64).to_bits()), [TAG_EPOCH]);
293 assert_eq!(
294 Timestamp::from_epoch(cbor.tagged_item()).unwrap(),
295 Timestamp::new(-15945368401, 990_000_000, 0)
296 );
297 }
298}