oxsdatatypes/
date_time.rs

1#![allow(clippy::expect_used)]
2
3use crate::{DayTimeDuration, Decimal, Duration, YearMonthDuration};
4use std::cmp::{min, Ordering};
5use std::fmt;
6use std::hash::{Hash, Hasher};
7use std::str::FromStr;
8
9/// [XML Schema `dateTime` datatype](https://www.w3.org/TR/xmlschema11-2/#dateTime)
10///
11/// It encodes the value using a number of seconds from the Gregorian calendar era using a [`Decimal`]
12/// and an optional timezone offset in minutes.
13#[derive(Eq, PartialEq, PartialOrd, Debug, Clone, Copy, Hash)]
14pub struct DateTime {
15    timestamp: Timestamp,
16}
17
18impl DateTime {
19    pub const MAX: Self = Self {
20        timestamp: Timestamp::MAX,
21    };
22    pub const MIN: Self = Self {
23        timestamp: Timestamp::MIN,
24    };
25
26    #[inline]
27    pub(super) fn new(
28        year: i64,
29        month: u8,
30        day: u8,
31        hour: u8,
32        minute: u8,
33        second: Decimal,
34        timezone_offset: Option<TimezoneOffset>,
35    ) -> Result<Self, DateTimeOverflowError> {
36        Ok(Self {
37            timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
38                year: Some(year),
39                month: Some(month),
40                day: Some(day),
41                hour: Some(hour),
42                minute: Some(minute),
43                second: Some(second),
44                timezone_offset,
45            })?,
46        })
47    }
48
49    /// [fn:current-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-current-dateTime)
50    #[inline]
51    pub fn now() -> Self {
52        Self {
53            timestamp: Timestamp::now(),
54        }
55    }
56
57    #[inline]
58    #[must_use]
59    pub fn from_be_bytes(bytes: [u8; 18]) -> Self {
60        Self {
61            timestamp: Timestamp::from_be_bytes(bytes),
62        }
63    }
64
65    /// [fn:year-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-year-from-dateTime)
66    #[inline]
67    #[must_use]
68    pub fn year(self) -> i64 {
69        self.timestamp.year()
70    }
71
72    /// [fn:month-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-month-from-dateTime)
73    #[inline]
74    #[must_use]
75    pub fn month(self) -> u8 {
76        self.timestamp.month()
77    }
78
79    /// [fn:day-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-day-from-dateTime)
80    #[inline]
81    #[must_use]
82    pub fn day(self) -> u8 {
83        self.timestamp.day()
84    }
85
86    /// [fn:hour-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-hours-from-dateTime)
87    #[inline]
88    #[must_use]
89    pub fn hour(self) -> u8 {
90        self.timestamp.hour()
91    }
92
93    /// [fn:minute-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-minutes-from-dateTime)
94    #[inline]
95    #[must_use]
96    pub fn minute(self) -> u8 {
97        self.timestamp.minute()
98    }
99
100    /// [fn:second-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-seconds-from-dateTime)
101    #[inline]
102    #[must_use]
103    pub fn second(self) -> Decimal {
104        self.timestamp.second()
105    }
106
107    /// [fn:timezone-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-timezone-from-dateTime)
108    #[inline]
109    #[must_use]
110    pub fn timezone(self) -> Option<DayTimeDuration> {
111        Some(self.timezone_offset()?.into())
112    }
113
114    #[inline]
115    #[must_use]
116    pub fn timezone_offset(self) -> Option<TimezoneOffset> {
117        self.timestamp.timezone_offset()
118    }
119
120    #[inline]
121    fn properties(self) -> DateTimeSevenPropertyModel {
122        DateTimeSevenPropertyModel {
123            year: Some(self.year()),
124            month: Some(self.month()),
125            day: Some(self.day()),
126            hour: Some(self.hour()),
127            minute: Some(self.minute()),
128            second: Some(self.second()),
129            timezone_offset: self.timezone_offset(),
130        }
131    }
132
133    #[inline]
134    #[must_use]
135    pub fn to_be_bytes(self) -> [u8; 18] {
136        self.timestamp.to_be_bytes()
137    }
138
139    /// [op:subtract-dateTimes](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dateTimes)
140    ///
141    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
142    #[inline]
143    #[must_use]
144    pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<DayTimeDuration> {
145        self.timestamp.checked_sub(rhs.into().timestamp)
146    }
147
148    /// [op:add-yearMonthDuration-to-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDuration-to-dateTime)
149    ///
150    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
151    #[inline]
152    #[must_use]
153    pub fn checked_add_year_month_duration(
154        self,
155        rhs: impl Into<YearMonthDuration>,
156    ) -> Option<Self> {
157        self.checked_add_duration(Duration::from(rhs.into()))
158    }
159
160    /// [op:add-dayTimeDuration-to-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-dateTime)
161    ///
162    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
163    #[inline]
164    #[must_use]
165    pub fn checked_add_day_time_duration(self, rhs: impl Into<Duration>) -> Option<Self> {
166        let rhs = rhs.into();
167        Some(Self {
168            timestamp: self.timestamp.checked_add_seconds(rhs.all_seconds())?,
169        })
170    }
171
172    /// [op:add-yearMonthDuration-to-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDuration-to-dateTime) and [op:add-dayTimeDuration-to-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-dateTime)
173    ///
174    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
175    #[inline]
176    #[must_use]
177    pub fn checked_add_duration(self, rhs: impl Into<Duration>) -> Option<Self> {
178        let rhs = rhs.into();
179        if let Ok(rhs) = DayTimeDuration::try_from(rhs) {
180            self.checked_add_day_time_duration(rhs)
181        } else {
182            Some(Self {
183                timestamp: Timestamp::new(&date_time_plus_duration(rhs, &self.properties())?)
184                    .ok()?,
185            })
186        }
187    }
188
189    /// [op:subtract-yearMonthDuration-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDuration-from-dateTime)
190    ///
191    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
192    #[inline]
193    #[must_use]
194    pub fn checked_sub_year_month_duration(
195        self,
196        rhs: impl Into<YearMonthDuration>,
197    ) -> Option<Self> {
198        self.checked_sub_duration(Duration::from(rhs.into()))
199    }
200
201    /// [op:subtract-dayTimeDuration-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDuration-from-dateTime)
202    ///
203    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
204    #[inline]
205    #[must_use]
206    pub fn checked_sub_day_time_duration(self, rhs: impl Into<DayTimeDuration>) -> Option<Self> {
207        let rhs = rhs.into();
208        Some(Self {
209            timestamp: self.timestamp.checked_sub_seconds(rhs.as_seconds())?,
210        })
211    }
212
213    /// [op:subtract-yearMonthDuration-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDuration-from-dateTime) and [op:subtract-dayTimeDuration-from-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDuration-from-dateTime)
214    ///
215    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
216    #[inline]
217    #[must_use]
218    pub fn checked_sub_duration(self, rhs: impl Into<Duration>) -> Option<Self> {
219        let rhs = rhs.into();
220        if let Ok(rhs) = DayTimeDuration::try_from(rhs) {
221            self.checked_sub_day_time_duration(rhs)
222        } else {
223            Some(Self {
224                timestamp: Timestamp::new(&date_time_plus_duration(
225                    rhs.checked_neg()?,
226                    &self.properties(),
227                )?)
228                .ok()?,
229            })
230        }
231    }
232
233    /// [fn:adjust-dateTime-to-timezone](https://www.w3.org/TR/xpath-functions-31/#func-adjust-dateTime-to-timezone)
234    ///
235    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
236    #[inline]
237    #[must_use]
238    pub fn adjust(self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
239        Some(Self {
240            timestamp: self.timestamp.adjust(timezone_offset)?,
241        })
242    }
243
244    /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity).
245    #[inline]
246    #[must_use]
247    pub fn is_identical_with(self, other: Self) -> bool {
248        self.timestamp.is_identical_with(other.timestamp)
249    }
250}
251
252/// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
253impl TryFrom<Date> for DateTime {
254    type Error = DateTimeOverflowError;
255
256    #[inline]
257    fn try_from(date: Date) -> Result<Self, Self::Error> {
258        Self::new(
259            date.year(),
260            date.month(),
261            date.day(),
262            0,
263            0,
264            Decimal::default(),
265            date.timezone_offset(),
266        )
267    }
268}
269
270impl FromStr for DateTime {
271    type Err = ParseDateTimeError;
272
273    fn from_str(input: &str) -> Result<Self, Self::Err> {
274        ensure_complete(input, date_time_lexical_rep)
275    }
276}
277
278impl fmt::Display for DateTime {
279    #[inline]
280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281        let year = self.year();
282        if year < 0 {
283            f.write_str("-")?;
284        }
285        let second = self.second();
286        write!(
287            f,
288            "{:04}-{:02}-{:02}T{:02}:{:02}:{}{}",
289            year.abs(),
290            self.month(),
291            self.day(),
292            self.hour(),
293            self.minute(),
294            if Decimal::from(-10) < second && second < Decimal::from(10) {
295                "0"
296            } else {
297                ""
298            },
299            second
300        )?;
301        if let Some(timezone_offset) = self.timezone_offset() {
302            write!(f, "{timezone_offset}")?;
303        }
304        Ok(())
305    }
306}
307
308/// [XML Schema `time` datatype](https://www.w3.org/TR/xmlschema11-2/#time)
309///
310/// It encodes the value using a number of seconds from the Gregorian calendar era using a [`Decimal`],
311/// when combined with the date 1972-12-31, and an optional timezone offset in minutes.
312#[derive(Eq, PartialEq, PartialOrd, Debug, Clone, Copy, Hash)]
313pub struct Time {
314    timestamp: Timestamp,
315}
316
317impl Time {
318    #[cfg(test)]
319    const MAX: Self = Self {
320        timestamp: Timestamp {
321            value: Decimal::new_from_i128_unchecked(62_230_255_200),
322            timezone_offset: Some(TimezoneOffset::MIN),
323        },
324    };
325    #[cfg(test)]
326    const MIN: Self = Self {
327        timestamp: Timestamp {
328            value: Decimal::new_from_i128_unchecked(62_230_154_400),
329            timezone_offset: Some(TimezoneOffset::MAX),
330        },
331    };
332
333    #[inline]
334    fn new(
335        mut hour: u8,
336        minute: u8,
337        second: Decimal,
338        timezone_offset: Option<TimezoneOffset>,
339    ) -> Result<Self, DateTimeOverflowError> {
340        if hour == 24 && minute == 0 && second == Decimal::default() {
341            hour = 0;
342        }
343        Ok(Self {
344            timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
345                year: None,
346                month: None,
347                day: None,
348                hour: Some(hour),
349                minute: Some(minute),
350                second: Some(second),
351                timezone_offset,
352            })?,
353        })
354    }
355
356    #[inline]
357    #[must_use]
358    pub fn from_be_bytes(bytes: [u8; 18]) -> Self {
359        Self {
360            timestamp: Timestamp::from_be_bytes(bytes),
361        }
362    }
363
364    /// [fn:current-time](https://www.w3.org/TR/xpath-functions-31/#func-current-time)
365    #[inline]
366    pub fn now() -> Self {
367        Self {
368            timestamp: Timestamp::now(),
369        }
370    }
371
372    /// [fn:hour-from-time](https://www.w3.org/TR/xpath-functions-31/#func-hours-from-time)
373    #[inline]
374    #[must_use]
375    pub fn hour(self) -> u8 {
376        self.timestamp.hour()
377    }
378
379    /// [fn:minute-from-time](https://www.w3.org/TR/xpath-functions-31/#func-minutes-from-time)
380    #[inline]
381    #[must_use]
382    pub fn minute(self) -> u8 {
383        self.timestamp.minute()
384    }
385
386    /// [fn:second-from-time](https://www.w3.org/TR/xpath-functions-31/#func-seconds-from-time)
387    #[inline]
388    #[must_use]
389    pub fn second(self) -> Decimal {
390        self.timestamp.second()
391    }
392
393    /// [fn:timezone-from-time](https://www.w3.org/TR/xpath-functions-31/#func-timezone-from-time)
394    #[inline]
395    #[must_use]
396    pub fn timezone(self) -> Option<DayTimeDuration> {
397        Some(self.timezone_offset()?.into())
398    }
399
400    #[inline]
401    #[must_use]
402    pub fn timezone_offset(self) -> Option<TimezoneOffset> {
403        self.timestamp.timezone_offset()
404    }
405
406    #[inline]
407    #[must_use]
408    pub fn to_be_bytes(self) -> [u8; 18] {
409        self.timestamp.to_be_bytes()
410    }
411
412    /// [op:subtract-times](https://www.w3.org/TR/xpath-functions-31/#func-subtract-times)
413    ///
414    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
415    #[inline]
416    #[must_use]
417    pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<DayTimeDuration> {
418        self.timestamp.checked_sub(rhs.into().timestamp)
419    }
420
421    /// [op:add-dayTimeDuration-to-time](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-time)
422    ///
423    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
424    #[inline]
425    #[must_use]
426    pub fn checked_add_day_time_duration(self, rhs: impl Into<DayTimeDuration>) -> Option<Self> {
427        self.checked_add_duration(Duration::from(rhs.into()))
428    }
429
430    /// [op:add-dayTimeDuration-to-time](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-time)
431    ///
432    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
433    #[inline]
434    #[must_use]
435    pub fn checked_add_duration(self, rhs: impl Into<Duration>) -> Option<Self> {
436        Some(
437            DateTime::new(
438                1972,
439                12,
440                31,
441                self.hour(),
442                self.minute(),
443                self.second(),
444                self.timezone_offset(),
445            )
446            .ok()?
447            .checked_add_duration(rhs)?
448            .into(),
449        )
450    }
451
452    /// [op:subtract-dayTimeDuration-from-time](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDuration-from-time)
453    ///
454    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
455    #[inline]
456    #[must_use]
457    pub fn checked_sub_day_time_duration(self, rhs: impl Into<DayTimeDuration>) -> Option<Self> {
458        self.checked_sub_duration(Duration::from(rhs.into()))
459    }
460
461    /// [op:subtract-dayTimeDuration-from-time](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDuration-from-time)
462    ///
463    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
464    #[inline]
465    #[must_use]
466    pub fn checked_sub_duration(self, rhs: impl Into<Duration>) -> Option<Self> {
467        Some(
468            DateTime::new(
469                1972,
470                12,
471                31,
472                self.hour(),
473                self.minute(),
474                self.second(),
475                self.timezone_offset(),
476            )
477            .ok()?
478            .checked_sub_duration(rhs)?
479            .into(),
480        )
481    }
482
483    // [fn:adjust-time-to-timezone](https://www.w3.org/TR/xpath-functions-31/#func-adjust-time-to-timezone)
484    #[inline]
485    #[must_use]
486    pub fn adjust(self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
487        Some(
488            DateTime::new(
489                1972,
490                12,
491                31,
492                self.hour(),
493                self.minute(),
494                self.second(),
495                self.timezone_offset(),
496            )
497            .ok()?
498            .adjust(timezone_offset)?
499            .into(),
500        )
501    }
502
503    /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity).
504    #[inline]
505    #[must_use]
506    pub fn is_identical_with(self, other: Self) -> bool {
507        self.timestamp.is_identical_with(other.timestamp)
508    }
509}
510
511/// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
512impl From<DateTime> for Time {
513    #[inline]
514    fn from(date_time: DateTime) -> Self {
515        Self::new(
516            date_time.hour(),
517            date_time.minute(),
518            date_time.second(),
519            date_time.timezone_offset(),
520        )
521        .expect("Casting from xsd:dateTime to xsd:date can't fail")
522    }
523}
524
525impl FromStr for Time {
526    type Err = ParseDateTimeError;
527
528    fn from_str(input: &str) -> Result<Self, Self::Err> {
529        ensure_complete(input, time_lexical_rep)
530    }
531}
532
533impl fmt::Display for Time {
534    #[inline]
535    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536        let second = self.second();
537        write!(
538            f,
539            "{:02}:{:02}:{}{}",
540            self.hour(),
541            self.minute(),
542            if Decimal::from(-10) < second && second < Decimal::from(10) {
543                "0"
544            } else {
545                ""
546            },
547            second
548        )?;
549        if let Some(timezone_offset) = self.timezone_offset() {
550            write!(f, "{timezone_offset}")?;
551        }
552        Ok(())
553    }
554}
555
556/// [XML Schema `date` datatype](https://www.w3.org/TR/xmlschema11-2/#date)
557///
558/// It encodes the value using a number of seconds from the Gregorian calendar era using a [`Decimal`],
559/// when combined with the time 00:00:00, and an optional timezone offset in minutes.
560#[derive(Eq, PartialEq, PartialOrd, Debug, Clone, Copy, Hash)]
561pub struct Date {
562    timestamp: Timestamp,
563}
564
565impl Date {
566    pub const MAX: Self = Self {
567        timestamp: Timestamp {
568            value: Decimal::new_from_i128_unchecked(170_141_183_460_469_216_800),
569            timezone_offset: Some(TimezoneOffset::MAX),
570        },
571    };
572    pub const MIN: Self = Self {
573        timestamp: Timestamp {
574            value: Decimal::new_from_i128_unchecked(-170_141_183_460_469_216_800),
575            timezone_offset: Some(TimezoneOffset::MIN),
576        },
577    };
578
579    #[inline]
580    fn new(
581        year: i64,
582        month: u8,
583        day: u8,
584        timezone_offset: Option<TimezoneOffset>,
585    ) -> Result<Self, DateTimeOverflowError> {
586        Ok(Self {
587            timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
588                year: Some(year),
589                month: Some(month),
590                day: Some(day),
591                hour: None,
592                minute: None,
593                second: None,
594                timezone_offset,
595            })?,
596        })
597    }
598
599    #[inline]
600    #[must_use]
601    pub fn from_be_bytes(bytes: [u8; 18]) -> Self {
602        Self {
603            timestamp: Timestamp::from_be_bytes(bytes),
604        }
605    }
606
607    /// [fn:current-date](https://www.w3.org/TR/xpath-functions-31/#func-current-date)
608    #[inline]
609    pub fn now() -> Self {
610        DateTime::now()
611            .try_into()
612            .expect("The current time seems way in the future, it's strange")
613    }
614
615    /// [fn:year-from-date](https://www.w3.org/TR/xpath-functions-31/#func-year-from-date)
616    #[inline]
617    #[must_use]
618    pub fn year(self) -> i64 {
619        self.timestamp.year()
620    }
621
622    /// [fn:month-from-date](https://www.w3.org/TR/xpath-functions-31/#func-month-from-date)
623    #[inline]
624    #[must_use]
625    pub fn month(self) -> u8 {
626        self.timestamp.month()
627    }
628
629    /// [fn:day-from-date](https://www.w3.org/TR/xpath-functions-31/#func-day-from-date)
630    #[inline]
631    #[must_use]
632    pub fn day(self) -> u8 {
633        self.timestamp.day()
634    }
635
636    /// [fn:timezone-from-date](https://www.w3.org/TR/xpath-functions-31/#func-timezone-from-date)
637    #[inline]
638    #[must_use]
639    pub fn timezone(self) -> Option<DayTimeDuration> {
640        Some(self.timezone_offset()?.into())
641    }
642
643    #[inline]
644    #[must_use]
645    pub fn timezone_offset(self) -> Option<TimezoneOffset> {
646        self.timestamp.timezone_offset()
647    }
648
649    #[inline]
650    #[must_use]
651    pub fn to_be_bytes(self) -> [u8; 18] {
652        self.timestamp.to_be_bytes()
653    }
654
655    /// [op:subtract-dates](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dates)
656    ///
657    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
658    #[inline]
659    #[must_use]
660    pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<DayTimeDuration> {
661        self.timestamp.checked_sub(rhs.into().timestamp)
662    }
663
664    /// [op:add-yearMonthDuration-to-date](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDuration-to-date)
665    ///
666    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
667    #[inline]
668    #[must_use]
669    pub fn checked_add_year_month_duration(
670        self,
671        rhs: impl Into<YearMonthDuration>,
672    ) -> Option<Self> {
673        self.checked_add_duration(Duration::from(rhs.into()))
674    }
675
676    /// [op:add-dayTimeDuration-to-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-date)
677    ///
678    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
679    #[inline]
680    #[must_use]
681    pub fn checked_add_day_time_duration(self, rhs: impl Into<DayTimeDuration>) -> Option<Self> {
682        self.checked_add_duration(Duration::from(rhs.into()))
683    }
684
685    /// [op:add-yearMonthDuration-to-date](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDuration-to-date) and [op:add-dayTimeDuration-to-dateTime](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDuration-to-date)
686    ///
687    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
688    #[inline]
689    #[must_use]
690    pub fn checked_add_duration(self, rhs: impl Into<Duration>) -> Option<Self> {
691        DateTime::try_from(self)
692            .ok()?
693            .checked_add_duration(rhs)?
694            .try_into()
695            .ok()
696    }
697
698    /// [op:subtract-yearMonthDuration-from-date](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDuration-from-date)
699    ///
700    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
701    #[inline]
702    #[must_use]
703    pub fn checked_sub_year_month_duration(
704        self,
705        rhs: impl Into<YearMonthDuration>,
706    ) -> Option<Self> {
707        self.checked_sub_duration(Duration::from(rhs.into()))
708    }
709
710    /// [op:subtract-dayTimeDuration-from-date](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDuration-from-date)
711    ///
712    /// Returns `None` in case of overflow ([`FODT0001`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001)).
713    #[inline]
714    #[must_use]
715    pub fn checked_sub_day_time_duration(self, rhs: impl Into<DayTimeDuration>) -> Option<Self> {
716        self.checked_sub_duration(Duration::from(rhs.into()))
717    }
718
719    /// [op:subtract-yearMonthDuration-from-date](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDuration-from-date) and [op:subtract-dayTimeDuration-from-date](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDuration-from-date)
720    #[inline]
721    #[must_use]
722    pub fn checked_sub_duration(self, rhs: impl Into<Duration>) -> Option<Self> {
723        DateTime::try_from(self)
724            .ok()?
725            .checked_sub_duration(rhs)?
726            .try_into()
727            .ok()
728    }
729
730    // [fn:adjust-date-to-timezone](https://www.w3.org/TR/xpath-functions-31/#func-adjust-date-to-timezone)
731    #[inline]
732    #[must_use]
733    pub fn adjust(self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
734        DateTime::new(
735            self.year(),
736            self.month(),
737            self.day(),
738            0,
739            0,
740            Decimal::default(),
741            self.timezone_offset(),
742        )
743        .ok()?
744        .adjust(timezone_offset)?
745        .try_into()
746        .ok()
747    }
748
749    /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity).
750    #[inline]
751    #[must_use]
752    pub fn is_identical_with(self, other: Self) -> bool {
753        self.timestamp.is_identical_with(other.timestamp)
754    }
755}
756
757/// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
758impl TryFrom<DateTime> for Date {
759    type Error = DateTimeOverflowError;
760
761    #[inline]
762    fn try_from(date_time: DateTime) -> Result<Self, Self::Error> {
763        Self::new(
764            date_time.year(),
765            date_time.month(),
766            date_time.day(),
767            date_time.timezone_offset(),
768        )
769    }
770}
771
772impl FromStr for Date {
773    type Err = ParseDateTimeError;
774
775    fn from_str(input: &str) -> Result<Self, Self::Err> {
776        ensure_complete(input, date_lexical_rep)
777    }
778}
779
780impl fmt::Display for Date {
781    #[inline]
782    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
783        let year = self.year();
784        if year < 0 {
785            f.write_str("-")?;
786        }
787        write!(f, "{:04}-{:02}-{:02}", year.abs(), self.month(), self.day())?;
788        if let Some(timezone_offset) = self.timezone_offset() {
789            write!(f, "{timezone_offset}")?;
790        }
791        Ok(())
792    }
793}
794
795/// [XML Schema `gYearMonth` datatype](https://www.w3.org/TR/xmlschema11-2/#gYearMonth)
796///
797/// It encodes the value using a number of seconds from the Gregorian calendar era using a [`Decimal`],
798/// when combined with the day-time 31T00:00:00, and an optional timezone offset in minutes.
799#[derive(Eq, PartialEq, PartialOrd, Debug, Clone, Copy, Hash)]
800pub struct GYearMonth {
801    timestamp: Timestamp,
802}
803
804impl GYearMonth {
805    pub const MAX: Self = Self {
806        timestamp: Timestamp {
807            value: Decimal::new_from_i128_unchecked(170_141_183_460_469_216_800),
808            timezone_offset: Some(TimezoneOffset::MAX),
809        },
810    };
811    pub const MIN: Self = Self {
812        timestamp: Timestamp {
813            value: Decimal::new_from_i128_unchecked(-170_141_183_460_466_970_400),
814            timezone_offset: Some(TimezoneOffset::MIN),
815        },
816    };
817
818    #[inline]
819    fn new(
820        year: i64,
821        month: u8,
822        timezone_offset: Option<TimezoneOffset>,
823    ) -> Result<Self, DateTimeOverflowError> {
824        Ok(Self {
825            timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
826                year: Some(year),
827                month: Some(month),
828                day: None,
829                hour: None,
830                minute: None,
831                second: None,
832                timezone_offset,
833            })?,
834        })
835    }
836
837    #[inline]
838    #[must_use]
839    pub fn from_be_bytes(bytes: [u8; 18]) -> Self {
840        Self {
841            timestamp: Timestamp::from_be_bytes(bytes),
842        }
843    }
844
845    #[inline]
846    #[must_use]
847    pub fn year(self) -> i64 {
848        self.timestamp.year()
849    }
850
851    #[inline]
852    #[must_use]
853    pub fn month(self) -> u8 {
854        self.timestamp.month()
855    }
856
857    #[inline]
858    #[must_use]
859    pub fn timezone(self) -> Option<DayTimeDuration> {
860        Some(self.timezone_offset()?.into())
861    }
862
863    #[inline]
864    #[must_use]
865    pub fn timezone_offset(self) -> Option<TimezoneOffset> {
866        self.timestamp.timezone_offset()
867    }
868
869    #[inline]
870    #[must_use]
871    pub fn adjust(self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
872        Some(Self {
873            timestamp: self.timestamp.adjust(timezone_offset)?,
874        })
875    }
876
877    #[inline]
878    #[must_use]
879    pub fn to_be_bytes(self) -> [u8; 18] {
880        self.timestamp.to_be_bytes()
881    }
882
883    /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity).
884    #[inline]
885    #[must_use]
886    pub fn is_identical_with(self, other: Self) -> bool {
887        self.timestamp.is_identical_with(other.timestamp)
888    }
889}
890
891/// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
892impl TryFrom<DateTime> for GYearMonth {
893    type Error = DateTimeOverflowError;
894
895    #[inline]
896    fn try_from(date_time: DateTime) -> Result<Self, Self::Error> {
897        Self::new(
898            date_time.year(),
899            date_time.month(),
900            date_time.timezone_offset(),
901        )
902    }
903}
904
905/// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
906impl From<Date> for GYearMonth {
907    #[inline]
908    fn from(date: Date) -> Self {
909        Self::new(date.year(), date.month(), date.timezone_offset())
910            .expect("Casting from xsd:date to xsd:gYearMonth can't fail")
911    }
912}
913
914impl FromStr for GYearMonth {
915    type Err = ParseDateTimeError;
916
917    fn from_str(input: &str) -> Result<Self, Self::Err> {
918        ensure_complete(input, g_year_month_lexical_rep)
919    }
920}
921
922impl fmt::Display for GYearMonth {
923    #[inline]
924    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
925        let year = self.year();
926        if year < 0 {
927            f.write_str("-")?;
928        }
929        write!(f, "{:04}-{:02}", year.abs(), self.month())?;
930        if let Some(timezone_offset) = self.timezone_offset() {
931            write!(f, "{timezone_offset}")?;
932        }
933        Ok(())
934    }
935}
936
937/// [XML Schema `gYear` datatype](https://www.w3.org/TR/xmlschema11-2/#gYear)
938///
939/// It encodes the value using a number of seconds from the Gregorian calendar era using a [`Decimal`],
940/// when combined with the month-day-time 12-31T00:00:00, and an optional timezone offset in minutes.
941#[derive(Eq, PartialEq, PartialOrd, Debug, Clone, Copy, Hash)]
942pub struct GYear {
943    timestamp: Timestamp,
944}
945
946impl GYear {
947    pub const MAX: Self = Self {
948        timestamp: Timestamp {
949            value: Decimal::new_from_i128_unchecked(170_141_183_460_461_440_800),
950            timezone_offset: Some(TimezoneOffset::MAX),
951        },
952    };
953    pub const MIN: Self = Self {
954        timestamp: Timestamp {
955            value: Decimal::new_from_i128_unchecked(-170_141_183_460_461_700_000),
956            timezone_offset: Some(TimezoneOffset::MIN),
957        },
958    };
959
960    #[inline]
961    fn new(
962        year: i64,
963        timezone_offset: Option<TimezoneOffset>,
964    ) -> Result<Self, DateTimeOverflowError> {
965        Ok(Self {
966            timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
967                year: Some(year),
968                month: None,
969                day: None,
970                hour: None,
971                minute: None,
972                second: None,
973                timezone_offset,
974            })?,
975        })
976    }
977
978    #[inline]
979    #[must_use]
980    pub fn from_be_bytes(bytes: [u8; 18]) -> Self {
981        Self {
982            timestamp: Timestamp::from_be_bytes(bytes),
983        }
984    }
985
986    #[inline]
987    #[must_use]
988    pub fn year(self) -> i64 {
989        self.timestamp.year()
990    }
991
992    #[inline]
993    #[must_use]
994    pub fn timezone(self) -> Option<DayTimeDuration> {
995        Some(self.timezone_offset()?.into())
996    }
997
998    #[inline]
999    #[must_use]
1000    pub fn timezone_offset(self) -> Option<TimezoneOffset> {
1001        self.timestamp.timezone_offset()
1002    }
1003
1004    #[inline]
1005    #[must_use]
1006    pub fn adjust(self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
1007        Some(Self {
1008            timestamp: self.timestamp.adjust(timezone_offset)?,
1009        })
1010    }
1011
1012    #[inline]
1013    #[must_use]
1014    pub fn to_be_bytes(self) -> [u8; 18] {
1015        self.timestamp.to_be_bytes()
1016    }
1017
1018    /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity).
1019    #[inline]
1020    #[must_use]
1021    pub fn is_identical_with(self, other: Self) -> bool {
1022        self.timestamp.is_identical_with(other.timestamp)
1023    }
1024}
1025
1026/// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
1027impl TryFrom<DateTime> for GYear {
1028    type Error = DateTimeOverflowError;
1029
1030    #[inline]
1031    fn try_from(date_time: DateTime) -> Result<Self, Self::Error> {
1032        Self::new(date_time.year(), date_time.timezone_offset())
1033    }
1034}
1035
1036/// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
1037impl TryFrom<Date> for GYear {
1038    type Error = DateTimeOverflowError;
1039
1040    #[inline]
1041    fn try_from(date: Date) -> Result<Self, Self::Error> {
1042        Self::new(date.year(), date.timezone_offset())
1043    }
1044}
1045
1046impl TryFrom<GYearMonth> for GYear {
1047    type Error = DateTimeOverflowError;
1048
1049    #[inline]
1050    fn try_from(year_month: GYearMonth) -> Result<Self, Self::Error> {
1051        Self::new(year_month.year(), year_month.timezone_offset())
1052    }
1053}
1054
1055impl FromStr for GYear {
1056    type Err = ParseDateTimeError;
1057
1058    fn from_str(input: &str) -> Result<Self, Self::Err> {
1059        ensure_complete(input, g_year_lexical_rep)
1060    }
1061}
1062
1063impl fmt::Display for GYear {
1064    #[inline]
1065    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1066        let year = self.year();
1067        if year < 0 {
1068            f.write_str("-")?;
1069        }
1070        write!(f, "{:04}", year.abs())?;
1071        if let Some(timezone_offset) = self.timezone_offset() {
1072            write!(f, "{timezone_offset}")?;
1073        }
1074        Ok(())
1075    }
1076}
1077
1078/// [XML Schema `gMonthDay` datatype](https://www.w3.org/TR/xmlschema11-2/#gMonthDay)
1079///
1080/// It encodes the value using a number of seconds from the Gregorian calendar era using a [`Decimal`],
1081/// when combined with the year 1972 and the time 31T00:00:00, and an optional timezone offset in minutes.
1082#[derive(Eq, PartialEq, PartialOrd, Debug, Clone, Copy, Hash)]
1083pub struct GMonthDay {
1084    timestamp: Timestamp,
1085}
1086
1087impl GMonthDay {
1088    #[inline]
1089    fn new(
1090        month: u8,
1091        day: u8,
1092        timezone_offset: Option<TimezoneOffset>,
1093    ) -> Result<Self, DateTimeOverflowError> {
1094        Ok(Self {
1095            timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
1096                year: None,
1097                month: Some(month),
1098                day: Some(day),
1099                hour: None,
1100                minute: None,
1101                second: None,
1102                timezone_offset,
1103            })?,
1104        })
1105    }
1106
1107    #[inline]
1108    #[must_use]
1109    pub fn from_be_bytes(bytes: [u8; 18]) -> Self {
1110        Self {
1111            timestamp: Timestamp::from_be_bytes(bytes),
1112        }
1113    }
1114
1115    #[inline]
1116    #[must_use]
1117    pub fn month(&self) -> u8 {
1118        self.timestamp.month()
1119    }
1120
1121    #[inline]
1122    #[must_use]
1123    pub fn day(&self) -> u8 {
1124        self.timestamp.day()
1125    }
1126
1127    #[inline]
1128    #[must_use]
1129    pub fn timezone(&self) -> Option<DayTimeDuration> {
1130        Some(self.timezone_offset()?.into())
1131    }
1132
1133    #[inline]
1134    #[must_use]
1135    pub fn timezone_offset(&self) -> Option<TimezoneOffset> {
1136        self.timestamp.timezone_offset()
1137    }
1138
1139    #[inline]
1140    #[must_use]
1141    pub fn adjust(&self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
1142        Some(Self {
1143            timestamp: self.timestamp.adjust(timezone_offset)?,
1144        })
1145    }
1146
1147    #[inline]
1148    #[must_use]
1149    pub fn to_be_bytes(self) -> [u8; 18] {
1150        self.timestamp.to_be_bytes()
1151    }
1152
1153    /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity).
1154    #[inline]
1155    #[must_use]
1156    pub fn is_identical_with(self, other: Self) -> bool {
1157        self.timestamp.is_identical_with(other.timestamp)
1158    }
1159}
1160
1161/// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
1162impl From<DateTime> for GMonthDay {
1163    #[inline]
1164    fn from(date_time: DateTime) -> Self {
1165        Self::new(
1166            date_time.month(),
1167            date_time.day(),
1168            date_time.timezone_offset(),
1169        )
1170        .expect("Casting from xsd:dateTime to xsd:gMonthDay can't fail")
1171    }
1172}
1173
1174/// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
1175impl From<Date> for GMonthDay {
1176    #[inline]
1177    fn from(date: Date) -> Self {
1178        Self::new(date.month(), date.day(), date.timezone_offset())
1179            .expect("Casting from xsd:date to xsd:gMonthDay can't fail")
1180    }
1181}
1182
1183impl FromStr for GMonthDay {
1184    type Err = ParseDateTimeError;
1185
1186    fn from_str(input: &str) -> Result<Self, Self::Err> {
1187        ensure_complete(input, g_month_day_lexical_rep)
1188    }
1189}
1190
1191impl fmt::Display for GMonthDay {
1192    #[inline]
1193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1194        write!(f, "--{:02}-{:02}", self.month(), self.day())?;
1195        if let Some(timezone_offset) = self.timezone_offset() {
1196            write!(f, "{timezone_offset}")?;
1197        }
1198        Ok(())
1199    }
1200}
1201
1202/// [XML Schema `gMonth` datatype](https://www.w3.org/TR/xmlschema11-2/#gMonth)
1203///
1204/// It encodes the value using a number of seconds from the Gregorian calendar era using a [`Decimal`],
1205/// when combined with the year 1972 and the day-time 31T00:00:00, and an optional timezone offset in minutes.
1206#[derive(Eq, PartialEq, PartialOrd, Debug, Clone, Copy, Hash)]
1207pub struct GMonth {
1208    timestamp: Timestamp,
1209}
1210
1211impl GMonth {
1212    #[inline]
1213    fn new(
1214        month: u8,
1215        timezone_offset: Option<TimezoneOffset>,
1216    ) -> Result<Self, DateTimeOverflowError> {
1217        Ok(Self {
1218            timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
1219                year: None,
1220                month: Some(month),
1221                day: None,
1222                hour: None,
1223                minute: None,
1224                second: None,
1225                timezone_offset,
1226            })?,
1227        })
1228    }
1229
1230    #[inline]
1231    #[must_use]
1232    pub fn from_be_bytes(bytes: [u8; 18]) -> Self {
1233        Self {
1234            timestamp: Timestamp::from_be_bytes(bytes),
1235        }
1236    }
1237
1238    #[inline]
1239    #[must_use]
1240    pub fn month(&self) -> u8 {
1241        self.timestamp.month()
1242    }
1243
1244    #[inline]
1245    #[must_use]
1246    pub fn timezone(&self) -> Option<DayTimeDuration> {
1247        Some(self.timezone_offset()?.into())
1248    }
1249
1250    #[inline]
1251    #[must_use]
1252    pub fn timezone_offset(&self) -> Option<TimezoneOffset> {
1253        self.timestamp.timezone_offset()
1254    }
1255
1256    #[inline]
1257    #[must_use]
1258    pub fn adjust(&self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
1259        Some(Self {
1260            timestamp: self.timestamp.adjust(timezone_offset)?,
1261        })
1262    }
1263
1264    #[inline]
1265    #[must_use]
1266    pub fn to_be_bytes(self) -> [u8; 18] {
1267        self.timestamp.to_be_bytes()
1268    }
1269
1270    /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity).
1271    #[inline]
1272    #[must_use]
1273    pub fn is_identical_with(self, other: Self) -> bool {
1274        self.timestamp.is_identical_with(other.timestamp)
1275    }
1276}
1277
1278/// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
1279impl From<DateTime> for GMonth {
1280    #[inline]
1281    fn from(date_time: DateTime) -> Self {
1282        Self::new(date_time.month(), date_time.timezone_offset())
1283            .expect("Casting from xsd:dateTime to xsd:gMonth can't fail")
1284    }
1285}
1286
1287/// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
1288impl From<Date> for GMonth {
1289    #[inline]
1290    fn from(date: Date) -> Self {
1291        Self::new(date.month(), date.timezone_offset())
1292            .expect("Casting from xsd:date to xsd:gMonth can't fail")
1293    }
1294}
1295
1296impl From<GYearMonth> for GMonth {
1297    #[inline]
1298    fn from(year_month: GYearMonth) -> Self {
1299        Self::new(year_month.month(), year_month.timezone_offset())
1300            .expect("Casting from xsd:gYearMonth to xsd:gMonth can't fail")
1301    }
1302}
1303
1304impl From<GMonthDay> for GMonth {
1305    #[inline]
1306    fn from(month_day: GMonthDay) -> Self {
1307        Self::new(month_day.month(), month_day.timezone_offset())
1308            .expect("Casting from xsd:gMonthDay to xsd:gMonth can't fail")
1309    }
1310}
1311
1312impl FromStr for GMonth {
1313    type Err = ParseDateTimeError;
1314
1315    fn from_str(input: &str) -> Result<Self, Self::Err> {
1316        ensure_complete(input, g_month_lexical_rep)
1317    }
1318}
1319
1320impl fmt::Display for GMonth {
1321    #[inline]
1322    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1323        write!(f, "--{:02}", self.month())?;
1324        if let Some(timezone_offset) = self.timezone_offset() {
1325            write!(f, "{timezone_offset}")?;
1326        }
1327        Ok(())
1328    }
1329}
1330
1331/// [XML Schema `date` datatype](https://www.w3.org/TR/xmlschema11-2/#date)
1332///
1333/// It encodes the value using a number of seconds from the Gregorian calendar era using a [`Decimal`],
1334/// when combined with the year-month 1972-12 and the 00:00:00, and an optional timezone offset in minutes.
1335#[derive(Eq, PartialEq, PartialOrd, Debug, Clone, Copy, Hash)]
1336pub struct GDay {
1337    timestamp: Timestamp,
1338}
1339
1340impl GDay {
1341    #[inline]
1342    fn new(
1343        day: u8,
1344        timezone_offset: Option<TimezoneOffset>,
1345    ) -> Result<Self, DateTimeOverflowError> {
1346        Ok(Self {
1347            timestamp: Timestamp::new(&DateTimeSevenPropertyModel {
1348                year: None,
1349                month: None,
1350                day: Some(day),
1351                hour: None,
1352                minute: None,
1353                second: None,
1354                timezone_offset,
1355            })?,
1356        })
1357    }
1358
1359    #[inline]
1360    #[must_use]
1361    pub fn from_be_bytes(bytes: [u8; 18]) -> Self {
1362        Self {
1363            timestamp: Timestamp::from_be_bytes(bytes),
1364        }
1365    }
1366
1367    #[inline]
1368    #[must_use]
1369    pub fn day(&self) -> u8 {
1370        self.timestamp.day()
1371    }
1372
1373    #[inline]
1374    #[must_use]
1375    pub fn timezone(&self) -> Option<DayTimeDuration> {
1376        Some(self.timezone_offset()?.into())
1377    }
1378
1379    #[inline]
1380    #[must_use]
1381    pub fn timezone_offset(&self) -> Option<TimezoneOffset> {
1382        self.timestamp.timezone_offset()
1383    }
1384
1385    #[inline]
1386    #[must_use]
1387    pub fn adjust(&self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
1388        Some(Self {
1389            timestamp: self.timestamp.adjust(timezone_offset)?,
1390        })
1391    }
1392
1393    #[inline]
1394    #[must_use]
1395    pub fn to_be_bytes(self) -> [u8; 18] {
1396        self.timestamp.to_be_bytes()
1397    }
1398
1399    /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity).
1400    #[inline]
1401    #[must_use]
1402    pub fn is_identical_with(self, other: Self) -> bool {
1403        self.timestamp.is_identical_with(other.timestamp)
1404    }
1405}
1406
1407/// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
1408impl From<DateTime> for GDay {
1409    #[inline]
1410    fn from(date_time: DateTime) -> Self {
1411        Self::new(date_time.day(), date_time.timezone_offset())
1412            .expect("Casting from xsd:dateTime to xsd:gDay can't fail")
1413    }
1414}
1415
1416/// Conversion according to [XPath cast rules](https://www.w3.org/TR/xpath-functions-31/#casting-to-datetimes).
1417impl From<Date> for GDay {
1418    #[inline]
1419    fn from(date: Date) -> Self {
1420        Self::new(date.day(), date.timezone_offset())
1421            .expect("Casting from xsd:date to xsd:gDay can't fail")
1422    }
1423}
1424
1425impl From<GMonthDay> for GDay {
1426    #[inline]
1427    fn from(month_day: GMonthDay) -> Self {
1428        Self::new(month_day.day(), month_day.timezone_offset())
1429            .expect("Casting from xsd:gMonthDay to xsd:gDay can't fail")
1430    }
1431}
1432
1433impl FromStr for GDay {
1434    type Err = ParseDateTimeError;
1435
1436    fn from_str(input: &str) -> Result<Self, Self::Err> {
1437        ensure_complete(input, g_day_lexical_rep)
1438    }
1439}
1440
1441impl fmt::Display for GDay {
1442    #[inline]
1443    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1444        write!(f, "---{:02}", self.day())?;
1445        if let Some(timezone_offset) = self.timezone_offset() {
1446            write!(f, "{timezone_offset}")?;
1447        }
1448        Ok(())
1449    }
1450}
1451
1452/// A timezone offset with respect to UTC.
1453///
1454/// It is encoded as a number of minutes between -PT14H and PT14H.
1455#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy, Hash)]
1456pub struct TimezoneOffset {
1457    offset: i16, // in minute with respect to UTC
1458}
1459
1460impl TimezoneOffset {
1461    pub const MAX: Self = Self { offset: 14 * 60 };
1462    pub const MIN: Self = Self { offset: -14 * 60 };
1463    pub const UTC: Self = Self { offset: 0 };
1464
1465    /// From offset in minute with respect to UTC
1466    #[inline]
1467    pub fn new(offset_in_minutes: i16) -> Result<Self, InvalidTimezoneError> {
1468        let value = Self {
1469            offset: offset_in_minutes,
1470        };
1471        if Self::MIN <= value && value <= Self::MAX {
1472            Ok(value)
1473        } else {
1474            Err(InvalidTimezoneError {
1475                offset_in_minutes: offset_in_minutes.into(),
1476            })
1477        }
1478    }
1479
1480    #[inline]
1481    #[must_use]
1482    pub fn from_be_bytes(bytes: [u8; 2]) -> Self {
1483        Self {
1484            offset: i16::from_be_bytes(bytes),
1485        }
1486    }
1487
1488    #[inline]
1489    #[must_use]
1490    pub fn to_be_bytes(self) -> [u8; 2] {
1491        self.offset.to_be_bytes()
1492    }
1493}
1494
1495impl TryFrom<DayTimeDuration> for TimezoneOffset {
1496    type Error = InvalidTimezoneError;
1497
1498    #[inline]
1499    fn try_from(value: DayTimeDuration) -> Result<Self, Self::Error> {
1500        let offset_in_minutes = value.minutes() + value.hours() * 60;
1501        let result = Self::new(
1502            offset_in_minutes
1503                .try_into()
1504                .map_err(|_| Self::Error { offset_in_minutes })?,
1505        )?;
1506        if DayTimeDuration::from(result) == value {
1507            Ok(result)
1508        } else {
1509            // The value is not an integral number of minutes or overflow problems
1510            Err(Self::Error { offset_in_minutes })
1511        }
1512    }
1513}
1514
1515impl TryFrom<Duration> for TimezoneOffset {
1516    type Error = InvalidTimezoneError;
1517
1518    #[inline]
1519    fn try_from(value: Duration) -> Result<Self, Self::Error> {
1520        DayTimeDuration::try_from(value)
1521            .map_err(|_| Self::Error {
1522                offset_in_minutes: 0,
1523            })?
1524            .try_into()
1525    }
1526}
1527
1528impl From<TimezoneOffset> for DayTimeDuration {
1529    #[inline]
1530    fn from(value: TimezoneOffset) -> Self {
1531        Self::new(i64::from(value.offset) * 60)
1532    }
1533}
1534
1535impl From<TimezoneOffset> for Duration {
1536    #[inline]
1537    fn from(value: TimezoneOffset) -> Self {
1538        DayTimeDuration::from(value).into()
1539    }
1540}
1541
1542impl fmt::Display for TimezoneOffset {
1543    #[inline]
1544    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1545        match self.offset {
1546            0 => f.write_str("Z"),
1547            offset if offset < 0 => write!(f, "-{:02}:{:02}", -offset / 60, -offset % 60),
1548            offset => write!(f, "+{:02}:{:02}", offset / 60, offset % 60),
1549        }
1550    }
1551}
1552
1553/// [The Date/time Seven-property model](https://www.w3.org/TR/xmlschema11-2/#dt-dt-7PropMod)
1554#[derive(Eq, PartialEq, Debug, Clone, Hash)]
1555struct DateTimeSevenPropertyModel {
1556    year: Option<i64>,
1557    month: Option<u8>,
1558    day: Option<u8>,
1559    hour: Option<u8>,
1560    minute: Option<u8>,
1561    second: Option<Decimal>,
1562    timezone_offset: Option<TimezoneOffset>,
1563}
1564
1565#[derive(Debug, Clone, Copy)]
1566struct Timestamp {
1567    value: Decimal,
1568    timezone_offset: Option<TimezoneOffset>,
1569}
1570
1571impl PartialEq for Timestamp {
1572    #[inline]
1573    fn eq(&self, other: &Self) -> bool {
1574        match (self.timezone_offset, other.timezone_offset) {
1575            (Some(_), Some(_)) | (None, None) => self.value.eq(&other.value),
1576            _ => false, // TODO: implicit timezone
1577        }
1578    }
1579}
1580
1581impl Eq for Timestamp {}
1582
1583impl PartialOrd for Timestamp {
1584    #[inline]
1585    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1586        match (self.timezone_offset, other.timezone_offset) {
1587            (Some(_), Some(_)) | (None, None) => self.value.partial_cmp(&other.value),
1588            (Some(_), None) => {
1589                let plus_result = self
1590                    .value
1591                    .partial_cmp(&(other.value.checked_add(14 * 3600)?));
1592                let minus_result = self
1593                    .value
1594                    .partial_cmp(&(other.value.checked_sub(14 * 3600)?));
1595                if plus_result == minus_result {
1596                    plus_result
1597                } else {
1598                    None
1599                }
1600            }
1601            (None, Some(_)) => {
1602                let plus_result = self.value.checked_add(14 * 3600)?.partial_cmp(&other.value);
1603                let minus_result = self.value.checked_sub(14 * 3600)?.partial_cmp(&other.value);
1604                if plus_result == minus_result {
1605                    plus_result
1606                } else {
1607                    None
1608                }
1609            }
1610        }
1611    }
1612}
1613
1614impl Hash for Timestamp {
1615    #[inline]
1616    fn hash<H: Hasher>(&self, state: &mut H) {
1617        self.value.hash(state)
1618    }
1619}
1620
1621impl Timestamp {
1622    pub const MAX: Self = Self {
1623        value: Decimal::MAX,
1624        timezone_offset: Some(TimezoneOffset::MAX),
1625    };
1626    pub const MIN: Self = Self {
1627        value: Decimal::MIN,
1628        timezone_offset: Some(TimezoneOffset::MIN),
1629    };
1630
1631    #[inline]
1632    fn new(props: &DateTimeSevenPropertyModel) -> Result<Self, DateTimeOverflowError> {
1633        Ok(Self {
1634            timezone_offset: props.timezone_offset,
1635            value: time_on_timeline(props).ok_or(DateTimeOverflowError)?,
1636        })
1637    }
1638
1639    #[inline]
1640    fn now() -> Self {
1641        Self::new(
1642            &date_time_plus_duration(
1643                since_unix_epoch(),
1644                &DateTimeSevenPropertyModel {
1645                    year: Some(1970),
1646                    month: Some(1),
1647                    day: Some(1),
1648                    hour: Some(0),
1649                    minute: Some(0),
1650                    second: Some(Decimal::default()),
1651                    timezone_offset: Some(TimezoneOffset::UTC),
1652                },
1653            )
1654            .expect("The current time seems way in the future, it's strange"),
1655        )
1656        .expect("The current time seems way in the future, it's strange")
1657    }
1658
1659    #[inline]
1660    fn from_be_bytes(bytes: [u8; 18]) -> Self {
1661        Self {
1662            value: Decimal::from_be_bytes(bytes[0..16].try_into().unwrap()),
1663            timezone_offset: if bytes[16..18] == [u8::MAX; 2] {
1664                None
1665            } else {
1666                Some(TimezoneOffset::from_be_bytes(
1667                    bytes[16..18].try_into().unwrap(),
1668                ))
1669            },
1670        }
1671    }
1672
1673    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1674    #[inline]
1675    #[must_use]
1676    fn year_month_day(&self) -> (i64, u8, u8) {
1677        let mut days = (self.value.as_i128()
1678            + i128::from(self.timezone_offset.unwrap_or(TimezoneOffset::UTC).offset) * 60)
1679            .div_euclid(86400)
1680            + 366;
1681
1682        // Make days positive
1683        let shift = if days < 0 {
1684            let shift = days / 146_097 - 1;
1685            days -= shift * 146_097;
1686            shift * 400
1687        } else {
1688            0
1689        };
1690
1691        let year_mul_400 = days / 146_097;
1692        days -= year_mul_400 * 146_097;
1693
1694        days -= 1;
1695        let year_mul_100 = days / 36524;
1696        days -= year_mul_100 * 36524;
1697
1698        days += 1;
1699        let year_mul_4 = days / 1461;
1700        days -= year_mul_4 * 1461;
1701
1702        days -= 1;
1703        let year_mod_4 = days / 365;
1704        days -= year_mod_4 * 365;
1705
1706        let year =
1707            (400 * year_mul_400 + 100 * year_mul_100 + 4 * year_mul_4 + year_mod_4 + shift) as i64;
1708
1709        let is_leap_year = (year_mul_100 == 0 || year_mul_4 != 0) && year_mod_4 == 0;
1710        days += i128::from(is_leap_year);
1711
1712        let mut month = 0;
1713        for month_i in 1..=12 {
1714            let days_in_month = i128::from(days_in_month(Some(year), month_i));
1715            if days_in_month > days {
1716                month = month_i;
1717                break;
1718            }
1719            days -= days_in_month
1720        }
1721        let day = days as u8 + 1;
1722
1723        (year, month, day)
1724    }
1725
1726    #[inline]
1727    #[must_use]
1728    fn year(&self) -> i64 {
1729        let (year, _, _) = self.year_month_day();
1730        year
1731    }
1732
1733    #[inline]
1734    #[must_use]
1735    fn month(&self) -> u8 {
1736        let (_, month, _) = self.year_month_day();
1737        month
1738    }
1739
1740    #[inline]
1741    #[must_use]
1742    fn day(&self) -> u8 {
1743        let (_, _, day) = self.year_month_day();
1744        day
1745    }
1746
1747    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1748    #[inline]
1749    #[must_use]
1750    fn hour(&self) -> u8 {
1751        (((self.value.as_i128()
1752            + i128::from(self.timezone_offset.unwrap_or(TimezoneOffset::UTC).offset) * 60)
1753            .rem_euclid(86400))
1754            / 3600) as u8
1755    }
1756
1757    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1758    #[inline]
1759    #[must_use]
1760    fn minute(&self) -> u8 {
1761        (((self.value.as_i128()
1762            + i128::from(self.timezone_offset.unwrap_or(TimezoneOffset::UTC).offset) * 60)
1763            .rem_euclid(3600))
1764            / 60) as u8
1765    }
1766
1767    #[inline]
1768    #[must_use]
1769    fn second(&self) -> Decimal {
1770        self.value
1771            .checked_rem_euclid(60)
1772            .unwrap()
1773            .checked_abs()
1774            .unwrap()
1775    }
1776
1777    #[inline]
1778    #[must_use]
1779    const fn timezone_offset(&self) -> Option<TimezoneOffset> {
1780        self.timezone_offset
1781    }
1782
1783    #[inline]
1784    #[must_use]
1785    fn checked_add_seconds(&self, seconds: impl Into<Decimal>) -> Option<Self> {
1786        Some(Self {
1787            value: self.value.checked_add(seconds.into())?,
1788            timezone_offset: self.timezone_offset,
1789        })
1790    }
1791
1792    #[inline]
1793    #[must_use]
1794    fn checked_sub(&self, rhs: Self) -> Option<DayTimeDuration> {
1795        match (self.timezone_offset, rhs.timezone_offset) {
1796            (Some(_), Some(_)) | (None, None) => {
1797                Some(DayTimeDuration::new(self.value.checked_sub(rhs.value)?))
1798            }
1799            _ => None, // TODO: implicit timezone
1800        }
1801    }
1802
1803    #[inline]
1804    #[must_use]
1805    fn checked_sub_seconds(&self, seconds: Decimal) -> Option<Self> {
1806        Some(Self {
1807            value: self.value.checked_sub(seconds)?,
1808            timezone_offset: self.timezone_offset,
1809        })
1810    }
1811
1812    #[inline]
1813    #[must_use]
1814    fn adjust(&self, timezone_offset: Option<TimezoneOffset>) -> Option<Self> {
1815        Some(if let Some(from_timezone) = self.timezone_offset {
1816            if let Some(to_timezone) = timezone_offset {
1817                Self {
1818                    value: self.value, // We keep the timestamp
1819                    timezone_offset: Some(to_timezone),
1820                }
1821            } else {
1822                Self {
1823                    value: self
1824                        .value
1825                        .checked_add(i64::from(from_timezone.offset) * 60)?, /* We keep the literal value */
1826                    timezone_offset: None,
1827                }
1828            }
1829        } else if let Some(to_timezone) = timezone_offset {
1830            Self {
1831                value: self.value.checked_sub(i64::from(to_timezone.offset) * 60)?, /* We keep the literal value */
1832                timezone_offset: Some(to_timezone),
1833            }
1834        } else {
1835            Self {
1836                value: self.value,
1837                timezone_offset: None,
1838            }
1839        })
1840    }
1841
1842    #[inline]
1843    #[must_use]
1844    fn to_be_bytes(self) -> [u8; 18] {
1845        let mut bytes = [0; 18];
1846        bytes[0..16].copy_from_slice(&self.value.to_be_bytes());
1847        bytes[16..18].copy_from_slice(&match &self.timezone_offset {
1848            Some(timezone_offset) => timezone_offset.to_be_bytes(),
1849            None => [u8::MAX; 2],
1850        });
1851        bytes
1852    }
1853
1854    /// Checks if the two values are [identical](https://www.w3.org/TR/xmlschema11-2/#identity).
1855    #[inline]
1856    #[must_use]
1857    pub fn is_identical_with(self, other: Self) -> bool {
1858        self.value == other.value && self.timezone_offset == other.timezone_offset
1859    }
1860}
1861
1862#[cfg(feature = "custom-now")]
1863#[allow(unsafe_code)]
1864pub fn since_unix_epoch() -> Duration {
1865    extern "Rust" {
1866        fn custom_ox_now() -> Duration;
1867    }
1868
1869    // SAFETY: Must be defined, if not compilation fails
1870    unsafe { custom_ox_now() }
1871}
1872
1873#[cfg(all(not(feature = "custom-now"), target_os = "zkvm"))]
1874fn since_unix_epoch() -> Duration {
1875    DayTimeDuration::new(0).into()
1876}
1877
1878#[cfg(all(
1879    feature = "js",
1880    not(feature = "custom-now"),
1881    target_family = "wasm",
1882    target_os = "unknown"
1883))]
1884fn since_unix_epoch() -> Duration {
1885    DayTimeDuration::new(
1886        Decimal::try_from(crate::Double::from(js_sys::Date::now() / 1000.))
1887            .expect("The current time seems way in the future, it's strange"),
1888    )
1889    .into()
1890}
1891
1892#[cfg(not(any(
1893    feature = "custom-now",
1894    target_os = "zkvm",
1895    all(feature = "js", target_family = "wasm", target_os = "unknown")
1896)))]
1897fn since_unix_epoch() -> Duration {
1898    use std::time::SystemTime;
1899
1900    SystemTime::now()
1901        .duration_since(SystemTime::UNIX_EPOCH)
1902        .expect("System time before UNIX epoch")
1903        .try_into()
1904        .expect("The current time seems way in the future, it's strange")
1905}
1906
1907/// The [normalizeMonth](https://www.w3.org/TR/xmlschema11-2/#f-dt-normMo) function
1908fn normalize_month(yr: i64, mo: i64) -> Option<(i64, u8)> {
1909    if mo >= 0 {
1910        let yr = yr.checked_add(mo.checked_sub(1)?.checked_div(12)?)?;
1911        let mo = u8::try_from(mo.checked_sub(1)?.checked_rem(12)?.abs().checked_add(1)?).ok()?;
1912        Some((yr, mo))
1913    } else {
1914        // Needed to make it work with negative durations
1915        let yr = yr.checked_add(mo.checked_sub(1)?.checked_div(12)?.checked_sub(1)?)?;
1916        let mo = u8::try_from(
1917            12_i64
1918                .checked_add(mo.checked_sub(1)?.checked_rem(12)?)?
1919                .checked_add(1)?,
1920        )
1921        .ok()?;
1922        Some((yr, mo))
1923    }
1924}
1925
1926/// The [normalizeDa](https://www.w3.org/TR/xmlschema11-2/#f-dt-normDa) function
1927fn normalize_day(yr: i64, mo: i64, mut da: i64) -> Option<(i64, u8, u8)> {
1928    let (mut yr, mut mo) = normalize_month(yr, mo)?;
1929    loop {
1930        if da <= 0 {
1931            let (yr2, mo2) = normalize_month(yr, i64::from(mo).checked_sub(1)?)?;
1932            yr = yr2;
1933            mo = mo2;
1934            da = da.checked_add(days_in_month(Some(yr), mo).into())?;
1935        } else if da > days_in_month(Some(yr), mo).into() {
1936            da = da.checked_sub(days_in_month(Some(yr), mo).into())?;
1937            let (yr2, mo2) = normalize_month(yr, i64::from(mo).checked_add(1)?)?;
1938            yr = yr2;
1939            mo = mo2;
1940        } else {
1941            return Some((yr, mo, u8::try_from(da).ok()?));
1942        };
1943    }
1944}
1945
1946/// The [normalizeMinute](https://www.w3.org/TR/xmlschema11-2/#f-dt-normMi) function
1947fn normalize_minute(yr: i64, mo: i64, da: i64, hr: i64, mi: i64) -> Option<(i64, u8, u8, u8, u8)> {
1948    let hr = hr.checked_add(mi.checked_div(60)?)?;
1949    let mi = mi.checked_rem(60)?;
1950    let da = da.checked_add(hr.checked_div(24)?)?;
1951    let hr = hr.checked_rem(24)?;
1952    let (yr, mo, da) = normalize_day(yr, mo, da)?;
1953    Some((yr, mo, da, u8::try_from(hr).ok()?, u8::try_from(mi).ok()?))
1954}
1955
1956/// The [normalizeSecond](https://www.w3.org/TR/xmlschema11-2/#f-dt-normSe) function
1957fn normalize_second(
1958    yr: i64,
1959    mo: i64,
1960    da: i64,
1961    hr: i64,
1962    mi: i64,
1963    se: Decimal,
1964) -> Option<(i64, u8, u8, u8, u8, Decimal)> {
1965    let mi = mi.checked_add(i64::try_from(se.as_i128().checked_div(60)?).ok()?)?; // TODO: good idea?
1966    let se = se.checked_rem(60)?;
1967    let (yr, mo, da, hr, mi) = normalize_minute(yr, mo, da, hr, mi)?;
1968    Some((yr, mo, da, hr, mi, se))
1969}
1970
1971/// The [daysInMonth](https://www.w3.org/TR/xmlschema11-2/#f-daysInMonth) function
1972fn days_in_month(y: Option<i64>, m: u8) -> u8 {
1973    match m {
1974        2 => {
1975            if let Some(y) = y {
1976                if y % 4 != 0 || (y % 100 == 0 && y % 400 != 0) {
1977                    28
1978                } else {
1979                    29
1980                }
1981            } else {
1982                28
1983            }
1984        }
1985        4 | 6 | 9 | 11 => 30,
1986        _ => 31,
1987    }
1988}
1989
1990/// The [dateTimePlusDuration](https://www.w3.org/TR/xmlschema11-2/#vp-dt-dateTimePlusDuration) function
1991fn date_time_plus_duration(
1992    du: Duration,
1993    dt: &DateTimeSevenPropertyModel,
1994) -> Option<DateTimeSevenPropertyModel> {
1995    let yr = dt.year.unwrap_or(1);
1996    let mo = dt.month.unwrap_or(1);
1997    let da = dt.day.unwrap_or(1);
1998    let hr = dt.hour.unwrap_or(0);
1999    let mi = dt.minute.unwrap_or(0);
2000    let se = dt.second.unwrap_or_default();
2001    let mo = i64::from(mo).checked_add(du.all_months())?;
2002    let (yr, mo) = normalize_month(yr, mo)?;
2003    let da = min(da, days_in_month(Some(yr), mo));
2004    let se = se.checked_add(du.all_seconds())?;
2005    let (yr, mo, da, hr, mi, se) =
2006        normalize_second(yr, mo.into(), da.into(), hr.into(), mi.into(), se)?;
2007
2008    Some(DateTimeSevenPropertyModel {
2009        year: dt.year.map(|_| yr),
2010        month: dt.month.map(|_| mo),
2011        day: dt.day.map(|_| da),
2012        hour: dt.hour.map(|_| hr),
2013        minute: dt.minute.map(|_| mi),
2014        second: dt.second.map(|_| se),
2015        timezone_offset: dt.timezone_offset,
2016    })
2017}
2018
2019/// The [timeOnTimeline](https://www.w3.org/TR/xmlschema11-2/#vp-dt-timeOnTimeline) function
2020fn time_on_timeline(props: &DateTimeSevenPropertyModel) -> Option<Decimal> {
2021    let yr = props.year.map_or(1971, |y| y - 1);
2022    let mo = props.month.unwrap_or(12);
2023    let da = props
2024        .day
2025        .map_or_else(|| days_in_month(Some(yr + 1), mo) - 1, |d| d - 1);
2026    let hr = props.hour.unwrap_or(0);
2027    let mi = i128::from(props.minute.unwrap_or(0))
2028        - i128::from(props.timezone_offset.unwrap_or(TimezoneOffset::UTC).offset);
2029    let se = props.second.unwrap_or_default();
2030
2031    Decimal::try_from(
2032        31_536_000 * i128::from(yr)
2033            + 86400 * i128::from(yr.div_euclid(400) - yr.div_euclid(100) + yr.div_euclid(4))
2034            + 86400
2035                * (1..mo)
2036                    .map(|m| i128::from(days_in_month(Some(yr + 1), m)))
2037                    .sum::<i128>()
2038            + 86400 * i128::from(da)
2039            + 3600 * i128::from(hr)
2040            + 60 * mi,
2041    )
2042    .ok()?
2043    .checked_add(se)
2044}
2045
2046/// A parsing error
2047#[derive(Debug, thiserror::Error)]
2048#[error(transparent)]
2049pub struct ParseDateTimeError(#[from] ParseDateTimeErrorKind);
2050
2051#[derive(Debug, Clone, thiserror::Error)]
2052enum ParseDateTimeErrorKind {
2053    #[error("{day} is not a valid day of {month}")]
2054    InvalidDayOfMonth { day: u8, month: u8 },
2055    #[error(transparent)]
2056    Overflow(#[from] DateTimeOverflowError),
2057    #[error(transparent)]
2058    InvalidTimezone(InvalidTimezoneError),
2059    #[error("{0}")]
2060    Message(&'static str),
2061}
2062
2063impl ParseDateTimeError {
2064    const fn msg(message: &'static str) -> Self {
2065        Self(ParseDateTimeErrorKind::Message(message))
2066    }
2067}
2068
2069// [16]   dateTimeLexicalRep ::= yearFrag '-' monthFrag '-' dayFrag 'T' ((hourFrag ':' minuteFrag ':' secondFrag) | endOfDayFrag) timezoneFrag?
2070fn date_time_lexical_rep(input: &str) -> Result<(DateTime, &str), ParseDateTimeError> {
2071    let (year, input) = year_frag(input)?;
2072    let input = expect_char(input, '-', "The year and month must be separated by '-'")?;
2073    let (month, input) = month_frag(input)?;
2074    let input = expect_char(input, '-', "The month and day must be separated by '-'")?;
2075    let (day, input) = day_frag(input)?;
2076    let input = expect_char(input, 'T', "The date and time must be separated by 'T'")?;
2077    let (hour, input) = hour_frag(input)?;
2078    let input = expect_char(input, ':', "The hours and minutes must be separated by ':'")?;
2079    let (minute, input) = minute_frag(input)?;
2080    let input = expect_char(
2081        input,
2082        ':',
2083        "The minutes and seconds must be separated by ':'",
2084    )?;
2085    let (second, input) = second_frag(input)?;
2086    // We validate 24:00:00
2087    if hour == 24 && minute != 0 && second != Decimal::from(0) {
2088        return Err(ParseDateTimeError::msg(
2089            "Times are not allowed to be after 24:00:00",
2090        ));
2091    }
2092    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
2093    validate_day_of_month(Some(year), month, day)?;
2094    Ok((
2095        DateTime::new(year, month, day, hour, minute, second, timezone_offset)?,
2096        input,
2097    ))
2098}
2099
2100// [17]   timeLexicalRep ::= ((hourFrag ':' minuteFrag ':' secondFrag) | endOfDayFrag) timezoneFrag?
2101fn time_lexical_rep(input: &str) -> Result<(Time, &str), ParseDateTimeError> {
2102    let (hour, input) = hour_frag(input)?;
2103    let input = expect_char(input, ':', "The hours and minutes must be separated by ':'")?;
2104    let (minute, input) = minute_frag(input)?;
2105    let input = expect_char(
2106        input,
2107        ':',
2108        "The minutes and seconds must be separated by ':'",
2109    )?;
2110    let (second, input) = second_frag(input)?;
2111    // We validate 24:00:00
2112    if hour == 24 && minute != 0 && second != Decimal::from(0) {
2113        return Err(ParseDateTimeError::msg(
2114            "Times are not allowed to be after 24:00:00",
2115        ));
2116    }
2117    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
2118    Ok((Time::new(hour, minute, second, timezone_offset)?, input))
2119}
2120
2121// [18]   dateLexicalRep ::= yearFrag '-' monthFrag '-' dayFrag timezoneFrag?   Constraint:  Day-of-month Representations
2122fn date_lexical_rep(input: &str) -> Result<(Date, &str), ParseDateTimeError> {
2123    let (year, input) = year_frag(input)?;
2124    let input = expect_char(input, '-', "The year and month must be separated by '-'")?;
2125    let (month, input) = month_frag(input)?;
2126    let input = expect_char(input, '-', "The month and day must be separated by '-'")?;
2127    let (day, input) = day_frag(input)?;
2128    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
2129    validate_day_of_month(Some(year), month, day)?;
2130    Ok((Date::new(year, month, day, timezone_offset)?, input))
2131}
2132
2133// [19]   gYearMonthLexicalRep ::= yearFrag '-' monthFrag timezoneFrag?
2134fn g_year_month_lexical_rep(input: &str) -> Result<(GYearMonth, &str), ParseDateTimeError> {
2135    let (year, input) = year_frag(input)?;
2136    let input = expect_char(input, '-', "The year and month must be separated by '-'")?;
2137    let (month, input) = month_frag(input)?;
2138    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
2139    Ok((GYearMonth::new(year, month, timezone_offset)?, input))
2140}
2141
2142// [20]   gYearLexicalRep ::= yearFrag timezoneFrag?
2143fn g_year_lexical_rep(input: &str) -> Result<(GYear, &str), ParseDateTimeError> {
2144    let (year, input) = year_frag(input)?;
2145    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
2146    Ok((GYear::new(year, timezone_offset)?, input))
2147}
2148
2149// [21]   gMonthDayLexicalRep ::= '--' monthFrag '-' dayFrag timezoneFrag?   Constraint:  Day-of-month Representations
2150fn g_month_day_lexical_rep(input: &str) -> Result<(GMonthDay, &str), ParseDateTimeError> {
2151    let input = expect_char(input, '-', "gMonthDay values must start with '--'")?;
2152    let input = expect_char(input, '-', "gMonthDay values must start with '--'")?;
2153    let (month, input) = month_frag(input)?;
2154    let input = expect_char(input, '-', "The month and day must be separated by '-'")?;
2155    let (day, input) = day_frag(input)?;
2156    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
2157    validate_day_of_month(None, month, day)?;
2158    Ok((GMonthDay::new(month, day, timezone_offset)?, input))
2159}
2160
2161// [22]   gDayLexicalRep ::= '---' dayFrag timezoneFrag?
2162fn g_day_lexical_rep(input: &str) -> Result<(GDay, &str), ParseDateTimeError> {
2163    let input = expect_char(input, '-', "gDay values must start with '---'")?;
2164    let input = expect_char(input, '-', "gDay values must start with '---'")?;
2165    let input = expect_char(input, '-', "gDay values must start with '---'")?;
2166    let (day, input) = day_frag(input)?;
2167    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
2168    Ok((GDay::new(day, timezone_offset)?, input))
2169}
2170
2171// [23]   gMonthLexicalRep ::= '--' monthFrag timezoneFrag?
2172fn g_month_lexical_rep(input: &str) -> Result<(GMonth, &str), ParseDateTimeError> {
2173    let input = expect_char(input, '-', "gMonth values must start with '--'")?;
2174    let input = expect_char(input, '-', "gMonth values must start with '--'")?;
2175    let (month, input) = month_frag(input)?;
2176    let (timezone_offset, input) = optional_end(input, timezone_frag)?;
2177    Ok((GMonth::new(month, timezone_offset)?, input))
2178}
2179
2180// [56]   yearFrag ::= '-'? (([1-9] digit digit digit+)) | ('0' digit digit digit))
2181fn year_frag(input: &str) -> Result<(i64, &str), ParseDateTimeError> {
2182    let (sign, input) = if let Some(left) = input.strip_prefix('-') {
2183        (-1, left)
2184    } else {
2185        (1, input)
2186    };
2187    let (number_str, input) = integer_prefix(input);
2188    if number_str.len() < 4 {
2189        return Err(ParseDateTimeError::msg(
2190            "The year should be encoded on 4 digits",
2191        ));
2192    }
2193    if number_str.len() > 4 && number_str.starts_with('0') {
2194        return Err(ParseDateTimeError::msg(
2195            "The years value must not start with 0 if it can be encoded in at least 4 digits",
2196        ));
2197    }
2198    let number = i64::from_str(number_str).expect("valid integer");
2199    Ok((sign * number, input))
2200}
2201
2202// [57]   monthFrag ::= ('0' [1-9]) | ('1' [0-2])
2203fn month_frag(input: &str) -> Result<(u8, &str), ParseDateTimeError> {
2204    let (number_str, input) = integer_prefix(input);
2205    if number_str.len() != 2 {
2206        return Err(ParseDateTimeError::msg(
2207            "Month must be encoded with two digits",
2208        ));
2209    }
2210    let number = u8::from_str(number_str).expect("valid integer");
2211    if !(1..=12).contains(&number) {
2212        return Err(ParseDateTimeError::msg("Month must be between 01 and 12"));
2213    }
2214    Ok((number, input))
2215}
2216
2217// [58]   dayFrag ::= ('0' [1-9]) | ([12] digit) | ('3' [01])
2218fn day_frag(input: &str) -> Result<(u8, &str), ParseDateTimeError> {
2219    let (number_str, input) = integer_prefix(input);
2220    if number_str.len() != 2 {
2221        return Err(ParseDateTimeError::msg(
2222            "Day must be encoded with two digits",
2223        ));
2224    }
2225    let number = u8::from_str(number_str).expect("valid integer");
2226    if !(1..=31).contains(&number) {
2227        return Err(ParseDateTimeError::msg("Day must be between 01 and 31"));
2228    }
2229    Ok((number, input))
2230}
2231
2232// [59]   hourFrag ::= ([01] digit) | ('2' [0-3])
2233// We also allow 24 for ease of parsing
2234fn hour_frag(input: &str) -> Result<(u8, &str), ParseDateTimeError> {
2235    let (number_str, input) = integer_prefix(input);
2236    if number_str.len() != 2 {
2237        return Err(ParseDateTimeError::msg(
2238            "Hours must be encoded with two digits",
2239        ));
2240    }
2241    let number = u8::from_str(number_str).expect("valid integer");
2242    if !(0..=24).contains(&number) {
2243        return Err(ParseDateTimeError::msg("Hours must be between 00 and 24"));
2244    }
2245    Ok((number, input))
2246}
2247
2248// [60]   minuteFrag ::= [0-5] digit
2249fn minute_frag(input: &str) -> Result<(u8, &str), ParseDateTimeError> {
2250    let (number_str, input) = integer_prefix(input);
2251    if number_str.len() != 2 {
2252        return Err(ParseDateTimeError::msg(
2253            "Minutes must be encoded with two digits",
2254        ));
2255    }
2256    let number = u8::from_str(number_str).expect("valid integer");
2257    if !(0..=59).contains(&number) {
2258        return Err(ParseDateTimeError::msg("Minutes must be between 00 and 59"));
2259    }
2260    Ok((number, input))
2261}
2262
2263// [61]   secondFrag ::= ([0-5] digit) ('.' digit+)?
2264fn second_frag(input: &str) -> Result<(Decimal, &str), ParseDateTimeError> {
2265    let (number_str, input) = decimal_prefix(input);
2266    let (before_dot_str, _) = number_str.split_once('.').unwrap_or((number_str, ""));
2267    if before_dot_str.len() != 2 {
2268        return Err(ParseDateTimeError::msg(
2269            "Seconds must be encoded with two digits",
2270        ));
2271    }
2272    let number = Decimal::from_str(number_str)
2273        .map_err(|_| ParseDateTimeError::msg("The second precision is too large"))?;
2274    if number < Decimal::from(0) || number >= Decimal::from(60) {
2275        return Err(ParseDateTimeError::msg("Seconds must be between 00 and 60"));
2276    }
2277    if number_str.ends_with('.') {
2278        return Err(ParseDateTimeError::msg(
2279            "Seconds are not allowed to end with a dot",
2280        ));
2281    }
2282    Ok((number, input))
2283}
2284
2285// [63]   timezoneFrag ::= 'Z' | ('+' | '-') (('0' digit | '1' [0-3]) ':' minuteFrag | '14:00')
2286fn timezone_frag(input: &str) -> Result<(TimezoneOffset, &str), ParseDateTimeError> {
2287    if let Some(left) = input.strip_prefix('Z') {
2288        return Ok((TimezoneOffset::UTC, left));
2289    }
2290    let (sign, input) = if let Some(left) = input.strip_prefix('-') {
2291        (-1, left)
2292    } else if let Some(left) = input.strip_prefix('+') {
2293        (1, left)
2294    } else {
2295        (1, input)
2296    };
2297
2298    let (hour_str, input) = integer_prefix(input);
2299    if hour_str.len() != 2 {
2300        return Err(ParseDateTimeError::msg(
2301            "The timezone hours must be encoded with two digits",
2302        ));
2303    }
2304    let hours = i16::from_str(hour_str).expect("valid integer");
2305
2306    let input = expect_char(
2307        input,
2308        ':',
2309        "The timezone hours and minutes must be separated by ':'",
2310    )?;
2311    let (minutes, input) = minute_frag(input)?;
2312
2313    if hours > 13 && !(hours == 14 && minutes == 0) {
2314        return Err(ParseDateTimeError::msg(
2315            "The timezone hours must be between 00 and 13",
2316        ));
2317    }
2318
2319    Ok((
2320        TimezoneOffset::new(sign * (hours * 60 + i16::from(minutes)))
2321            .map_err(|e| ParseDateTimeError(ParseDateTimeErrorKind::InvalidTimezone(e)))?,
2322        input,
2323    ))
2324}
2325
2326fn ensure_complete<T>(
2327    input: &str,
2328    parse: impl FnOnce(&str) -> Result<(T, &str), ParseDateTimeError>,
2329) -> Result<T, ParseDateTimeError> {
2330    let (result, left) = parse(input)?;
2331    if !left.is_empty() {
2332        return Err(ParseDateTimeError::msg("Unrecognized value suffix"));
2333    }
2334    Ok(result)
2335}
2336
2337fn expect_char<'a>(
2338    input: &'a str,
2339    constant: char,
2340    error_message: &'static str,
2341) -> Result<&'a str, ParseDateTimeError> {
2342    if let Some(left) = input.strip_prefix(constant) {
2343        Ok(left)
2344    } else {
2345        Err(ParseDateTimeError::msg(error_message))
2346    }
2347}
2348
2349fn integer_prefix(input: &str) -> (&str, &str) {
2350    let mut end = input.len();
2351    for (i, c) in input.char_indices() {
2352        if !c.is_ascii_digit() {
2353            end = i;
2354            break;
2355        }
2356    }
2357    input.split_at(end)
2358}
2359
2360fn decimal_prefix(input: &str) -> (&str, &str) {
2361    let mut end = input.len();
2362    let mut dot_seen = false;
2363    for (i, c) in input.char_indices() {
2364        if c.is_ascii_digit() {
2365            // Ok
2366        } else if c == '.' && !dot_seen {
2367            dot_seen = true;
2368        } else {
2369            end = i;
2370            break;
2371        }
2372    }
2373    input.split_at(end)
2374}
2375
2376fn optional_end<T>(
2377    input: &str,
2378    parse: impl FnOnce(&str) -> Result<(T, &str), ParseDateTimeError>,
2379) -> Result<(Option<T>, &str), ParseDateTimeError> {
2380    Ok(if input.is_empty() {
2381        (None, input)
2382    } else {
2383        let (result, input) = parse(input)?;
2384        (Some(result), input)
2385    })
2386}
2387
2388fn validate_day_of_month(year: Option<i64>, month: u8, day: u8) -> Result<(), ParseDateTimeError> {
2389    // Constraint: Day-of-month Values
2390    if day > days_in_month(year, month) {
2391        return Err(ParseDateTimeError(
2392            ParseDateTimeErrorKind::InvalidDayOfMonth { day, month },
2393        ));
2394    }
2395    Ok(())
2396}
2397
2398/// An overflow during [`DateTime`]-related operations.
2399///
2400/// Matches XPath [`FODT0001` error](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0001).
2401#[derive(Debug, Clone, Copy, thiserror::Error)]
2402#[error("overflow during xsd:dateTime computation")]
2403pub struct DateTimeOverflowError;
2404
2405impl From<DateTimeOverflowError> for ParseDateTimeError {
2406    fn from(error: DateTimeOverflowError) -> Self {
2407        Self(ParseDateTimeErrorKind::Overflow(error))
2408    }
2409}
2410
2411/// The value provided as timezone is not valid.
2412///
2413/// Matches XPath [`FODT0003` error](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0003).
2414#[derive(Debug, Clone, Copy, thiserror::Error)]
2415#[error("invalid timezone offset {}:{}",
2416        self.offset_in_minutes / 60,
2417        self.offset_in_minutes.abs() % 60)]
2418pub struct InvalidTimezoneError {
2419    offset_in_minutes: i64,
2420}
2421
2422#[cfg(test)]
2423#[allow(clippy::panic_in_result_fn)]
2424mod tests {
2425    use super::*;
2426    use std::error::Error;
2427
2428    #[test]
2429    fn from_str() -> Result<(), ParseDateTimeError> {
2430        assert_eq!(Time::from_str("00:00:00Z")?.to_string(), "00:00:00Z");
2431        assert_eq!(Time::from_str("00:00:00+00:00")?.to_string(), "00:00:00Z");
2432        assert_eq!(Time::from_str("00:00:00-00:00")?.to_string(), "00:00:00Z");
2433        assert_eq!(Time::from_str("00:00:00")?.to_string(), "00:00:00");
2434        assert_eq!(
2435            Time::from_str("00:00:00+02:00")?.to_string(),
2436            "00:00:00+02:00"
2437        );
2438        assert_eq!(
2439            Time::from_str("00:00:00+14:00")?.to_string(),
2440            "00:00:00+14:00"
2441        );
2442        assert_eq!(Time::from_str("24:00:00")?.to_string(), "00:00:00");
2443        assert_eq!(Time::from_str("24:00:00.00")?.to_string(), "00:00:00");
2444        assert_eq!(
2445            Time::from_str("23:59:59.9999999999")?.to_string(),
2446            "23:59:59.9999999999"
2447        );
2448
2449        assert_eq!(Date::from_str("0001-01-01Z")?.to_string(), "0001-01-01Z");
2450        assert_eq!(Date::from_str("0001-01-01")?.to_string(), "0001-01-01");
2451        assert_eq!(
2452            DateTime::from_str("0001-01-01T00:00:00Z")?.to_string(),
2453            "0001-01-01T00:00:00Z"
2454        );
2455        assert_eq!(
2456            DateTime::from_str("0001-01-01T00:00:00")?.to_string(),
2457            "0001-01-01T00:00:00"
2458        );
2459        assert_eq!(
2460            DateTime::from_str("1000000000-01-01T00:00:00")?.to_string(),
2461            "1000000000-01-01T00:00:00"
2462        );
2463        assert_eq!(
2464            DateTime::from_str("2001-12-31T23:59:59")?.to_string(),
2465            "2001-12-31T23:59:59"
2466        );
2467        assert_eq!(
2468            DateTime::from_str("2004-12-31T23:59:59")?.to_string(),
2469            "2004-12-31T23:59:59"
2470        );
2471        assert_eq!(
2472            DateTime::from_str("1900-12-31T23:59:59")?.to_string(),
2473            "1900-12-31T23:59:59"
2474        );
2475        assert_eq!(
2476            DateTime::from_str("2000-12-31T23:59:59")?.to_string(),
2477            "2000-12-31T23:59:59",
2478        );
2479        assert_eq!(
2480            DateTime::from_str("1899-12-31T23:59:59")?.to_string(),
2481            "1899-12-31T23:59:59"
2482        );
2483
2484        assert_eq!(
2485            DateTime::from_str("2001-02-28T23:59:59")?.to_string(),
2486            "2001-02-28T23:59:59"
2487        );
2488        assert_eq!(
2489            DateTime::from_str("2004-02-29T23:59:59")?.to_string(),
2490            "2004-02-29T23:59:59"
2491        );
2492        assert_eq!(
2493            DateTime::from_str("1900-02-28T23:59:59")?.to_string(),
2494            "1900-02-28T23:59:59"
2495        );
2496        assert_eq!(
2497            DateTime::from_str("2000-02-29T23:59:59")?.to_string(),
2498            "2000-02-29T23:59:59",
2499        );
2500        assert_eq!(
2501            DateTime::from_str("1899-02-28T23:59:59")?.to_string(),
2502            "1899-02-28T23:59:59"
2503        );
2504        assert_eq!(
2505            DateTime::from_str("2001-03-01T00:00:00")?.to_string(),
2506            "2001-03-01T00:00:00"
2507        );
2508        assert_eq!(
2509            DateTime::from_str("2004-03-01T00:00:00")?.to_string(),
2510            "2004-03-01T00:00:00"
2511        );
2512        assert_eq!(
2513            DateTime::from_str("1900-03-01T00:00:00")?.to_string(),
2514            "1900-03-01T00:00:00"
2515        );
2516        assert_eq!(
2517            DateTime::from_str("2000-03-01T00:00:00")?.to_string(),
2518            "2000-03-01T00:00:00",
2519        );
2520        assert_eq!(
2521            DateTime::from_str("1899-03-01T00:00:00")?.to_string(),
2522            "1899-03-01T00:00:00"
2523        );
2524        assert_eq!(
2525            DateTime::from_str("-0899-03-01T00:00:00")?.to_string(),
2526            "-0899-03-01T00:00:00"
2527        );
2528        assert_eq!(
2529            DateTime::from_str("2000-01-01T00:00:00.1234567")?.to_string(),
2530            "2000-01-01T00:00:00.1234567"
2531        );
2532        assert_eq!(
2533            DateTime::from_str("2000-01-01T00:00:12.1234567")?.to_string(),
2534            "2000-01-01T00:00:12.1234567"
2535        );
2536        assert_eq!(
2537            Time::from_str("01:02:03.1234567")?.to_string(),
2538            "01:02:03.1234567"
2539        );
2540        assert_eq!(
2541            Time::from_str("01:02:13.1234567")?.to_string(),
2542            "01:02:13.1234567"
2543        );
2544
2545        assert_eq!(
2546            DateTime::from_str("-1000000000-01-01T00:00:00")?.to_string(),
2547            "-1000000000-01-01T00:00:00"
2548        );
2549        assert_eq!(
2550            DateTime::from_str("-2001-12-31T23:59:59")?.to_string(),
2551            "-2001-12-31T23:59:59"
2552        );
2553        assert_eq!(
2554            DateTime::from_str("-2004-12-31T23:59:59")?.to_string(),
2555            "-2004-12-31T23:59:59"
2556        );
2557        assert_eq!(
2558            DateTime::from_str("-1900-12-31T23:59:59")?.to_string(),
2559            "-1900-12-31T23:59:59"
2560        );
2561        assert_eq!(
2562            DateTime::from_str("-2000-12-31T23:59:59")?.to_string(),
2563            "-2000-12-31T23:59:59"
2564        );
2565        assert_eq!(
2566            DateTime::from_str("-1899-12-31T23:59:59")?.to_string(),
2567            "-1899-12-31T23:59:59"
2568        );
2569
2570        assert_eq!(
2571            GYearMonth::from_str("-1899-12+01:00")?.to_string(),
2572            "-1899-12+01:00"
2573        );
2574        assert_eq!(GYearMonth::from_str("-1899-12")?.to_string(), "-1899-12");
2575        assert_eq!(GYear::from_str("-1899+01:00")?.to_string(), "-1899+01:00");
2576        assert_eq!(GYear::from_str("-1899")?.to_string(), "-1899");
2577        assert_eq!(
2578            GMonthDay::from_str("--01-01+01:00")?.to_string(),
2579            "--01-01+01:00"
2580        );
2581        assert_eq!(GMonthDay::from_str("--01-01")?.to_string(), "--01-01");
2582        assert_eq!(GDay::from_str("---01+01:00")?.to_string(), "---01+01:00");
2583        assert_eq!(GDay::from_str("---01")?.to_string(), "---01");
2584        assert_eq!(GMonth::from_str("--01+01:00")?.to_string(), "--01+01:00");
2585        assert_eq!(GMonth::from_str("--01")?.to_string(), "--01");
2586
2587        GYear::from_str("02020").unwrap_err();
2588        GYear::from_str("+2020").unwrap_err();
2589        GYear::from_str("33").unwrap_err();
2590
2591        assert_eq!(Time::from_str("00:00:00+14:00")?, Time::MIN);
2592        assert_eq!(Time::from_str("24:00:00-14:00")?, Time::MAX);
2593        Ok(())
2594    }
2595
2596    #[test]
2597    fn to_be_bytes() -> Result<(), ParseDateTimeError> {
2598        assert_eq!(
2599            DateTime::from_be_bytes(DateTime::MIN.to_be_bytes()),
2600            DateTime::MIN
2601        );
2602        assert_eq!(
2603            DateTime::from_be_bytes(DateTime::MAX.to_be_bytes()),
2604            DateTime::MAX
2605        );
2606        assert_eq!(
2607            DateTime::from_be_bytes(DateTime::from_str("2022-01-03T01:02:03")?.to_be_bytes()),
2608            DateTime::from_str("2022-01-03T01:02:03")?
2609        );
2610        assert_eq!(Date::from_be_bytes(Date::MIN.to_be_bytes()), Date::MIN);
2611        assert_eq!(Date::from_be_bytes(Date::MAX.to_be_bytes()), Date::MAX);
2612        assert_eq!(
2613            Date::from_be_bytes(Date::from_str("2022-01-03")?.to_be_bytes()),
2614            Date::from_str("2022-01-03")?
2615        );
2616        assert_eq!(Time::from_be_bytes(Time::MIN.to_be_bytes()), Time::MIN);
2617        assert_eq!(Time::from_be_bytes(Time::MAX.to_be_bytes()), Time::MAX);
2618        assert_eq!(
2619            Time::from_be_bytes(Time::from_str("01:02:03")?.to_be_bytes()),
2620            Time::from_str("01:02:03")?
2621        );
2622        assert_eq!(
2623            Time::from_be_bytes(Time::from_str("01:02:03")?.to_be_bytes()),
2624            Time::from_str("01:02:03")?
2625        );
2626        assert_eq!(
2627            GYearMonth::from_be_bytes(GYearMonth::MIN.to_be_bytes()),
2628            GYearMonth::MIN
2629        );
2630        assert_eq!(
2631            GYearMonth::from_be_bytes(GYearMonth::MAX.to_be_bytes()),
2632            GYearMonth::MAX
2633        );
2634        assert_eq!(GYear::from_be_bytes(GYear::MIN.to_be_bytes()), GYear::MIN);
2635        assert_eq!(GYear::from_be_bytes(GYear::MAX.to_be_bytes()), GYear::MAX);
2636        Ok(())
2637    }
2638
2639    #[test]
2640    fn equals() -> Result<(), ParseDateTimeError> {
2641        assert_eq!(
2642            DateTime::from_str("2002-04-02T12:00:00-01:00")?,
2643            DateTime::from_str("2002-04-02T17:00:00+04:00")?
2644        );
2645        assert_eq!(
2646            DateTime::from_str("2002-04-02T12:00:00-05:00")?,
2647            DateTime::from_str("2002-04-02T23:00:00+06:00")?
2648        );
2649        assert_ne!(
2650            DateTime::from_str("2002-04-02T12:00:00-05:00")?,
2651            DateTime::from_str("2002-04-02T17:00:00-05:00")?
2652        );
2653        assert_eq!(
2654            DateTime::from_str("2002-04-02T12:00:00-05:00")?,
2655            DateTime::from_str("2002-04-02T12:00:00-05:00")?
2656        );
2657        assert_eq!(
2658            DateTime::from_str("2002-04-02T23:00:00-04:00")?,
2659            DateTime::from_str("2002-04-03T02:00:00-01:00")?
2660        );
2661        assert_eq!(
2662            DateTime::from_str("1999-12-31T24:00:00-05:00")?,
2663            DateTime::from_str("2000-01-01T00:00:00-05:00")?
2664        );
2665        assert_ne!(
2666            DateTime::from_str("2005-04-04T24:00:00-05:00")?,
2667            DateTime::from_str("2005-04-04T00:00:00-05:00")?
2668        );
2669
2670        assert_ne!(
2671            Date::from_str("2004-12-25Z")?,
2672            Date::from_str("2004-12-25+07:00")?
2673        );
2674        assert_eq!(
2675            Date::from_str("2004-12-25-12:00")?,
2676            Date::from_str("2004-12-26+12:00")?
2677        );
2678
2679        assert_ne!(
2680            Time::from_str("08:00:00+09:00")?,
2681            Time::from_str("17:00:00-06:00")?
2682        );
2683        assert_eq!(
2684            Time::from_str("21:30:00+10:30")?,
2685            Time::from_str("06:00:00-05:00")?
2686        );
2687        assert_eq!(
2688            Time::from_str("24:00:00+01:00")?,
2689            Time::from_str("00:00:00+01:00")?
2690        );
2691
2692        assert_eq!(
2693            Time::from_str("05:00:00-03:00")?,
2694            Time::from_str("10:00:00+02:00")?
2695        );
2696        assert_ne!(
2697            Time::from_str("23:00:00-03:00")?,
2698            Time::from_str("02:00:00Z")?
2699        );
2700
2701        assert_ne!(
2702            GYearMonth::from_str("1986-02")?,
2703            GYearMonth::from_str("1986-03")?
2704        );
2705        assert_ne!(
2706            GYearMonth::from_str("1978-03")?,
2707            GYearMonth::from_str("1978-03Z")?
2708        );
2709
2710        assert_ne!(
2711            GYear::from_str("2005-12:00")?,
2712            GYear::from_str("2005+12:00")?
2713        );
2714        assert_ne!(GYear::from_str("1976-05:00")?, GYear::from_str("1976")?);
2715
2716        assert_eq!(
2717            GMonthDay::from_str("--12-25-14:00")?,
2718            GMonthDay::from_str("--12-26+10:00")?
2719        );
2720        assert_ne!(
2721            GMonthDay::from_str("--12-25")?,
2722            GMonthDay::from_str("--12-26Z")?
2723        );
2724
2725        assert_ne!(
2726            GMonth::from_str("--12-14:00")?,
2727            GMonth::from_str("--12+10:00")?
2728        );
2729        assert_ne!(GMonth::from_str("--12")?, GMonth::from_str("--12Z")?);
2730
2731        assert_ne!(
2732            GDay::from_str("---25-14:00")?,
2733            GDay::from_str("---25+10:00")?
2734        );
2735        assert_ne!(GDay::from_str("---12")?, GDay::from_str("---12Z")?);
2736        Ok(())
2737    }
2738
2739    #[test]
2740    #[allow(clippy::neg_cmp_op_on_partial_ord)]
2741    fn cmp() -> Result<(), ParseDateTimeError> {
2742        assert!(Date::from_str("2004-12-25Z")? < Date::from_str("2004-12-25-05:00")?);
2743        assert!(!(Date::from_str("2004-12-25-12:00")? < Date::from_str("2004-12-26+12:00")?));
2744
2745        assert!(Date::from_str("2004-12-25Z")? > Date::from_str("2004-12-25+07:00")?);
2746        assert!(!(Date::from_str("2004-12-25-12:00")? > Date::from_str("2004-12-26+12:00")?));
2747
2748        assert!(!(Time::from_str("12:00:00")? < Time::from_str("23:00:00+06:00")?));
2749        assert!(Time::from_str("11:00:00-05:00")? < Time::from_str("17:00:00Z")?);
2750        assert!(!(Time::from_str("23:59:59")? < Time::from_str("24:00:00")?));
2751
2752        assert!(!(Time::from_str("08:00:00+09:00")? > Time::from_str("17:00:00-06:00")?));
2753
2754        assert!(GMonthDay::from_str("--12-12+13:00")? < GMonthDay::from_str("--12-12+11:00")?);
2755        assert!(GDay::from_str("---15")? < GDay::from_str("---16")?);
2756        assert!(GDay::from_str("---15-13:00")? > GDay::from_str("---16+13:00")?);
2757        assert_eq!(
2758            GDay::from_str("---15-11:00")?,
2759            GDay::from_str("---16+13:00")?
2760        );
2761        assert!(GDay::from_str("---15-13:00")?
2762            .partial_cmp(&GDay::from_str("---16")?)
2763            .is_none());
2764        Ok(())
2765    }
2766
2767    #[test]
2768    fn year() -> Result<(), ParseDateTimeError> {
2769        assert_eq!(
2770            DateTime::from_str("1999-05-31T13:20:00-05:00")?.year(),
2771            1999
2772        );
2773        assert_eq!(
2774            DateTime::from_str("1999-05-31T21:30:00-05:00")?.year(),
2775            1999
2776        );
2777        assert_eq!(DateTime::from_str("1999-12-31T19:20:00")?.year(), 1999);
2778        assert_eq!(DateTime::from_str("1999-12-31T24:00:00")?.year(), 2000);
2779        assert_eq!(DateTime::from_str("-0002-06-06T00:00:00")?.year(), -2);
2780
2781        assert_eq!(Date::from_str("1999-05-31")?.year(), 1999);
2782        assert_eq!(Date::from_str("2000-01-01+05:00")?.year(), 2000);
2783        assert_eq!(Date::from_str("-0002-06-01")?.year(), -2);
2784
2785        assert_eq!(GYear::from_str("-0002")?.year(), -2);
2786        assert_eq!(GYearMonth::from_str("-0002-02")?.year(), -2);
2787        Ok(())
2788    }
2789
2790    #[test]
2791    fn month() -> Result<(), ParseDateTimeError> {
2792        assert_eq!(DateTime::from_str("1999-05-31T13:20:00-05:00")?.month(), 5);
2793        assert_eq!(DateTime::from_str("1999-12-31T19:20:00-05:00")?.month(), 12);
2794
2795        assert_eq!(Date::from_str("1999-05-31-05:00")?.month(), 5);
2796        assert_eq!(Date::from_str("2000-01-01+05:00")?.month(), 1);
2797
2798        assert_eq!(GMonth::from_str("--02")?.month(), 2);
2799        assert_eq!(GYearMonth::from_str("-0002-02")?.month(), 2);
2800        assert_eq!(GMonthDay::from_str("--02-03")?.month(), 2);
2801        Ok(())
2802    }
2803
2804    #[test]
2805    fn day() -> Result<(), ParseDateTimeError> {
2806        assert_eq!(DateTime::from_str("1999-05-31T13:20:00-05:00")?.day(), 31);
2807        assert_eq!(DateTime::from_str("1999-12-31T20:00:00-05:00")?.day(), 31);
2808
2809        assert_eq!(Date::from_str("1999-05-31-05:00")?.day(), 31);
2810        assert_eq!(Date::from_str("2000-01-01+05:00")?.day(), 1);
2811
2812        assert_eq!(GDay::from_str("---03")?.day(), 3);
2813        assert_eq!(GMonthDay::from_str("--02-03")?.day(), 3);
2814        Ok(())
2815    }
2816
2817    #[test]
2818    fn hour() -> Result<(), ParseDateTimeError> {
2819        assert_eq!(DateTime::from_str("1999-05-31T08:20:00-05:00")?.hour(), 8);
2820        assert_eq!(DateTime::from_str("1999-12-31T21:20:00-05:00")?.hour(), 21);
2821        assert_eq!(DateTime::from_str("1999-12-31T12:00:00")?.hour(), 12);
2822        assert_eq!(DateTime::from_str("1999-12-31T24:00:00")?.hour(), 0);
2823
2824        assert_eq!(Time::from_str("11:23:00-05:00")?.hour(), 11);
2825        assert_eq!(Time::from_str("21:23:00-05:00")?.hour(), 21);
2826        assert_eq!(Time::from_str("01:23:00+05:00")?.hour(), 1);
2827        assert_eq!(Time::from_str("24:00:00")?.hour(), 0);
2828        Ok(())
2829    }
2830
2831    #[test]
2832    fn minute() -> Result<(), ParseDateTimeError> {
2833        assert_eq!(
2834            DateTime::from_str("1999-05-31T13:20:00-05:00")?.minute(),
2835            20
2836        );
2837        assert_eq!(
2838            DateTime::from_str("1999-05-31T13:30:00+05:30")?.minute(),
2839            30
2840        );
2841
2842        assert_eq!(Time::from_str("13:00:00Z")?.minute(), 0);
2843        Ok(())
2844    }
2845
2846    #[test]
2847    fn second() -> Result<(), Box<dyn Error>> {
2848        assert_eq!(
2849            DateTime::from_str("1999-05-31T13:20:00-05:00")?.second(),
2850            Decimal::from(0)
2851        );
2852
2853        assert_eq!(
2854            Time::from_str("13:20:10.5")?.second(),
2855            Decimal::from_str("10.5")?
2856        );
2857        Ok(())
2858    }
2859
2860    #[test]
2861    fn timezone() -> Result<(), Box<dyn Error>> {
2862        assert_eq!(
2863            DateTime::from_str("1999-05-31T13:20:00-05:00")?.timezone(),
2864            Some(DayTimeDuration::from_str("-PT5H")?)
2865        );
2866        assert_eq!(
2867            DateTime::from_str("2000-06-12T13:20:00Z")?.timezone(),
2868            Some(DayTimeDuration::from_str("PT0S")?)
2869        );
2870        assert_eq!(DateTime::from_str("2004-08-27T00:00:00")?.timezone(), None);
2871
2872        assert_eq!(
2873            Date::from_str("1999-05-31-05:00")?.timezone(),
2874            Some(DayTimeDuration::from_str("-PT5H")?)
2875        );
2876        assert_eq!(
2877            Date::from_str("2000-06-12Z")?.timezone(),
2878            Some(DayTimeDuration::from_str("PT0S")?)
2879        );
2880
2881        assert_eq!(
2882            Time::from_str("13:20:00-05:00")?.timezone(),
2883            Some(DayTimeDuration::from_str("-PT5H")?)
2884        );
2885        assert_eq!(Time::from_str("13:20:00")?.timezone(), None);
2886        Ok(())
2887    }
2888
2889    #[test]
2890    fn sub() -> Result<(), Box<dyn Error>> {
2891        assert_eq!(
2892            DateTime::from_str("2000-10-30T06:12:00-05:00")?
2893                .checked_sub(DateTime::from_str("1999-11-28T09:00:00Z")?),
2894            Some(DayTimeDuration::from_str("P337DT2H12M")?)
2895        );
2896
2897        assert_eq!(
2898            Date::from_str("2000-10-30")?.checked_sub(Date::from_str("1999-11-28")?),
2899            Some(DayTimeDuration::from_str("P337D")?)
2900        );
2901        assert_eq!(
2902            Date::from_str("2000-10-30+05:00")?.checked_sub(Date::from_str("1999-11-28Z")?),
2903            Some(DayTimeDuration::from_str("P336DT19H")?)
2904        );
2905        assert_eq!(
2906            Date::from_str("2000-10-15-05:00")?.checked_sub(Date::from_str("2000-10-10+02:00")?),
2907            Some(DayTimeDuration::from_str("P5DT7H")?)
2908        );
2909
2910        assert_eq!(
2911            Time::from_str("11:12:00Z")?.checked_sub(Time::from_str("04:00:00-05:00")?),
2912            Some(DayTimeDuration::from_str("PT2H12M")?)
2913        );
2914        assert_eq!(
2915            Time::from_str("11:00:00-05:00")?.checked_sub(Time::from_str("21:30:00+05:30")?),
2916            Some(DayTimeDuration::from_str("PT0S")?)
2917        );
2918        assert_eq!(
2919            Time::from_str("17:00:00-06:00")?.checked_sub(Time::from_str("08:00:00+09:00")?),
2920            Some(DayTimeDuration::from_str("P1D")?)
2921        );
2922        assert_eq!(
2923            Time::from_str("24:00:00")?.checked_sub(Time::from_str("23:59:59")?),
2924            Some(DayTimeDuration::from_str("-PT23H59M59S")?)
2925        );
2926        Ok(())
2927    }
2928
2929    #[test]
2930    fn add_duration() -> Result<(), Box<dyn Error>> {
2931        assert_eq!(
2932            DateTime::from_str("2000-01-12T12:13:14Z")?
2933                .checked_add_duration(Duration::from_str("P1Y3M5DT7H10M3.3S")?),
2934            Some(DateTime::from_str("2001-04-17T19:23:17.3Z")?)
2935        );
2936        assert_eq!(
2937            Date::from_str("2000-01-01")?.checked_add_duration(Duration::from_str("-P3M")?),
2938            Some(Date::from_str("1999-10-01")?)
2939        );
2940        assert_eq!(
2941            Date::from_str("2000-01-12")?.checked_add_duration(Duration::from_str("PT33H")?),
2942            Some(Date::from_str("2000-01-13")?)
2943        );
2944        assert_eq!(
2945            Date::from_str("2000-03-30")?.checked_add_duration(Duration::from_str("P1D")?),
2946            Some(Date::from_str("2000-03-31")?)
2947        );
2948        assert_eq!(
2949            Date::from_str("2000-03-31")?.checked_add_duration(Duration::from_str("P1M")?),
2950            Some(Date::from_str("2000-04-30")?)
2951        );
2952        assert_eq!(
2953            Date::from_str("2000-03-30")?.checked_add_duration(Duration::from_str("P1M")?),
2954            Some(Date::from_str("2000-04-30")?)
2955        );
2956        assert_eq!(
2957            Date::from_str("2000-04-30")?.checked_add_duration(Duration::from_str("P1D")?),
2958            Some(Date::from_str("2000-05-01")?)
2959        );
2960
2961        assert_eq!(
2962            DateTime::from_str("2000-10-30T11:12:00")?
2963                .checked_add_duration(Duration::from_str("P1Y2M")?),
2964            Some(DateTime::from_str("2001-12-30T11:12:00")?)
2965        );
2966        assert_eq!(
2967            DateTime::from_str("2000-10-30T11:12:00")?
2968                .checked_add_duration(Duration::from_str("P3DT1H15M")?),
2969            Some(DateTime::from_str("2000-11-02T12:27:00")?)
2970        );
2971
2972        assert_eq!(
2973            Date::from_str("2000-10-30")?.checked_add_duration(Duration::from_str("P1Y2M")?),
2974            Some(Date::from_str("2001-12-30")?)
2975        );
2976        assert_eq!(
2977            Date::from_str("2004-10-30Z")?.checked_add_duration(Duration::from_str("P2DT2H30M0S")?),
2978            Some(Date::from_str("2004-11-01Z")?)
2979        );
2980
2981        assert_eq!(
2982            Time::from_str("11:12:00")?.checked_add_duration(Duration::from_str("P3DT1H15M")?),
2983            Some(Time::from_str("12:27:00")?)
2984        );
2985        assert_eq!(
2986            Time::from_str("23:12:00+03:00")?
2987                .checked_add_duration(Duration::from_str("P1DT3H15M")?),
2988            Some(Time::from_str("02:27:00+03:00")?)
2989        );
2990        Ok(())
2991    }
2992
2993    #[test]
2994    fn sub_duration() -> Result<(), Box<dyn Error>> {
2995        assert_eq!(
2996            DateTime::from_str("2000-10-30T11:12:00")?
2997                .checked_sub_duration(Duration::from_str("P1Y2M")?),
2998            Some(DateTime::from_str("1999-08-30T11:12:00")?)
2999        );
3000        assert_eq!(
3001            DateTime::from_str("2000-10-30T11:12:00")?
3002                .checked_sub_duration(Duration::from_str("P3DT1H15M")?),
3003            Some(DateTime::from_str("2000-10-27T09:57:00")?)
3004        );
3005
3006        assert_eq!(
3007            Date::from_str("2000-10-30")?.checked_sub_duration(Duration::from_str("P1Y2M")?),
3008            Some(Date::from_str("1999-08-30")?)
3009        );
3010        assert_eq!(
3011            Date::from_str("2000-02-29Z")?.checked_sub_duration(Duration::from_str("P1Y")?),
3012            Some(Date::from_str("1999-02-28Z")?)
3013        );
3014        assert_eq!(
3015            Date::from_str("2000-10-31-05:00")?.checked_sub_duration(Duration::from_str("P1Y1M")?),
3016            Some(Date::from_str("1999-09-30-05:00")?)
3017        );
3018        assert_eq!(
3019            Date::from_str("2000-10-30")?.checked_sub_duration(Duration::from_str("P3DT1H15M")?),
3020            Some(Date::from_str("2000-10-26")?)
3021        );
3022
3023        assert_eq!(
3024            Time::from_str("11:12:00")?.checked_sub_duration(Duration::from_str("P3DT1H15M")?),
3025            Some(Time::from_str("09:57:00")?)
3026        );
3027        assert_eq!(
3028            Time::from_str("08:20:00-05:00")?
3029                .checked_sub_duration(Duration::from_str("P23DT10H10M")?),
3030            Some(Time::from_str("22:10:00-05:00")?)
3031        );
3032        Ok(())
3033    }
3034
3035    #[test]
3036    fn adjust() -> Result<(), Box<dyn Error>> {
3037        assert_eq!(
3038            DateTime::from_str("2002-03-07T10:00:00-07:00")?
3039                .adjust(Some(DayTimeDuration::from_str("PT10H")?.try_into()?)),
3040            Some(DateTime::from_str("2002-03-08T03:00:00+10:00")?)
3041        );
3042        assert_eq!(
3043            DateTime::from_str("2002-03-07T00:00:00+01:00")?
3044                .adjust(Some(DayTimeDuration::from_str("-PT8H")?.try_into()?)),
3045            Some(DateTime::from_str("2002-03-06T15:00:00-08:00")?)
3046        );
3047        assert_eq!(
3048            DateTime::from_str("2002-03-07T10:00:00")?.adjust(None),
3049            Some(DateTime::from_str("2002-03-07T10:00:00")?)
3050        );
3051        assert_eq!(
3052            DateTime::from_str("2002-03-07T10:00:00-07:00")?.adjust(None),
3053            Some(DateTime::from_str("2002-03-07T10:00:00")?)
3054        );
3055
3056        assert_eq!(
3057            Date::from_str("2002-03-07")?
3058                .adjust(Some(DayTimeDuration::from_str("-PT10H")?.try_into()?)),
3059            Some(Date::from_str("2002-03-07-10:00")?)
3060        );
3061        assert_eq!(
3062            Date::from_str("2002-03-07-07:00")?
3063                .adjust(Some(DayTimeDuration::from_str("-PT10H")?.try_into()?)),
3064            Some(Date::from_str("2002-03-06-10:00")?)
3065        );
3066        assert_eq!(
3067            Date::from_str("2002-03-07")?.adjust(None),
3068            Some(Date::from_str("2002-03-07")?)
3069        );
3070        assert_eq!(
3071            Date::from_str("2002-03-07-07:00")?.adjust(None),
3072            Some(Date::from_str("2002-03-07")?)
3073        );
3074
3075        assert_eq!(
3076            Time::from_str("10:00:00")?
3077                .adjust(Some(DayTimeDuration::from_str("-PT10H")?.try_into()?)),
3078            Some(Time::from_str("10:00:00-10:00")?)
3079        );
3080        assert_eq!(
3081            Time::from_str("10:00:00-07:00")?
3082                .adjust(Some(DayTimeDuration::from_str("-PT10H")?.try_into()?)),
3083            Some(Time::from_str("07:00:00-10:00")?)
3084        );
3085        assert_eq!(
3086            Time::from_str("10:00:00")?.adjust(None),
3087            Some(Time::from_str("10:00:00")?)
3088        );
3089        assert_eq!(
3090            Time::from_str("10:00:00-07:00")?.adjust(None),
3091            Some(Time::from_str("10:00:00")?)
3092        );
3093        assert_eq!(
3094            Time::from_str("10:00:00-07:00")?
3095                .adjust(Some(DayTimeDuration::from_str("PT10H")?.try_into()?)),
3096            Some(Time::from_str("03:00:00+10:00")?)
3097        );
3098        Ok(())
3099    }
3100
3101    #[test]
3102    fn time_from_datetime() -> Result<(), ParseDateTimeError> {
3103        assert_eq!(
3104            Time::from(DateTime::MIN),
3105            Time::from_str("19:51:08.312696284115894272-14:00")?
3106        );
3107        assert_eq!(
3108            Time::from(DateTime::MAX),
3109            Time::from_str("04:08:51.687303715884105727+14:00")?
3110        );
3111        Ok(())
3112    }
3113
3114    #[test]
3115    fn date_from_datetime() -> Result<(), Box<dyn Error>> {
3116        assert_eq!(
3117            Date::try_from(
3118                DateTime::MIN
3119                    .checked_add_day_time_duration(DayTimeDuration::from_str("P1D")?)
3120                    .unwrap()
3121            )?,
3122            Date::MIN
3123        );
3124        assert_eq!(Date::try_from(DateTime::MAX)?, Date::MAX);
3125        Ok(())
3126    }
3127
3128    #[test]
3129    fn g_year_month_from_date() {
3130        assert_eq!(GYearMonth::from(Date::MIN), GYearMonth::MIN);
3131        assert_eq!(GYearMonth::from(Date::MAX), GYearMonth::MAX);
3132    }
3133
3134    #[test]
3135    fn g_year_from_g_year_month() -> Result<(), ParseDateTimeError> {
3136        assert_eq!(GYear::try_from(GYearMonth::MIN)?, GYear::MIN);
3137        assert_eq!(
3138            GYear::try_from(GYearMonth::from_str("5391559471918-12+14:00")?)?,
3139            GYear::MAX
3140        );
3141        Ok(())
3142    }
3143
3144    #[cfg(feature = "custom-now")]
3145    #[test]
3146    fn custom_now() {
3147        #[allow(unsafe_code)]
3148        #[no_mangle]
3149        extern "Rust" fn custom_ox_now() -> Duration {
3150            Duration::default()
3151        }
3152        DateTime::now();
3153    }
3154
3155    #[cfg(not(feature = "custom-now"))]
3156    #[test]
3157    fn now() -> Result<(), ParseDateTimeError> {
3158        let now = DateTime::now();
3159        assert!(DateTime::from_str("2022-01-01T00:00:00Z")? < now);
3160        assert!(now < DateTime::from_str("2100-01-01T00:00:00Z")?);
3161        Ok(())
3162    }
3163
3164    #[test]
3165    fn minimally_conformant() -> Result<(), ParseDateTimeError> {
3166        // All minimally conforming processors must support nonnegative year values less than 10000
3167        // (i.e., those expressible with four digits) in all datatypes which
3168        // use the seven-property model defined in The Seven-property Model (§D.2.1)
3169        // and have a non-absent value for year (i.e. dateTime, dateTimeStamp, date, gYearMonth, and gYear).
3170        assert_eq!(GYear::from_str("9999")?.to_string(), "9999");
3171        assert_eq!(
3172            DateTime::from_str("9999-12-31T23:59:59Z")?.to_string(),
3173            "9999-12-31T23:59:59Z"
3174        );
3175
3176        // All minimally conforming processors must support second values to milliseconds
3177        // (i.e. those expressible with three fraction digits) in all datatypes
3178        // which use the seven-property model defined in The Seven-property Model (§D.2.1)
3179        // and have a non-absent value for second (i.e. dateTime, dateTimeStamp, and time).
3180        assert_eq!(
3181            Time::from_str("00:00:00.678Z")?.to_string(),
3182            "00:00:00.678Z"
3183        );
3184        assert_eq!(
3185            DateTime::from_str("2000-01-01T00:00:00.678Z")?.to_string(),
3186            "2000-01-01T00:00:00.678Z"
3187        );
3188        Ok(())
3189    }
3190}