xee_interpreter/atomic/
datetime.rs

1use chrono::{Offset, TimeZone};
2
3use crate::{atomic::Atomic, error};
4
5pub(crate) trait EqWithDefaultOffset: ToDateTimeStamp {
6    fn eq_with_default_offset(&self, other: &Self, default_offset: chrono::FixedOffset) -> bool {
7        let self_date_time_stamp = self.to_date_time_stamp(default_offset);
8        let other_date_time_stamp = other.to_date_time_stamp(default_offset);
9        self_date_time_stamp == other_date_time_stamp
10    }
11}
12
13pub(crate) trait OrdWithDefaultOffset: ToDateTimeStamp {
14    fn cmp_with_default_offset(
15        &self,
16        other: &Self,
17        default_offset: chrono::FixedOffset,
18    ) -> std::cmp::Ordering {
19        let self_date_time_stamp = self.to_date_time_stamp(default_offset);
20        let other_date_time_stamp = other.to_date_time_stamp(default_offset);
21        self_date_time_stamp.cmp(&other_date_time_stamp)
22    }
23}
24
25pub(crate) trait ToDateTimeStamp {
26    fn to_date_time_stamp(
27        &self,
28        default_offset: chrono::FixedOffset,
29    ) -> chrono::DateTime<chrono::FixedOffset>;
30
31    fn to_naive_date_time(&self, default_offset: chrono::FixedOffset) -> chrono::NaiveDateTime {
32        self.to_date_time_stamp(default_offset).naive_utc()
33    }
34}
35
36impl<T> EqWithDefaultOffset for T where T: ToDateTimeStamp {}
37impl<T> OrdWithDefaultOffset for T where T: ToDateTimeStamp {}
38
39#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
40pub struct YearMonthDuration {
41    pub(crate) months: i64,
42}
43
44impl YearMonthDuration {
45    pub(crate) fn new(months: i64) -> Self {
46        Self { months }
47    }
48
49    pub(crate) fn years(&self) -> i64 {
50        self.months / 12
51    }
52
53    pub(crate) fn months(&self) -> i64 {
54        self.months % 12
55    }
56}
57
58impl From<YearMonthDuration> for Atomic {
59    fn from(year_month_duration: YearMonthDuration) -> Self {
60        Atomic::YearMonthDuration(year_month_duration)
61    }
62}
63
64/// A Duration is a combination of a [`YearMonthDuration`]` and
65/// [`chrono::Duration`].
66///
67/// It represents `xs:duration`
68#[derive(Debug, Clone, PartialEq, Eq, Hash)]
69pub struct Duration {
70    pub(crate) year_month: YearMonthDuration,
71    pub(crate) day_time: chrono::Duration,
72}
73
74impl Duration {
75    pub(crate) fn new(months: i64, day_time: chrono::Duration) -> Self {
76        Self {
77            year_month: YearMonthDuration { months },
78            day_time,
79        }
80    }
81
82    pub(crate) fn from_year_month(year_month_duration: YearMonthDuration) -> Self {
83        Self {
84            year_month: year_month_duration,
85            day_time: chrono::Duration::zero(),
86        }
87    }
88
89    pub(crate) fn from_day_time(duration: chrono::Duration) -> Self {
90        Self {
91            year_month: YearMonthDuration { months: 0 },
92            day_time: duration,
93        }
94    }
95}
96
97impl From<Duration> for Atomic {
98    fn from(duration: Duration) -> Self {
99        Atomic::Duration(duration.into())
100    }
101}
102
103impl TryFrom<Atomic> for Duration {
104    type Error = error::Error;
105
106    fn try_from(a: Atomic) -> Result<Self, Self::Error> {
107        match a {
108            Atomic::Duration(d) => Ok(d.as_ref().clone()),
109            Atomic::YearMonthDuration(d) => Ok(Duration::from_year_month(d)),
110            Atomic::DayTimeDuration(d) => Ok(Duration::from_day_time(*d)),
111            _ => Err(error::Error::XPTY0004),
112        }
113    }
114}
115
116impl TryFrom<Atomic> for chrono::Duration {
117    type Error = error::Error;
118
119    fn try_from(a: Atomic) -> Result<Self, Self::Error> {
120        match a {
121            Atomic::DayTimeDuration(d) => Ok(*d.as_ref()),
122            _ => Err(error::Error::XPTY0004),
123        }
124    }
125}
126
127/// A `NaiveDateTimeWithOffset` is a combination of a [`chrono::NaiveDateTime`] and
128/// an optional [`chrono::FixedOffset`].
129///
130/// It represents `xs:dateTime`
131#[derive(Debug, Clone, PartialEq, Eq, Hash)]
132pub struct NaiveDateTimeWithOffset {
133    pub(crate) date_time: chrono::NaiveDateTime,
134    pub(crate) offset: Option<chrono::FixedOffset>,
135}
136
137impl From<NaiveDateTimeWithOffset> for chrono::DateTime<chrono::FixedOffset> {
138    fn from(naive_date_time_with_offset: NaiveDateTimeWithOffset) -> Self {
139        let offset = naive_date_time_with_offset
140            .offset
141            .unwrap_or_else(|| chrono::offset::Utc.fix());
142        chrono::DateTime::from_naive_utc_and_offset(naive_date_time_with_offset.date_time, offset)
143    }
144}
145
146impl From<chrono::DateTime<chrono::FixedOffset>> for NaiveDateTimeWithOffset {
147    fn from(date_time: chrono::DateTime<chrono::FixedOffset>) -> Self {
148        NaiveDateTimeWithOffset::new(date_time.naive_local(), Some(*date_time.offset()))
149    }
150}
151
152impl From<NaiveDateTimeWithOffset> for Atomic {
153    fn from(date_time: NaiveDateTimeWithOffset) -> Self {
154        Atomic::DateTime(date_time.into())
155    }
156}
157
158impl TryFrom<Atomic> for NaiveDateTimeWithOffset {
159    type Error = error::Error;
160
161    fn try_from(a: Atomic) -> Result<Self, Self::Error> {
162        match a {
163            Atomic::DateTime(d) => Ok(d.as_ref().clone()),
164            Atomic::DateTimeStamp(d) => Ok((*d.as_ref()).into()),
165            _ => Err(error::Error::XPTY0004),
166        }
167    }
168}
169
170impl ToDateTimeStamp for NaiveDateTimeWithOffset {
171    fn to_date_time_stamp(
172        &self,
173        default_offset: chrono::FixedOffset,
174    ) -> chrono::DateTime<chrono::FixedOffset> {
175        let offset = self.offset.unwrap_or(default_offset);
176        offset.from_local_datetime(&self.date_time).unwrap()
177    }
178}
179
180impl NaiveDateTimeWithOffset {
181    pub(crate) fn new(
182        date_time: chrono::NaiveDateTime,
183        offset: Option<chrono::FixedOffset>,
184    ) -> Self {
185        Self { date_time, offset }
186    }
187}
188
189/// A `NaiveTimeWithOffset` is a combination of a [`chrono::NaiveTime`] and
190/// an optional [`chrono::FixedOffset`].
191///
192/// It represents `xs:time`
193#[derive(Debug, Clone, PartialEq, Eq, Hash)]
194pub struct NaiveTimeWithOffset {
195    pub(crate) time: chrono::NaiveTime,
196    pub(crate) offset: Option<chrono::FixedOffset>,
197}
198
199impl TryFrom<Atomic> for NaiveTimeWithOffset {
200    type Error = error::Error;
201
202    fn try_from(a: Atomic) -> Result<Self, Self::Error> {
203        match a {
204            Atomic::Time(d) => Ok(d.as_ref().clone()),
205            _ => Err(error::Error::XPTY0004),
206        }
207    }
208}
209
210impl NaiveTimeWithOffset {
211    pub(crate) fn new(time: chrono::NaiveTime, offset: Option<chrono::FixedOffset>) -> Self {
212        Self { time, offset }
213    }
214}
215
216impl ToDateTimeStamp for NaiveTimeWithOffset {
217    fn to_date_time_stamp(
218        &self,
219        default_offset: chrono::FixedOffset,
220    ) -> chrono::DateTime<chrono::FixedOffset> {
221        let offset = self.offset.unwrap_or(default_offset);
222        // https://www.w3.org/TR/xpath-functions-31/#func-subtract-times
223        let date_time = chrono::NaiveDate::from_ymd_opt(1972, 12, 31)
224            .unwrap()
225            .and_time(self.time);
226        offset.from_local_datetime(&date_time).unwrap()
227    }
228}
229
230impl From<NaiveTimeWithOffset> for Atomic {
231    fn from(time: NaiveTimeWithOffset) -> Self {
232        Atomic::Time(time.into())
233    }
234}
235
236/// A `NaiveDateWithOffset` is a combination of a [`chrono::NaiveDate`] and
237/// an optional [`chrono::FixedOffset`].
238///
239/// It represents `xs:date`
240#[derive(Debug, Clone, PartialEq, Eq, Hash)]
241pub struct NaiveDateWithOffset {
242    pub(crate) date: chrono::NaiveDate,
243    pub(crate) offset: Option<chrono::FixedOffset>,
244}
245
246impl TryFrom<Atomic> for NaiveDateWithOffset {
247    type Error = error::Error;
248
249    fn try_from(a: Atomic) -> Result<Self, Self::Error> {
250        match a {
251            Atomic::Date(d) => Ok(d.as_ref().clone()),
252            _ => Err(error::Error::XPTY0004),
253        }
254    }
255}
256
257impl NaiveDateWithOffset {
258    pub(crate) fn new(date: chrono::NaiveDate, offset: Option<chrono::FixedOffset>) -> Self {
259        Self { date, offset }
260    }
261}
262
263impl ToDateTimeStamp for NaiveDateWithOffset {
264    fn to_date_time_stamp(
265        &self,
266        default_offset: chrono::FixedOffset,
267    ) -> chrono::DateTime<chrono::FixedOffset> {
268        let offset = self.offset.unwrap_or(default_offset);
269        let date_time = self.date.and_hms_opt(0, 0, 0).unwrap();
270        offset.from_local_datetime(&date_time).unwrap()
271    }
272}
273
274impl From<NaiveDateWithOffset> for Atomic {
275    fn from(date: NaiveDateWithOffset) -> Self {
276        Atomic::Date(date.into())
277    }
278}
279
280/// A `GYearMonth` is a combination of a year and a month, and an optional
281/// [`chrono::FixedOffset`].
282///
283/// It represents `xs:gYearMonth`
284#[derive(Debug, Clone, PartialEq, Eq, Hash)]
285pub struct GYearMonth {
286    pub(crate) year: i32,
287    pub(crate) month: u32,
288    pub(crate) offset: Option<chrono::FixedOffset>,
289}
290
291impl GYearMonth {
292    pub(crate) fn new(year: i32, month: u32, offset: Option<chrono::FixedOffset>) -> Self {
293        Self {
294            year,
295            month,
296            offset,
297        }
298    }
299}
300
301impl From<GYearMonth> for Atomic {
302    fn from(g_year_month: GYearMonth) -> Self {
303        Atomic::GYearMonth(g_year_month.into())
304    }
305}
306
307/// A `GYear` is a combination of a year and an optional
308/// [`chrono::FixedOffset`].
309///
310/// It represents `xs:gYear`
311#[derive(Debug, Clone, PartialEq, Eq, Hash)]
312pub struct GYear {
313    pub(crate) year: i32,
314    pub(crate) offset: Option<chrono::FixedOffset>,
315}
316
317impl GYear {
318    pub(crate) fn new(year: i32, offset: Option<chrono::FixedOffset>) -> Self {
319        Self { year, offset }
320    }
321}
322
323impl From<GYear> for Atomic {
324    fn from(g_year: GYear) -> Self {
325        Atomic::GYear(g_year.into())
326    }
327}
328
329/// A `GMonthDay` is a combination of a month and a day, and an optional
330/// [`chrono::FixedOffset`].
331///
332/// It represents `xs:gMonthDay`
333#[derive(Debug, Clone, PartialEq, Eq, Hash)]
334pub struct GMonthDay {
335    pub(crate) month: u32,
336    pub(crate) day: u32,
337    pub(crate) offset: Option<chrono::FixedOffset>,
338}
339
340impl GMonthDay {
341    pub(crate) fn new(month: u32, day: u32, offset: Option<chrono::FixedOffset>) -> Self {
342        Self { month, day, offset }
343    }
344}
345
346impl From<GMonthDay> for Atomic {
347    fn from(g_month_day: GMonthDay) -> Self {
348        Atomic::GMonthDay(g_month_day.into())
349    }
350}
351
352/// A `GDay` is a combination of a day and an optional
353/// [`chrono::FixedOffset`].
354///
355/// It represents `xs:gDay`
356#[derive(Debug, Clone, PartialEq, Eq, Hash)]
357pub struct GDay {
358    pub(crate) day: u32,
359    pub(crate) offset: Option<chrono::FixedOffset>,
360}
361
362impl GDay {
363    pub(crate) fn new(day: u32, offset: Option<chrono::FixedOffset>) -> Self {
364        Self { day, offset }
365    }
366}
367
368impl From<GDay> for Atomic {
369    fn from(g_day: GDay) -> Self {
370        Atomic::GDay(g_day.into())
371    }
372}
373
374/// A `GMonth` is a combination of a month and an optional
375/// [`chrono::FixedOffset`].
376///
377/// It represents `xs:gMonth`
378#[derive(Debug, Clone, PartialEq, Eq, Hash)]
379pub struct GMonth {
380    pub(crate) month: u32,
381    pub(crate) offset: Option<chrono::FixedOffset>,
382}
383
384impl GMonth {
385    pub(crate) fn new(month: u32, offset: Option<chrono::FixedOffset>) -> Self {
386        Self { month, offset }
387    }
388}
389
390impl From<GMonth> for Atomic {
391    fn from(g_month: GMonth) -> Self {
392        Atomic::GMonth(g_month.into())
393    }
394}
395
396impl From<chrono::Duration> for Atomic {
397    fn from(duration: chrono::Duration) -> Self {
398        Atomic::DayTimeDuration(duration.into())
399    }
400}
401
402impl From<chrono::DateTime<chrono::FixedOffset>> for Atomic {
403    fn from(date_time: chrono::DateTime<chrono::FixedOffset>) -> Self {
404        Atomic::DateTimeStamp(date_time.into())
405    }
406}
407
408#[cfg(test)]
409mod tests {
410    use crate::atomic::{AtomicCompare, OpGt};
411
412    use super::*;
413
414    #[test]
415    fn test_compare_dates() {
416        let a_date = NaiveDateWithOffset::new(
417            chrono::NaiveDate::from_ymd_opt(2004, 12, 25).unwrap(),
418            Some(chrono::offset::Utc.fix()),
419        );
420        let b_date = NaiveDateWithOffset::new(
421            chrono::NaiveDate::from_ymd_opt(2004, 12, 25).unwrap(),
422            Some(chrono::FixedOffset::east_opt(60 * 60 * 7).unwrap()),
423        );
424
425        let a: Atomic = Atomic::Date(a_date.into());
426        let b: Atomic = Atomic::Date(b_date.into());
427
428        assert!(
429            OpGt::atomic_compare(a.clone(), b.clone(), str::cmp, chrono::offset::Utc.fix())
430                .unwrap()
431        );
432    }
433}