1use std::{
8 cmp::Ordering,
9 fmt,
10 io::{Read, Write},
11 ops::{Add, Sub},
12 str::FromStr,
13};
14
15use chrono::{Duration, SecondsFormat, TimeDelta, TimeZone, Timelike, Utc};
16use tracing::error;
17
18use crate::{encoding::*, Context};
19
20const NANOS_PER_SECOND: i64 = 1_000_000_000;
21const NANOS_PER_TICK: i64 = 100;
22const TICKS_PER_SECOND: i64 = NANOS_PER_SECOND / NANOS_PER_TICK;
23
24const MIN_YEAR: u16 = 1601;
25const MAX_YEAR: u16 = 9999;
26
27pub type DateTimeUtc = chrono::DateTime<Utc>;
29
30#[derive(PartialEq, Debug, Clone, Copy, Eq)]
33pub struct DateTime {
34 date_time: DateTimeUtc,
35}
36
37impl crate::UaNullable for DateTime {
38 fn is_ua_null(&self) -> bool {
39 self.is_null()
40 }
41}
42
43#[cfg(feature = "json")]
44mod json {
45 use crate::{json::*, Error};
46
47 use super::DateTime;
48
49 impl JsonEncodable for DateTime {
50 fn encode(
51 &self,
52 stream: &mut JsonStreamWriter<&mut dyn std::io::Write>,
53 _ctx: &crate::Context<'_>,
54 ) -> super::EncodingResult<()> {
55 Ok(stream.string_value(&self.to_rfc3339())?)
56 }
57 }
58
59 impl JsonDecodable for DateTime {
60 fn decode(
61 stream: &mut JsonStreamReader<&mut dyn std::io::Read>,
62 _ctx: &Context<'_>,
63 ) -> super::EncodingResult<Self> {
64 let v = stream.next_str()?;
65 let dt = DateTime::parse_from_rfc3339(v)
66 .map_err(|e| Error::decoding(format!("Cannot parse date time \"{v}\": {e}")))?;
67 Ok(dt)
68 }
69 }
70}
71
72#[cfg(feature = "xml")]
73mod xml {
74 use crate::xml::*;
75 use std::io::{Read, Write};
76
77 use super::DateTime;
78
79 impl XmlType for DateTime {
80 const TAG: &'static str = "DateTime";
81 }
82
83 impl XmlEncodable for DateTime {
84 fn encode(
85 &self,
86 writer: &mut XmlStreamWriter<&mut dyn Write>,
87 context: &Context<'_>,
88 ) -> EncodingResult<()> {
89 self.to_rfc3339().encode(writer, context)
90 }
91 }
92
93 impl XmlDecodable for DateTime {
94 fn decode(
95 read: &mut XmlStreamReader<&mut dyn Read>,
96 _context: &Context<'_>,
97 ) -> Result<Self, Error> {
98 let v = read.consume_as_text()?;
99 let dt = DateTime::parse_from_rfc3339(&v)
100 .map_err(|e| Error::decoding(format!("Cannot parse date time \"{v}\": {e}")))?;
101 Ok(dt)
102 }
103 }
104}
105
106impl BinaryEncodable for DateTime {
108 fn byte_len(&self, _ctx: &Context<'_>) -> usize {
109 8
110 }
111
112 fn encode<S: Write + ?Sized>(&self, stream: &mut S, _ctx: &Context<'_>) -> EncodingResult<()> {
113 let ticks = self.checked_ticks();
114 write_i64(stream, ticks)
115 }
116}
117
118impl BinaryDecodable for DateTime {
119 fn decode<S: Read + ?Sized>(stream: &mut S, ctx: &Context<'_>) -> EncodingResult<Self> {
120 let ticks = read_i64(stream)?;
121 let date_time = DateTime::from(ticks);
122 Ok(date_time - ctx.options().client_offset)
125 }
126}
127
128impl Default for DateTime {
129 fn default() -> Self {
130 DateTime::epoch()
131 }
132}
133
134impl Add<Duration> for DateTime {
135 type Output = Self;
136
137 fn add(self, duration: Duration) -> Self {
138 DateTime::from(self.date_time + duration)
139 }
140}
141
142impl Sub<DateTime> for DateTime {
143 type Output = Duration;
144
145 fn sub(self, other: Self) -> Duration {
146 self.date_time - other.date_time
147 }
148}
149
150impl Sub<Duration> for DateTime {
151 type Output = Self;
152
153 fn sub(self, duration: Duration) -> Self {
154 DateTime::from(self.date_time - duration)
155 }
156}
157
158impl PartialOrd for DateTime {
159 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
160 Some(self.cmp(other))
161 }
162}
163
164impl Ord for DateTime {
165 fn cmp(&self, other: &Self) -> Ordering {
166 self.date_time.cmp(&other.date_time)
167 }
168}
169
170impl From<(u16, u16, u16, u16, u16, u16)> for DateTime {
172 fn from(dt: (u16, u16, u16, u16, u16, u16)) -> Self {
173 let (year, month, day, hour, minute, second) = dt;
174 DateTime::from((year, month, day, hour, minute, second, 0))
175 }
176}
177
178impl From<(u16, u16, u16, u16, u16, u16, u32)> for DateTime {
180 fn from(dt: (u16, u16, u16, u16, u16, u16, u32)) -> Self {
181 let (year, month, day, hour, minute, second, nanos) = dt;
182 if !(1..=12).contains(&month) {
183 panic!("Invalid month");
184 }
185 if !(1..=31).contains(&day) {
186 panic!("Invalid day");
187 }
188 if hour > 23 {
189 panic!("Invalid hour");
190 }
191 if minute > 59 {
192 panic!("Invalid minute");
193 }
194 if second > 59 {
195 panic!("Invalid second");
196 }
197 if nanos as i64 >= NANOS_PER_SECOND {
198 panic!("Invalid nanosecond");
199 }
200 let dt = Utc
201 .with_ymd_and_hms(
202 year as i32,
203 month as u32,
204 day as u32,
205 hour as u32,
206 minute as u32,
207 second as u32,
208 )
209 .unwrap()
210 .with_nanosecond(nanos)
211 .unwrap();
212 DateTime::from(dt)
213 }
214}
215
216impl From<DateTimeUtc> for DateTime {
217 fn from(date_time: DateTimeUtc) -> Self {
218 let nanos = (date_time.nanosecond() / NANOS_PER_TICK as u32) * NANOS_PER_TICK as u32;
220 let date_time = date_time.with_nanosecond(nanos).unwrap();
221 DateTime { date_time }
222 }
223}
224
225impl From<i64> for DateTime {
226 fn from(value: i64) -> Self {
227 if value == i64::MAX {
228 Self::endtimes()
230 } else {
231 let secs = value / TICKS_PER_SECOND;
232 let nanos = (value - secs * TICKS_PER_SECOND) * NANOS_PER_TICK;
233 let duration = TimeDelta::try_seconds(secs).unwrap() + Duration::nanoseconds(nanos);
234 Self::from(Self::epoch_chrono() + duration)
235 }
236 }
237}
238
239impl From<DateTime> for i64 {
240 fn from(value: DateTime) -> Self {
241 value.checked_ticks()
242 }
243}
244
245impl From<DateTime> for DateTimeUtc {
246 fn from(value: DateTime) -> Self {
247 value.as_chrono()
248 }
249}
250
251impl fmt::Display for DateTime {
252 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
253 write!(f, "{}", self.date_time.to_rfc3339())
254 }
255}
256
257impl FromStr for DateTime {
258 type Err = chrono::ParseError;
259
260 fn from_str(s: &str) -> Result<Self, Self::Err> {
261 DateTimeUtc::from_str(s)
262 .map(DateTime::from)
263 .inspect_err(|e| {
264 error!("Cannot parse date {}, error = {}", s, e);
265 })
266 }
267}
268
269impl DateTime {
270 pub fn now() -> DateTime {
272 DateTime::from(Utc::now())
273 }
274
275 #[cfg(test)]
278 pub fn rfc3339_now() -> DateTime {
279 use std::time::{SystemTime, UNIX_EPOCH};
280
281 let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
282 let now = DateTimeUtc::from_timestamp(duration.as_secs() as i64, 0).unwrap();
283 DateTime::from(now)
284 }
285
286 pub fn now_with_offset(offset: Duration) -> DateTime {
288 DateTime::from(Utc::now() + offset)
289 }
290
291 pub fn null() -> DateTime {
293 DateTime::epoch()
295 }
296
297 pub fn is_null(&self) -> bool {
299 self.ticks() == 0i64
300 }
301
302 pub fn epoch() -> DateTime {
304 DateTime::from(Self::epoch_chrono())
305 }
306
307 pub fn endtimes() -> DateTime {
309 DateTime::from(Self::endtimes_chrono())
310 }
311
312 pub fn endtimes_ticks() -> i64 {
314 Self::duration_to_ticks(Self::endtimes_chrono().signed_duration_since(Self::epoch_chrono()))
315 }
316
317 pub fn ymd(year: u16, month: u16, day: u16) -> DateTime {
319 DateTime::ymd_hms(year, month, day, 0, 0, 0)
320 }
321
322 pub fn ymd_hms(
324 year: u16,
325 month: u16,
326 day: u16,
327 hour: u16,
328 minute: u16,
329 second: u16,
330 ) -> DateTime {
331 DateTime::from((year, month, day, hour, minute, second))
332 }
333
334 pub fn ymd_hms_nano(
336 year: u16,
337 month: u16,
338 day: u16,
339 hour: u16,
340 minute: u16,
341 second: u16,
342 nanos: u32,
343 ) -> DateTime {
344 DateTime::from((year, month, day, hour, minute, second, nanos))
345 }
346
347 pub fn to_rfc3339(&self) -> String {
349 self.date_time.to_rfc3339_opts(SecondsFormat::Millis, true)
350 }
351
352 pub fn parse_from_rfc3339(s: &str) -> Result<DateTime, chrono::ParseError> {
354 let date_time = chrono::DateTime::parse_from_rfc3339(s)?;
355 let mut date_time = date_time.with_timezone(&Utc);
357 if date_time < Self::epoch_chrono() {
358 date_time = Self::epoch_chrono();
359 }
360 if date_time > Self::endtimes_chrono() {
362 date_time = Self::endtimes_chrono();
363 }
364
365 Ok(Self { date_time })
366 }
367
368 pub fn ticks(&self) -> i64 {
370 Self::duration_to_ticks(self.date_time.signed_duration_since(Self::epoch_chrono()))
371 }
372
373 pub fn checked_ticks(&self) -> i64 {
376 let nanos = self.ticks();
377 if nanos < 0 {
378 return 0;
379 }
380 if nanos > Self::endtimes_ticks() {
381 return i64::MAX;
382 }
383 nanos
384 }
385
386 pub fn as_chrono(&self) -> DateTimeUtc {
388 self.date_time
389 }
390
391 fn epoch_chrono() -> DateTimeUtc {
393 Utc.with_ymd_and_hms(MIN_YEAR as i32, 1, 1, 0, 0, 0)
394 .unwrap()
395 }
396
397 fn endtimes_chrono() -> DateTimeUtc {
400 Utc.with_ymd_and_hms(MAX_YEAR as i32, 12, 31, 23, 59, 59)
401 .unwrap()
402 }
403
404 fn duration_to_ticks(duration: Duration) -> i64 {
406 let seconds_part = TimeDelta::try_seconds(duration.num_seconds()).unwrap();
409 let seconds = seconds_part.num_seconds();
410 let nanos = (duration - seconds_part).num_nanoseconds().unwrap();
411 seconds * TICKS_PER_SECOND + nanos / NANOS_PER_TICK
413 }
414}