Skip to main content

anyxml_datetime/
lib.rs

1//! A simple implementation of datetime format as defined in
2//! [XML Schema Part 2: Datatypes Second Edition Appendix D ISO 8601 Date and Time Formats](https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#isoformats).
3//!
4//! I do not target full ISO 8601 format support.
5//!
6//! # BCE year notation
7//! This crate follows the older ISO 8601 referenced by XML Schema 1.0, which
8//! diverges from the BCE notation in the current ISO 8601 standard.  \
9//! In the current ISO 8601 standard, "0000" denotes the year 1 BC, and "-0001"
10//! denotes the year 2 BC. However, in the older ISO 8601 referenced by the schema
11//! specification this crate follows, "0000" is an invalid year notation, and "-0001"
12//! indicates the year 1 BC.  \
13//! Since XML Schema 1.1 now conforms to the current ISO 8601, this crate may follow
14//! the new notation in the future.
15
16use std::{
17    num::{NonZeroU64, ParseIntError},
18    ops::{Add, AddAssign, Sub, SubAssign},
19    str::FromStr,
20};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum ErrorSegment {
24    Year,
25    Month,
26    Day,
27    Hour,
28    Minute,
29    Second,
30    TimeZone,
31}
32
33impl std::fmt::Display for ErrorSegment {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            Self::Year => write!(f, "year"),
37            Self::Month => write!(f, "month"),
38            Self::Day => write!(f, "day"),
39            Self::Hour => write!(f, "hour"),
40            Self::Minute => write!(f, "minute"),
41            Self::Second => write!(f, "second"),
42            Self::TimeZone => write!(f, "timezone"),
43        }
44    }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum ErrorKind {
49    NotEnoughDigits,
50    ParseIntError(ParseIntError),
51    TooLarge,
52    TooSmall,
53    OutOfRange,
54    InvalidFormat,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct DateTimeError {
59    segment: ErrorSegment,
60    kind: ErrorKind,
61}
62
63impl std::fmt::Display for DateTimeError {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        use ErrorKind::*;
66
67        match self.kind {
68            NotEnoughDigits => write!(f, "Not enough digits in the {}.", self.segment),
69            ParseIntError(ref err) => {
70                write!(f, "Failed to parse {} because '{err}'.", self.segment)
71            }
72            TooLarge => write!(f, "The {} is too large.", self.segment),
73            TooSmall => write!(f, "The {} is too small.", self.segment),
74            InvalidFormat => write!(f, "The format of {} is invalid.", self.segment),
75            OutOfRange => write!(
76                f,
77                "The value of {} is outside the range of the domain.",
78                self.segment
79            ),
80        }
81    }
82}
83
84impl std::error::Error for DateTimeError {}
85
86macro_rules! datetime_error {
87    ( $segment:ident, $kind:expr ) => {{
88        use ErrorKind::*;
89        use ErrorSegment::*;
90        DateTimeError {
91            segment: $segment,
92            kind: $kind,
93        }
94    }};
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
98pub struct TimeZone(i16);
99
100impl TimeZone {
101    const MIN: Self = Self(-14 * 60);
102    const MAX: Self = Self(14 * 60);
103    const UTC: Self = Self(0);
104}
105
106impl std::fmt::Display for TimeZone {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        if self.0 == 0 {
109            write!(f, "Z")
110        } else {
111            let h = self.0 / 60;
112            let m = self.0 % 60;
113            write!(f, "{:+02}:{:02}", h, m.abs())
114        }
115    }
116}
117
118impl FromStr for TimeZone {
119    type Err = DateTimeError;
120
121    fn from_str(s: &str) -> Result<Self, Self::Err> {
122        if s == "Z" {
123            return Ok(Self(0));
124        }
125
126        let (hour, minute) = s
127            .split_once(':')
128            .ok_or(datetime_error!(TimeZone, InvalidFormat))?;
129        if !hour.starts_with(['+', '-'])
130            || hour.len() != 3
131            || minute.len() != 2
132            || minute.starts_with(['+', '-'])
133        {
134            return Err(datetime_error!(TimeZone, InvalidFormat))?;
135        }
136        let hour = hour
137            .parse::<i16>()
138            .map_err(|err| datetime_error!(TimeZone, ParseIntError(err)))?;
139        let minute = minute
140            .parse::<i16>()
141            .map_err(|err| datetime_error!(TimeZone, ParseIntError(err)))?;
142
143        if minute >= 60 {
144            return Err(datetime_error!(TimeZone, TooLarge));
145        }
146
147        if hour < -14 || (hour == -14 && minute != 0) {
148            Err(datetime_error!(TimeZone, TooSmall))
149        } else if hour > 14 || (hour == 14 && minute != 0) {
150            Err(datetime_error!(TimeZone, TooLarge))
151        } else {
152            Ok(Self(hour * 60 + minute * hour.signum()))
153        }
154    }
155}
156
157const NUM_OF_DAYS_IN_A_MONTH: [u8; 13] = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
160struct NaiveYear(i128);
161
162impl NaiveYear {
163    fn checked_add(&self, rhs: i128) -> Option<Self> {
164        let mut ret = self.0.checked_add(rhs)?;
165        if ret == 0 {
166            ret = 1;
167        }
168        Some(Self(ret))
169    }
170
171    fn checked_sub(&self, rhs: i128) -> Option<Self> {
172        let mut ret = self.0.checked_sub(rhs)?;
173        if ret == 0 {
174            ret = -1;
175        }
176        Some(Self(ret))
177    }
178}
179
180impl std::fmt::Display for NaiveYear {
181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182        write!(f, "{:04}", self.0)
183    }
184}
185
186impl FromStr for NaiveYear {
187    type Err = DateTimeError;
188
189    fn from_str(s: &str) -> Result<Self, Self::Err> {
190        if s.starts_with('+') {
191            return Err(datetime_error!(Year, InvalidFormat));
192        }
193        if s.len() < 4 + s.starts_with('-') as usize {
194            return Err(datetime_error!(Year, NotEnoughDigits));
195        }
196
197        let ret = s
198            .parse()
199            .map(Self)
200            .map_err(|err| datetime_error!(Year, ParseIntError(err)))?;
201        if ret.0 == 0 {
202            // '0000' is not allowed in XML Schema v1.0.
203            // '0000' is allowed in v1.1, but since only v1.0 is supported currently,
204            // this is not an issue.
205            return Err(datetime_error!(Year, OutOfRange));
206        }
207        Ok(ret)
208    }
209}
210
211macro_rules! impl_add_for_naive_year {
212    ( $( $t:ty ),* ) => {
213        $(
214            impl Add<$t> for NaiveYear {
215                type Output = NaiveYear;
216
217                fn add(self, rhs: $t) -> Self::Output {
218                    let mut ret = self.0 + rhs as i128;
219                    if ret == 0 {
220                        ret += 1;
221                    }
222                    Self(ret)
223                }
224            }
225        )*
226    };
227}
228impl_add_for_naive_year!(i8, u8, i16, u16, i32, u32, i64, u64, i128);
229macro_rules! impl_sub_for_naive_year {
230    ( $( $t:ty ),* ) => {
231        $(
232            impl Sub<$t> for NaiveYear {
233                type Output = NaiveYear;
234
235                fn sub(self, rhs: $t) -> Self::Output {
236                    let mut ret = self.0 - rhs as i128;
237                    if ret == 0 {
238                        ret -= 1;
239                    }
240                    Self(ret)
241                }
242            }
243        )*
244    };
245}
246impl_sub_for_naive_year!(i8, u8, i16, u16, i32, u32, i64, u64, i128);
247
248impl Default for NaiveYear {
249    fn default() -> Self {
250        Self(1)
251    }
252}
253
254#[derive(Debug, Clone, Copy)]
255pub struct GYear {
256    year: NaiveYear,
257    tz: Option<TimeZone>,
258}
259
260impl PartialOrd for GYear {
261    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
262        match (self.tz, other.tz) {
263            (Some(stz), Some(otz)) => match self.year.cmp(&other.year) {
264                std::cmp::Ordering::Equal => Some(stz.cmp(&otz)),
265                cmp => Some(cmp),
266            },
267            (None, None) => self.year.partial_cmp(&other.year),
268            _ => None,
269        }
270    }
271}
272
273impl std::fmt::Display for GYear {
274    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275        write!(f, "{}", self.year)?;
276        if let Some(tz) = self.tz.as_ref() {
277            write!(f, "{}", tz)?;
278        }
279        Ok(())
280    }
281}
282
283impl FromStr for GYear {
284    type Err = DateTimeError;
285
286    fn from_str(s: &str) -> Result<Self, Self::Err> {
287        let base = s.starts_with('-') as usize;
288        if let Some(sep) = s[base..].bytes().position(|b| !b.is_ascii_digit()) {
289            let (year, tz) = s.split_at(base + sep);
290            Ok(Self {
291                year: year.parse()?,
292                tz: Some(tz.parse()?),
293            })
294        } else {
295            Ok(Self {
296                year: s.parse()?,
297                tz: None,
298            })
299        }
300    }
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
304struct NaiveMonth(u8);
305
306impl std::fmt::Display for NaiveMonth {
307    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308        write!(f, "{:02}", self.0)
309    }
310}
311
312impl FromStr for NaiveMonth {
313    type Err = DateTimeError;
314
315    fn from_str(s: &str) -> Result<Self, Self::Err> {
316        let ret = s
317            .parse()
318            .map(Self)
319            .map_err(|err| datetime_error!(Month, ParseIntError(err)))?;
320        if s.starts_with('+') {
321            Err(datetime_error!(Month, InvalidFormat))
322        } else if s.len() < 2 {
323            Err(datetime_error!(Month, NotEnoughDigits))
324        } else if !(1..=12).contains(&ret.0) {
325            Err(datetime_error!(Month, OutOfRange))
326        } else {
327            Ok(ret)
328        }
329    }
330}
331
332impl Default for NaiveMonth {
333    fn default() -> Self {
334        Self(1)
335    }
336}
337
338#[derive(Debug, Clone, Copy)]
339pub struct GMonth {
340    month: NaiveMonth,
341    tz: Option<TimeZone>,
342}
343
344impl PartialOrd for GMonth {
345    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
346        match (self.tz, other.tz) {
347            (Some(stz), Some(otz)) => match self.month.cmp(&other.month) {
348                std::cmp::Ordering::Equal => Some(stz.cmp(&otz)),
349                cmp => Some(cmp),
350            },
351            (None, None) => self.month.partial_cmp(&other.month),
352            _ => None,
353        }
354    }
355}
356
357impl std::fmt::Display for GMonth {
358    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
359        write!(f, "{}", self.month)?;
360        if let Some(tz) = self.tz.as_ref() {
361            write!(f, "{}", tz)?;
362        }
363        Ok(())
364    }
365}
366
367impl FromStr for GMonth {
368    type Err = DateTimeError;
369
370    fn from_str(s: &str) -> Result<Self, Self::Err> {
371        let s = s
372            .strip_prefix("--")
373            .ok_or(datetime_error!(Year, InvalidFormat))?;
374        if let Some(sep) = s.bytes().position(|b| !b.is_ascii_digit()) {
375            let (month, tz) = s.split_at(sep);
376            Ok(Self {
377                month: month.parse()?,
378                tz: Some(tz.parse()?),
379            })
380        } else {
381            Ok(Self {
382                month: s.parse()?,
383                tz: None,
384            })
385        }
386    }
387}
388
389#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
390struct NaiveDay(u8);
391
392impl std::fmt::Display for NaiveDay {
393    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394        write!(f, "{:02}", self.0)
395    }
396}
397
398impl FromStr for NaiveDay {
399    type Err = DateTimeError;
400
401    fn from_str(s: &str) -> Result<Self, Self::Err> {
402        let ret = s
403            .parse()
404            .map(Self)
405            .map_err(|err| datetime_error!(Day, ParseIntError(err)))?;
406        if s.starts_with('+') {
407            Err(datetime_error!(Day, InvalidFormat))
408        } else if s.len() < 2 {
409            Err(datetime_error!(Day, NotEnoughDigits))
410        } else if !(1..=31).contains(&ret.0) {
411            Err(datetime_error!(Day, OutOfRange))
412        } else {
413            Ok(ret)
414        }
415    }
416}
417
418impl Default for NaiveDay {
419    fn default() -> Self {
420        Self(1)
421    }
422}
423
424#[derive(Debug, Clone, Copy)]
425pub struct GDay {
426    day: NaiveDay,
427    tz: Option<TimeZone>,
428}
429
430impl PartialOrd for GDay {
431    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
432        let year = NaiveYear(2015);
433        // This must be any month with 31 days.
434        let month = NaiveMonth(5);
435        let time = NaiveTime {
436            hour: 0,
437            minute: 0,
438            nanosecond: 0,
439        };
440
441        DateTime {
442            datetime: NaiveDateTime {
443                date: NaiveDate {
444                    year,
445                    month,
446                    day: self.day,
447                },
448                time,
449            },
450            tz: self.tz,
451        }
452        .partial_cmp(&DateTime {
453            datetime: NaiveDateTime {
454                date: NaiveDate {
455                    year,
456                    month,
457                    day: other.day,
458                },
459                time,
460            },
461            tz: other.tz,
462        })
463    }
464}
465
466impl std::fmt::Display for GDay {
467    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
468        write!(f, "{}", self.day)?;
469        if let Some(tz) = self.tz.as_ref() {
470            write!(f, "{}", tz)?;
471        }
472        Ok(())
473    }
474}
475
476impl FromStr for GDay {
477    type Err = DateTimeError;
478
479    fn from_str(s: &str) -> Result<Self, Self::Err> {
480        let s = s
481            .strip_prefix("---")
482            .ok_or(datetime_error!(Year, InvalidFormat))?;
483        if let Some(sep) = s.bytes().position(|b| !b.is_ascii_digit()) {
484            let (day, tz) = s.split_at(sep);
485            Ok(Self {
486                day: day.parse()?,
487                tz: Some(tz.parse()?),
488            })
489        } else {
490            Ok(Self {
491                day: s.parse()?,
492                tz: None,
493            })
494        }
495    }
496}
497
498#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
499struct NaiveYearMonth {
500    year: NaiveYear,
501    month: NaiveMonth,
502}
503
504impl std::fmt::Display for NaiveYearMonth {
505    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
506        write!(f, "{}-{}", self.year, self.month)
507    }
508}
509
510impl FromStr for NaiveYearMonth {
511    type Err = DateTimeError;
512
513    fn from_str(s: &str) -> Result<Self, Self::Err> {
514        let (year, month) = s
515            .rsplit_once('-')
516            .ok_or(datetime_error!(Year, InvalidFormat))?;
517        Ok(Self {
518            year: year.parse()?,
519            month: month.parse()?,
520        })
521    }
522}
523
524#[derive(Debug, Clone, Copy)]
525pub struct GYearMonth {
526    ym: NaiveYearMonth,
527    tz: Option<TimeZone>,
528}
529
530impl PartialOrd for GYearMonth {
531    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
532        match (self.tz, other.tz) {
533            (Some(stz), Some(otz)) => match self.ym.cmp(&other.ym) {
534                std::cmp::Ordering::Equal => Some(stz.cmp(&otz)),
535                cmp => Some(cmp),
536            },
537            (None, None) => self.ym.partial_cmp(&other.ym),
538            _ => None,
539        }
540    }
541}
542
543impl std::fmt::Display for GYearMonth {
544    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
545        write!(f, "{}", self.ym)?;
546        if let Some(tz) = self.tz.as_ref() {
547            write!(f, "{}", tz)?;
548        }
549        Ok(())
550    }
551}
552
553impl FromStr for GYearMonth {
554    type Err = DateTimeError;
555
556    fn from_str(s: &str) -> Result<Self, Self::Err> {
557        if s.len() < 7 {
558            // CCYY-MM
559            Err(datetime_error!(Year, InvalidFormat))
560        } else if s.ends_with('Z') {
561            let (ym, tz) = s.split_at(s.len() - 1);
562            Ok(Self {
563                ym: ym.parse()?,
564                tz: Some(tz.parse()?),
565            })
566        } else if let (ym, tz) = s.split_at(s.len() - 6)
567            && let Ok(tz) = tz.parse()
568            && let Ok(ym) = ym.parse()
569        {
570            Ok(Self { ym, tz: Some(tz) })
571        } else {
572            Ok(Self {
573                ym: s.parse()?,
574                tz: None,
575            })
576        }
577    }
578}
579
580#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
581struct NaiveMonthDay {
582    month: NaiveMonth,
583    day: NaiveDay,
584}
585
586impl std::fmt::Display for NaiveMonthDay {
587    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
588        write!(f, "{}-{}", self.month, self.day)
589    }
590}
591
592impl FromStr for NaiveMonthDay {
593    type Err = DateTimeError;
594
595    fn from_str(s: &str) -> Result<Self, Self::Err> {
596        let (month, day) = s
597            .split_once('-')
598            .ok_or(datetime_error!(Month, InvalidFormat))?;
599        let ret = Self {
600            month: month.parse()?,
601            day: day.parse()?,
602        };
603        if NUM_OF_DAYS_IN_A_MONTH[ret.month.0 as usize] < ret.day.0 {
604            return Err(datetime_error!(Day, TooLarge));
605        }
606        Ok(ret)
607    }
608}
609
610#[derive(Debug, Clone, Copy)]
611pub struct GMonthDay {
612    md: NaiveMonthDay,
613    tz: Option<TimeZone>,
614}
615
616impl PartialOrd for GMonthDay {
617    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
618        // This must be an arbitrary leap year.
619        let year = NaiveYear(2000);
620        let time = NaiveTime {
621            hour: 0,
622            minute: 0,
623            nanosecond: 0,
624        };
625
626        DateTime {
627            datetime: NaiveDateTime {
628                date: NaiveDate {
629                    year,
630                    month: self.md.month,
631                    day: self.md.day,
632                },
633                time,
634            },
635            tz: self.tz,
636        }
637        .partial_cmp(&DateTime {
638            datetime: NaiveDateTime {
639                date: NaiveDate {
640                    year,
641                    month: other.md.month,
642                    day: other.md.day,
643                },
644                time,
645            },
646            tz: other.tz,
647        })
648    }
649}
650
651impl std::fmt::Display for GMonthDay {
652    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
653        write!(f, "{}", self.md)?;
654        if let Some(tz) = self.tz.as_ref() {
655            write!(f, "{}", tz)?;
656        }
657        Ok(())
658    }
659}
660
661impl FromStr for GMonthDay {
662    type Err = DateTimeError;
663
664    fn from_str(s: &str) -> Result<Self, Self::Err> {
665        let s = s
666            .strip_prefix("--")
667            .ok_or(datetime_error!(Year, InvalidFormat))?;
668        if s.len() < 5 {
669            // MM-DD
670            Err(datetime_error!(Month, InvalidFormat))
671        } else if let Some(md) = s.strip_suffix('Z') {
672            Ok(Self {
673                md: md.parse()?,
674                tz: Some("Z".parse()?),
675            })
676        } else if s.len() >= 6
677            && let (md, tz) = s.split_at(s.len() - 6)
678            && let Ok(tz) = tz.parse()
679            && let Ok(md) = md.parse()
680        {
681            Ok(Self { md, tz: Some(tz) })
682        } else {
683            Ok(Self {
684                md: s.parse()?,
685                tz: None,
686            })
687        }
688    }
689}
690
691#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
692struct NaiveDate {
693    year: NaiveYear,
694    month: NaiveMonth,
695    day: NaiveDay,
696}
697
698impl std::fmt::Display for NaiveDate {
699    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
700        write!(f, "{}-{}-{}", self.year, self.month, self.day)
701    }
702}
703
704impl FromStr for NaiveDate {
705    type Err = DateTimeError;
706
707    fn from_str(s: &str) -> Result<Self, Self::Err> {
708        let len = s.len();
709        if len < 10 {
710            // CCYY-MM-DD
711            return Err(datetime_error!(Year, InvalidFormat));
712        }
713        let (year, md) = s.split_at(len - 5);
714        let md = md.parse::<NaiveMonthDay>()?;
715        let year = year
716            .strip_suffix('-')
717            .ok_or(datetime_error!(Year, InvalidFormat))?;
718        let year = year.parse::<NaiveYear>()?;
719        if !is_leap(year) && md.month.0 == 2 && md.day.0 == 29 {
720            return Err(datetime_error!(Day, TooLarge));
721        }
722        Ok(Self {
723            year,
724            month: md.month,
725            day: md.day,
726        })
727    }
728}
729
730#[derive(Debug, Clone, Copy)]
731pub struct Date {
732    ymd: NaiveDate,
733    tz: Option<TimeZone>,
734}
735
736impl PartialOrd for Date {
737    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
738        let time = NaiveTime {
739            hour: 0,
740            minute: 0,
741            nanosecond: 0,
742        };
743        DateTime {
744            datetime: NaiveDateTime {
745                date: self.ymd,
746                time,
747            },
748            tz: self.tz,
749        }
750        .partial_cmp(&DateTime {
751            datetime: NaiveDateTime {
752                date: other.ymd,
753                time,
754            },
755            tz: other.tz,
756        })
757    }
758}
759
760impl std::fmt::Display for Date {
761    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
762        write!(f, "{}", self.ymd)?;
763        if let Some(tz) = self.tz.as_ref() {
764            write!(f, "{}", tz)?;
765        }
766        Ok(())
767    }
768}
769
770impl FromStr for Date {
771    type Err = DateTimeError;
772
773    fn from_str(s: &str) -> Result<Self, Self::Err> {
774        if s.len() < 10 {
775            // CCYY-MM-DD
776            Err(datetime_error!(Month, InvalidFormat))
777        } else if let Some(ymd) = s.strip_suffix('Z') {
778            Ok(Self {
779                ymd: ymd.parse()?,
780                tz: Some("Z".parse()?),
781            })
782        } else if let (ymd, tz) = s.split_at(s.len() - 6)
783            && let Ok(tz) = tz.parse()
784            && let Ok(ymd) = ymd.parse()
785        {
786            Ok(Self { ymd, tz: Some(tz) })
787        } else {
788            Ok(Self {
789                ymd: s.parse()?,
790                tz: None,
791            })
792        }
793    }
794}
795
796/// generated by anyxml/resources/generate-leapsecond-list.ts
797///
798/// # Reference
799/// https://data.iana.org/time-zones/tzdb/leap-seconds.list
800const INSERTED_LEAPSECONDS: &[(u16, u8, u8)] = include!("../resources/inserted-leapseconds.rs");
801const REMOVED_LEAPSECONDS: &[(u16, u8, u8)] = include!("../resources/removed-leapseconds.rs");
802
803#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
804struct NaiveTime {
805    hour: u8,
806    minute: u8,
807    // support down to the nanosecond level
808    nanosecond: u64,
809}
810
811impl std::fmt::Display for NaiveTime {
812    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
813        let second = self.nanosecond / NANOSECONDS_PER_SECOND;
814        let mut nano = self.nanosecond % NANOSECONDS_PER_SECOND;
815        write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, second)?;
816        if nano != 0 {
817            while nano % 10 == 0 {
818                nano /= 10;
819            }
820            write!(f, ".{}", nano)?;
821        }
822        Ok(())
823    }
824}
825
826impl FromStr for NaiveTime {
827    type Err = DateTimeError;
828
829    fn from_str(s: &str) -> Result<Self, Self::Err> {
830        if s.len() < 8 {
831            // hh:mm:ss
832            return Err(datetime_error!(Hour, InvalidFormat));
833        }
834
835        macro_rules! parse_number {
836            ( $s:expr, $seg:ident, $max:expr ) => {{
837                let num = $s
838                    .parse()
839                    .map_err(|err| datetime_error!($seg, ParseIntError(err)))?;
840                if $s.starts_with('+') {
841                    return Err(datetime_error!($seg, InvalidFormat));
842                }
843                if num > $max {
844                    return Err(datetime_error!($seg, TooLarge));
845                }
846                num
847            }};
848        }
849
850        let (sh, ms) = s.split_at(2);
851        let hour = parse_number!(sh, Hour, 24);
852        let (sm, second) = ms
853            .strip_prefix(':')
854            .ok_or(datetime_error!(Hour, InvalidFormat))?
855            .split_at(2);
856        let minute = parse_number!(sm, Minute, 59);
857        let (ss, nano) = second
858            .strip_prefix(':')
859            .ok_or(datetime_error!(Minute, InvalidFormat))?
860            .split_at(2);
861        let second: u64 = parse_number!(ss, Second, 60);
862        let ret = if nano.is_empty() {
863            Self {
864                hour,
865                minute,
866                nanosecond: second * NANOSECONDS_PER_SECOND,
867            }
868        } else if nano.len() == 1 {
869            // If a decimal point is present, at least one digit must follow it;
870            // lengths less than 4 are invalid.
871            return Err(datetime_error!(Second, InvalidFormat));
872        } else {
873            let sn = nano
874                .strip_prefix('.')
875                .ok_or(datetime_error!(Second, InvalidFormat))?;
876            // It only supports up to nanoseconds,
877            // so it does not handle values with more than 9 digits.
878            let mut nano: u64 = parse_number!(sn, Second, 999_999_999);
879            let base = 9 - sn.len();
880            nano *= 10u64.pow(base as u32);
881            Self {
882                hour,
883                minute,
884                nanosecond: second * NANOSECONDS_PER_SECOND + nano,
885            }
886        };
887
888        if ret.hour == 24 && (ret.minute != 0 || ret.nanosecond != 0) {
889            return Err(datetime_error!(Hour, OutOfRange));
890        }
891        if ret.nanosecond == 60 * NANOSECONDS_PER_SECOND && (ret.hour != 23 || ret.minute != 59) {
892            return Err(datetime_error!(Second, OutOfRange));
893        }
894
895        Ok(ret)
896    }
897}
898
899#[derive(Debug, Clone, Copy)]
900pub struct Time {
901    time: NaiveTime,
902    tz: Option<TimeZone>,
903}
904
905impl PartialOrd for Time {
906    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
907        let date = NaiveDate {
908            year: NaiveYear(2015),
909            month: NaiveMonth(5),
910            day: NaiveDay(15),
911        };
912
913        DateTime {
914            datetime: NaiveDateTime {
915                date,
916                time: self.time,
917            },
918            tz: self.tz,
919        }
920        .partial_cmp(&DateTime {
921            datetime: NaiveDateTime {
922                date,
923                time: other.time,
924            },
925            tz: other.tz,
926        })
927    }
928}
929
930impl std::fmt::Display for Time {
931    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
932        write!(f, "{}", self.time)?;
933        if let Some(tz) = self.tz.as_ref() {
934            write!(f, "{}", tz)?;
935        }
936        Ok(())
937    }
938}
939
940impl FromStr for Time {
941    type Err = DateTimeError;
942
943    fn from_str(s: &str) -> Result<Self, Self::Err> {
944        if let Some(time) = s.strip_suffix('Z') {
945            Ok(Self {
946                time: time.parse()?,
947                tz: Some("Z".parse()?),
948            })
949        } else if s.len() >= 14 && matches!(s.as_bytes()[s.len() - 6], b'+' | b'-') {
950            // hh:mm:ss+zz:zz
951            let (time, tz) = s.split_at(s.len() - 6);
952            Ok(Self {
953                time: time.parse()?,
954                tz: Some(tz.parse()?),
955            })
956        } else {
957            Ok(Self {
958                time: s.parse()?,
959                tz: None,
960            })
961        }
962    }
963}
964
965#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
966struct NaiveDateTime {
967    date: NaiveDate,
968    time: NaiveTime,
969}
970
971impl std::fmt::Display for NaiveDateTime {
972    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
973        write!(f, "{}T{}", self.date, self.time)
974    }
975}
976
977impl FromStr for NaiveDateTime {
978    type Err = DateTimeError;
979
980    fn from_str(s: &str) -> Result<Self, Self::Err> {
981        if s.len() < 19 {
982            // CCYY-MM-DDThh:mm:ss
983            return Err(datetime_error!(Year, InvalidFormat));
984        }
985
986        let (date, time) = s
987            .split_once('T')
988            .ok_or(datetime_error!(Year, InvalidFormat))?;
989        let ret = Self {
990            date: date.parse()?,
991            time: time.parse()?,
992        };
993
994        if ret.time.nanosecond == 60 * NANOSECONDS_PER_SECOND {
995            // leap second (insertion)
996            if let Ok(year) = u16::try_from(ret.date.year.0)
997                && INSERTED_LEAPSECONDS
998                    .binary_search(&(year, ret.date.month.0, ret.date.day.0))
999                    .is_err()
1000            {
1001                return Err(datetime_error!(Second, OutOfRange));
1002            }
1003        } else if ret.time.nanosecond == 59 * NANOSECONDS_PER_SECOND {
1004            // leap second (removal)
1005            if let Ok(year) = u16::try_from(ret.date.year.0)
1006                && REMOVED_LEAPSECONDS
1007                    .binary_search(&(year, ret.date.month.0, ret.date.day.0))
1008                    .is_ok()
1009            {
1010                return Err(datetime_error!(Second, OutOfRange));
1011            }
1012        }
1013
1014        Ok(ret)
1015    }
1016}
1017
1018#[derive(Debug, Clone, Copy)]
1019pub struct DateTime {
1020    datetime: NaiveDateTime,
1021    tz: Option<TimeZone>,
1022}
1023
1024impl DateTime {
1025    /// # Safety
1026    /// The datetime must be valid.
1027    ///
1028    /// Note that some values may be calculated assuming a strictly limited range,
1029    /// which could lead to unexpected results.
1030    pub const unsafe fn new_unchecked(
1031        year: i128,
1032        month: u8,
1033        day: u8,
1034        hour: u8,
1035        minute: u8,
1036        nanosecond: u64,
1037        tz: Option<TimeZone>,
1038    ) -> Self {
1039        DateTime {
1040            datetime: NaiveDateTime {
1041                date: NaiveDate {
1042                    year: NaiveYear(year),
1043                    month: NaiveMonth(month),
1044                    day: NaiveDay(day),
1045                },
1046                time: NaiveTime {
1047                    hour,
1048                    minute,
1049                    nanosecond,
1050                },
1051            },
1052            tz,
1053        }
1054    }
1055
1056    /// # Reference
1057    /// - [E Adding durations to dateTimes](https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#adding-durations-to-dateTimes)
1058    pub fn checked_add(&self, rhs: Duration) -> Option<DateTime> {
1059        if rhs.neg {
1060            return self.checked_sub(Duration { neg: false, ..rhs });
1061        }
1062
1063        let mut ret = DateTime {
1064            datetime: NaiveDateTime::default(),
1065            tz: None,
1066        };
1067
1068        // Months (may be modified additionally below)
1069        //  - temp      := S[month] + D[month]
1070        //  - E[month]  := modulo(temp, 1, 13)
1071        //  - carry     := fQuotient(temp, 1, 13)
1072        let temp = rhs
1073            .month
1074            .map(|m| m.get())
1075            .unwrap_or(0)
1076            .checked_add(self.datetime.date.month.0 as u64)?;
1077        ret.datetime.date.month.0 = ((temp - 1) % 12 + 1) as u8;
1078        let carry = (temp - 1) / 12;
1079
1080        // Years (may be modified additionally below)
1081        //  - E[year]   := S[year] + D[year] + carry
1082        ret.datetime.date.year = self
1083            .datetime
1084            .date
1085            .year
1086            .checked_add(rhs.year.map(|y| y.get()).unwrap_or(0) as i128)?
1087            .checked_add(carry as i128)?;
1088
1089        // Zone
1090        //  - E[zone]   := S[zone]
1091        ret.tz = self.tz;
1092
1093        // Seconds
1094        //  - temp      := S[second] + D[second]
1095        //  - E[second] := modulo(temp, 60)
1096        //  - carry     := fQuotient(temp, 60)
1097        let temp = rhs
1098            .nanosecond
1099            .map(|n| n.get())
1100            .unwrap_or(0)
1101            .checked_add(self.datetime.time.nanosecond)?;
1102        ret.datetime.time.nanosecond = temp % (60 * NANOSECONDS_PER_SECOND);
1103        let carry = temp / (60 * NANOSECONDS_PER_SECOND);
1104
1105        // Minutes
1106        //  - temp      := S[minute] + D[minute] + carry
1107        //  - E[minute] := modulo(temp, 60)
1108        //  - carry     := fQuotient(temp, 60)
1109        let temp = rhs
1110            .minute
1111            .map(|m| m.get())
1112            .unwrap_or(0)
1113            .checked_add(self.datetime.time.minute as u64)?
1114            .checked_add(carry)?;
1115        ret.datetime.time.minute = (temp % 60) as u8;
1116        let carry = temp / 60;
1117
1118        // Hours
1119        //  - temp      := S[hour] + D[hour] + carry
1120        //  - E[hour]   := modulo(temp, 24)
1121        //  - carry     := fQuotient(temp, 24)
1122        let temp = rhs
1123            .hour
1124            .map(|h| h.get())
1125            .unwrap_or(0)
1126            .checked_add(self.datetime.time.hour as u64)?
1127            .checked_add(carry)?;
1128        ret.datetime.time.hour = (temp % 24) as u8;
1129        let carry = temp / 24;
1130
1131        // Days
1132        //  - if S[day] > maximumDayInMonthFor(E[year], E[month])
1133        //      - tempDays  := maximumDayInMonthFor(E[year], E[month])
1134        //  - else if S[day] < 1
1135        //      - tempDays  := 1
1136        //  - else
1137        //      - tempDays  := S[day]
1138        let temp_days = self.datetime.date.day.0.clamp(
1139            1,
1140            maximum_day_in_month_for(ret.datetime.date.year, ret.datetime.date.month.0 as i8)?,
1141        );
1142        //  - E[day]    := tempDays + D[day] + carry
1143        let mut day = rhs
1144            .day
1145            .map(|d| d.get())
1146            .unwrap_or(0)
1147            .checked_add(temp_days as u64)?
1148            .checked_add(carry)?;
1149        //  - START LOOP
1150        //      - IF E[day] < 1
1151        //            E[day]        := E[day] + maximumDayInMonthFor(E[year], E[month] - 1)
1152        //            carry         := -1
1153        //      - ELSE IF E[day] > maximumDayInMonthFor(E[year], E[month])
1154        //            E[day]        := E[day] - maximumDayInMonthFor(E[year], E[month])
1155        //            carry         := 1
1156        //      - ELSE EXIT LOOP
1157        //      - temp      := E[month] + carry
1158        //      - E[month]  := modulo(temp, 1, 13)
1159        //      - E[year]   := E[year] + fQuotient(temp, 1, 13)
1160        //      - GOTO START LOOP
1161
1162        // The original loop-based calculation is too slow, so I will change the approach.
1163        //
1164        // First, advance the year in units of 365*400+100-4+1 days.
1165        const NUM_OF_DAYS_OVER_400YEARS: u64 = 365 * 400 + 100 - 4 + 1;
1166        ret.datetime.date.year = ret
1167            .datetime
1168            .date
1169            .year
1170            .checked_add((day / NUM_OF_DAYS_OVER_400YEARS) as i128)?;
1171        day %= NUM_OF_DAYS_OVER_400YEARS;
1172        // Next, advance the year in units of 100 years.
1173        const NUM_OF_DAYS_OVER_100YEARS: u64 = 365 * 100 + 25 - 1;
1174        ret.datetime.date.year = ret
1175            .datetime
1176            .date
1177            .year
1178            .checked_add((day / NUM_OF_DAYS_OVER_100YEARS) as i128)?;
1179        day %= NUM_OF_DAYS_OVER_100YEARS;
1180        // Next, advance the year in units of 4 years.
1181        const NUM_OF_DAYS_OVER_4YEARS: u64 = 365 * 4 + 1;
1182        ret.datetime.date.year = ret
1183            .datetime
1184            .date
1185            .year
1186            .checked_add((day / NUM_OF_DAYS_OVER_4YEARS) as i128)?;
1187        day %= NUM_OF_DAYS_OVER_4YEARS;
1188        // Finally, it is determined using a loop method.It should take no more than 50 iterations.
1189        // While advancing one year at a time is possible, handling boundary cases is complex,
1190        // so I determine the last four years using a loop.
1191        loop {
1192            let temp = if day < 1 {
1193                let month = ret.datetime.date.month.0 as i8 - 1;
1194                day = day
1195                    .checked_add(maximum_day_in_month_for(ret.datetime.date.year, month)? as u64)?;
1196                month
1197            } else if let max_days =
1198                maximum_day_in_month_for(ret.datetime.date.year, ret.datetime.date.month.0 as i8)?
1199                    as u64
1200                && day > max_days
1201            {
1202                day -= max_days;
1203                ret.datetime.date.month.0 as i8 + 1
1204            } else {
1205                break;
1206            };
1207
1208            ret.datetime.date.month.0 = (temp - 1).rem_euclid(12) as u8 + 1;
1209            ret.datetime.date.year = ret
1210                .datetime
1211                .date
1212                .year
1213                .checked_add((temp - 1).div_euclid(12) as i128)?;
1214        }
1215        ret.datetime.date.day.0 = day as u8;
1216
1217        Some(ret)
1218    }
1219
1220    /// # Reference
1221    /// - [E Adding durations to dateTimes](https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#adding-durations-to-dateTimes)
1222    pub fn checked_sub(&self, rhs: Duration) -> Option<DateTime> {
1223        if rhs.neg {
1224            return self.checked_add(Duration { neg: false, ..rhs });
1225        }
1226
1227        let mut ret = DateTime {
1228            datetime: NaiveDateTime::default(),
1229            tz: None,
1230        };
1231
1232        // Months (may be modified additionally below)
1233        //  - temp      := S[month] + D[month]
1234        //  - E[month]  := modulo(temp, 1, 13)
1235        //  - carry     := fQuotient(temp, 1, 13)
1236        let temp = (self.datetime.date.month.0 as i128)
1237            .checked_sub(rhs.month.map(|m| m.get() as i128).unwrap_or(0))?;
1238        ret.datetime.date.month.0 = temp.checked_sub(1)?.rem_euclid(12) as u8 + 1;
1239        let carry = (temp - 1).div_euclid(12);
1240
1241        // Years (may be modified additionally below)
1242        //  - E[year]   := S[year] + D[year] + carry
1243        ret.datetime.date.year = self
1244            .datetime
1245            .date
1246            .year
1247            .checked_sub(rhs.year.map(|y| y.get()).unwrap_or(0) as i128)?
1248            .checked_add(carry)?;
1249
1250        // Zone
1251        //  - E[zone]   := S[zone]
1252        ret.tz = self.tz;
1253
1254        // Seconds
1255        //  - temp      := S[second] + D[second]
1256        //  - E[second] := modulo(temp, 60)
1257        //  - carry     := fQuotient(temp, 60)
1258        let temp = (self.datetime.time.nanosecond as i128)
1259            .checked_sub(rhs.nanosecond.map(|n| n.get()).unwrap_or(0) as i128)?;
1260        ret.datetime.time.nanosecond =
1261            temp.rem_euclid((60 * NANOSECONDS_PER_SECOND) as i128) as u64;
1262        let carry = temp.div_euclid((60 * NANOSECONDS_PER_SECOND) as i128);
1263
1264        // Minutes
1265        //  - temp      := S[minute] + D[minute] + carry
1266        //  - E[minute] := modulo(temp, 60)
1267        //  - carry     := fQuotient(temp, 60)
1268        let temp = (self.datetime.time.minute as i128)
1269            .checked_sub(rhs.minute.map(|m| m.get()).unwrap_or(0) as i128)?
1270            .checked_add(carry)?;
1271        ret.datetime.time.minute = temp.rem_euclid(60) as u8;
1272        let carry = temp.div_euclid(60);
1273
1274        // Hours
1275        //  - temp      := S[hour] + D[hour] + carry
1276        //  - E[hour]   := modulo(temp, 24)
1277        //  - carry     := fQuotient(temp, 24)
1278        let temp = (self.datetime.time.hour as i128)
1279            .checked_sub(rhs.hour.map(|h| h.get()).unwrap_or(0) as i128)?
1280            .checked_add(carry)?;
1281        ret.datetime.time.hour = temp.rem_euclid(24) as u8;
1282        let carry = temp.div_euclid(24);
1283
1284        // Days
1285        //  - if S[day] > maximumDayInMonthFor(E[year], E[month])
1286        //      - tempDays  := maximumDayInMonthFor(E[year], E[month])
1287        //  - else if S[day] < 1
1288        //      - tempDays  := 1
1289        //  - else
1290        //      - tempDays  := S[day]
1291        let temp_days = self.datetime.date.day.0.clamp(
1292            1,
1293            maximum_day_in_month_for(ret.datetime.date.year, ret.datetime.date.month.0 as i8)?,
1294        );
1295        //  - E[day]    := tempDays + D[day] + carry
1296        let mut day = (temp_days as i128)
1297            .checked_sub(rhs.day.map(|d| d.get()).unwrap_or(0) as i128)?
1298            .checked_add(carry)?;
1299        //  - START LOOP
1300        //      - IF E[day] < 1
1301        //            E[day]        := E[day] + maximumDayInMonthFor(E[year], E[month] - 1)
1302        //            carry         := -1
1303        //      - ELSE IF E[day] > maximumDayInMonthFor(E[year], E[month])
1304        //            E[day]        := E[day] - maximumDayInMonthFor(E[year], E[month])
1305        //            carry         := 1
1306        //      - ELSE EXIT LOOP
1307        //      - temp      := E[month] + carry
1308        //      - E[month]  := modulo(temp, 1, 13)
1309        //      - E[year]   := E[year] + fQuotient(temp, 1, 13)
1310        //      - GOTO START LOOP
1311
1312        // The original loop-based calculation is too slow, so I will change the approach.
1313        //
1314        // First, advance the year in units of 365*400+100-4+1 days.
1315        const NUM_OF_DAYS_OVER_400YEARS: i128 = 365 * 400 + 100 - 4 + 1;
1316        ret.datetime.date.year = ret
1317            .datetime
1318            .date
1319            .year
1320            .checked_add(day / NUM_OF_DAYS_OVER_400YEARS)?;
1321        day %= NUM_OF_DAYS_OVER_400YEARS;
1322        // Next, advance the year in units of 100 years.
1323        const NUM_OF_DAYS_OVER_100YEARS: i128 = 365 * 100 + 25 - 1;
1324        ret.datetime.date.year = ret
1325            .datetime
1326            .date
1327            .year
1328            .checked_add(day / NUM_OF_DAYS_OVER_100YEARS)?;
1329        day %= NUM_OF_DAYS_OVER_100YEARS;
1330        // Next, advance the year in units of 4 years.
1331        const NUM_OF_DAYS_OVER_4YEARS: i128 = 365 * 4 + 1;
1332        ret.datetime.date.year = ret
1333            .datetime
1334            .date
1335            .year
1336            .checked_add(day / NUM_OF_DAYS_OVER_4YEARS)?;
1337        day %= NUM_OF_DAYS_OVER_4YEARS;
1338        // Finally, it is determined using a loop method.It should take no more than 50 iterations.
1339        // While advancing one year at a time is possible, handling boundary cases is complex,
1340        // so I determine the last four years using a loop.
1341        loop {
1342            let temp = if day < 1 {
1343                let month = ret.datetime.date.month.0 as i8 - 1;
1344                day = day
1345                    .checked_add(maximum_day_in_month_for(ret.datetime.date.year, month)? as i128)?;
1346                month
1347            } else if let max_days =
1348                maximum_day_in_month_for(ret.datetime.date.year, ret.datetime.date.month.0 as i8)?
1349                    as i128
1350                && day > max_days
1351            {
1352                day -= max_days;
1353                ret.datetime.date.month.0 as i8 + 1
1354            } else {
1355                break;
1356            };
1357
1358            ret.datetime.date.month.0 = (temp - 1).rem_euclid(12) as u8 + 1;
1359            ret.datetime.date.year = ret
1360                .datetime
1361                .date
1362                .year
1363                .checked_add((temp - 1).div_euclid(12) as i128)?;
1364        }
1365        ret.datetime.date.day.0 = day as u8;
1366
1367        Some(ret)
1368    }
1369
1370    /// If a time zone is set, change it to UTC. If no time zone is set, do nothing.
1371    pub fn to_utc(&self) -> DateTime {
1372        let Some(tz) = self.tz else {
1373            return *self;
1374        };
1375
1376        // self is already set to UTC
1377        if tz.0 == 0 {
1378            return *self;
1379        }
1380
1381        let h = tz.0 / 60;
1382        let m = tz.0 % 60;
1383        let duration = Duration {
1384            neg: tz.0 > 0,
1385            hour: NonZeroU64::new(h.unsigned_abs() as u64),
1386            minute: NonZeroU64::new(m.unsigned_abs() as u64),
1387            ..Default::default()
1388        };
1389
1390        DateTime {
1391            tz: Some(TimeZone(0)),
1392            ..*self + duration
1393        }
1394    }
1395}
1396
1397impl PartialOrd for DateTime {
1398    /// # Reference
1399    /// - [3.2.7.4 Order relation on dateTime](https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#dateTime)
1400    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1401        let lhs = self.to_utc();
1402        let rhs = other.to_utc();
1403
1404        if lhs.tz.is_some() == rhs.tz.is_some() {
1405            Some(lhs.datetime.cmp(&rhs.datetime))
1406        } else if lhs.tz.is_some() {
1407            let min = DateTime {
1408                datetime: rhs.datetime,
1409                tz: Some(TimeZone::MAX),
1410            };
1411            let max = DateTime {
1412                datetime: rhs.datetime,
1413                tz: Some(TimeZone::MIN),
1414            };
1415            if lhs < min {
1416                Some(std::cmp::Ordering::Less)
1417            } else if max < lhs {
1418                Some(std::cmp::Ordering::Greater)
1419            } else {
1420                None
1421            }
1422        } else {
1423            let min = DateTime {
1424                datetime: lhs.datetime,
1425                tz: Some(TimeZone::MAX),
1426            };
1427            let max = DateTime {
1428                datetime: lhs.datetime,
1429                tz: Some(TimeZone::MIN),
1430            };
1431            if max < rhs {
1432                Some(std::cmp::Ordering::Less)
1433            } else if rhs < min {
1434                Some(std::cmp::Ordering::Greater)
1435            } else {
1436                None
1437            }
1438        }
1439    }
1440}
1441
1442impl std::fmt::Display for DateTime {
1443    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1444        write!(f, "{}", self.datetime)?;
1445        if let Some(tz) = self.tz.as_ref() {
1446            write!(f, "{}", tz)?;
1447        }
1448        Ok(())
1449    }
1450}
1451
1452impl FromStr for DateTime {
1453    type Err = DateTimeError;
1454
1455    fn from_str(s: &str) -> Result<Self, Self::Err> {
1456        if let Some(datetime) = s.strip_suffix('Z') {
1457            Ok(Self {
1458                datetime: datetime.parse()?,
1459                tz: Some("Z".parse()?),
1460            })
1461        } else if s.len() >= 25 && matches!(s.as_bytes()[s.len() - 6], b'+' | b'-') {
1462            // CCYY-MM-DDThh:mm:ss+zz:zz
1463            let (datetime, tz) = s.split_at(s.len() - 6);
1464            Ok(Self {
1465                datetime: datetime.parse()?,
1466                tz: Some(tz.parse()?),
1467            })
1468        } else {
1469            Ok(Self {
1470                datetime: s.parse()?,
1471                tz: None,
1472            })
1473        }
1474    }
1475}
1476
1477impl Add<Duration> for DateTime {
1478    type Output = DateTime;
1479
1480    fn add(self, rhs: Duration) -> Self::Output {
1481        self.checked_add(rhs).expect("attempt to add with overflow")
1482    }
1483}
1484
1485impl AddAssign<Duration> for DateTime {
1486    fn add_assign(&mut self, rhs: Duration) {
1487        *self = *self + rhs;
1488    }
1489}
1490
1491impl Sub<Duration> for DateTime {
1492    type Output = DateTime;
1493
1494    fn sub(self, rhs: Duration) -> Self::Output {
1495        self.checked_sub(rhs)
1496            .expect("attempt to subtract with overflow")
1497    }
1498}
1499
1500impl SubAssign<Duration> for DateTime {
1501    fn sub_assign(&mut self, rhs: Duration) {
1502        *self = *self - rhs;
1503    }
1504}
1505
1506const NANOSECONDS_PER_SECOND: u64 = 1_000_000_000;
1507
1508#[derive(Debug, Clone, Copy, Default)]
1509pub struct Duration {
1510    neg: bool,
1511    year: Option<NonZeroU64>,
1512    month: Option<NonZeroU64>,
1513    day: Option<NonZeroU64>,
1514    hour: Option<NonZeroU64>,
1515    minute: Option<NonZeroU64>,
1516    nanosecond: Option<NonZeroU64>,
1517}
1518
1519impl PartialOrd for Duration {
1520    /// # Reference
1521    /// - [3.2.6.2 Order relation on duration](https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#duration)
1522    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1523        // Since the suggested optimization method is not well understood,
1524        // comparisons will be made based on the baseline dates provided
1525        // in the specifications unless critical performance degradation occurs.
1526
1527        // 1696-09-01T00:00:00Z
1528        // 1697-02-01T00:00:00Z
1529        // 1903-03-01T00:00:00Z
1530        // 1903-07-01T00:00:00Z
1531        const BASELINE: [DateTime; 4] = unsafe {
1532            // # Safety
1533            // The datetimes specified in the specification,
1534            // and also datetimes whose existence is obvious.
1535            [
1536                DateTime::new_unchecked(1696, 9, 1, 0, 0, 0, Some(TimeZone::UTC)),
1537                DateTime::new_unchecked(1697, 2, 1, 0, 0, 0, Some(TimeZone::UTC)),
1538                DateTime::new_unchecked(1903, 3, 1, 0, 0, 0, Some(TimeZone::UTC)),
1539                DateTime::new_unchecked(1903, 7, 1, 0, 0, 0, Some(TimeZone::UTC)),
1540            ]
1541        };
1542
1543        let ret0 = (BASELINE[0] + *self).partial_cmp(&(BASELINE[0] + *other))?;
1544        let ret1 = (BASELINE[1] + *self).partial_cmp(&(BASELINE[1] + *other))?;
1545        let ret2 = (BASELINE[2] + *self).partial_cmp(&(BASELINE[2] + *other))?;
1546        let ret3 = (BASELINE[3] + *self).partial_cmp(&(BASELINE[3] + *other))?;
1547        (ret0 == ret1 && ret0 == ret2 && ret0 == ret3).then_some(ret0)
1548    }
1549}
1550
1551impl std::fmt::Display for Duration {
1552    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1553        if self.neg {
1554            write!(f, "-")?;
1555        }
1556        write!(f, "P")?;
1557        if self.year.is_none()
1558            && self.month.is_none()
1559            && self.day.is_none()
1560            && self.hour.is_none()
1561            && self.minute.is_none()
1562            && self.nanosecond.is_none()
1563        {
1564            write!(f, "0Y")?;
1565            return Ok(());
1566        }
1567        if let Some(year) = self.year {
1568            write!(f, "{}Y", year)?;
1569        }
1570        if let Some(month) = self.month {
1571            write!(f, "{}M", month)?;
1572        }
1573        if let Some(day) = self.day {
1574            write!(f, "{}D", day)?;
1575        }
1576
1577        match (self.hour, self.minute, self.nanosecond) {
1578            (None, None, None) => {}
1579            (hour, minute, nanosecond) => {
1580                write!(f, "T")?;
1581                if let Some(hour) = hour {
1582                    write!(f, "{}H", hour)?;
1583                }
1584                if let Some(minute) = minute {
1585                    write!(f, "{}M", minute)?;
1586                }
1587                if let Some(nanosecond) = nanosecond {
1588                    let sec = nanosecond.get() / NANOSECONDS_PER_SECOND;
1589                    let mut nano = nanosecond.get() % NANOSECONDS_PER_SECOND;
1590                    if nano == 0 {
1591                        write!(f, "{}S", sec)?;
1592                    } else {
1593                        while nano % 10 == 0 {
1594                            nano /= 10;
1595                        }
1596                        write!(f, "{}.{}S", sec, nano)?;
1597                    }
1598                }
1599            }
1600        }
1601
1602        Ok(())
1603    }
1604}
1605
1606impl FromStr for Duration {
1607    type Err = DateTimeError;
1608
1609    fn from_str(mut s: &str) -> Result<Self, Self::Err> {
1610        let mut neg = false;
1611        if let Some(rem) = s.strip_prefix('-') {
1612            neg = true;
1613            s = rem;
1614        }
1615
1616        s = s
1617            .strip_prefix('P')
1618            .ok_or(datetime_error!(Year, InvalidFormat))?;
1619        if s.is_empty() {
1620            return Err(datetime_error!(Year, InvalidFormat))?;
1621        }
1622
1623        let mut year = None;
1624        let mut month = None;
1625        let mut day = None;
1626        let mut hour = None;
1627        let mut minute = None;
1628        let mut nanosecond = None;
1629
1630        macro_rules! parse_number {
1631            ( $s:expr, $ind:literal, $seg:ident, $res:expr ) => {
1632                if let Some((seg, rem)) = $s.split_once($ind) {
1633                    let seg = seg
1634                        .parse::<u64>()
1635                        .map_err(|err| datetime_error!($seg, ParseIntError(err)))?;
1636                    $res = NonZeroU64::new(seg);
1637                    $s = rem;
1638                }
1639            };
1640        }
1641        parse_number!(s, 'Y', Year, year);
1642        parse_number!(s, 'M', Month, month);
1643        parse_number!(s, 'D', Day, day);
1644        if let Some(rem) = s.strip_prefix('T') {
1645            s = rem;
1646            if s.is_empty() {
1647                return Err(datetime_error!(Hour, InvalidFormat));
1648            }
1649            parse_number!(s, 'H', Hour, hour);
1650            parse_number!(s, 'M', Minute, minute);
1651            if let Some((sec, rem)) = s.split_once('S') {
1652                s = rem;
1653
1654                if let Some((sec, frac)) = sec.split_once('.') {
1655                    let sec = sec
1656                        .parse::<u64>()
1657                        .map_err(|err| datetime_error!(Second, ParseIntError(err)))?
1658                        .checked_mul(NANOSECONDS_PER_SECOND)
1659                        .ok_or(datetime_error!(Second, TooLarge))?;
1660                    let base = 10u64.pow(9 - frac.len() as u32);
1661                    let frac = frac
1662                        .parse::<u64>()
1663                        .map_err(|err| datetime_error!(Second, ParseIntError(err)))?
1664                        .checked_mul(base)
1665                        .ok_or(datetime_error!(Second, TooLarge))?;
1666                    let nano = sec
1667                        .checked_add(frac)
1668                        .ok_or(datetime_error!(Second, TooLarge))?;
1669                    nanosecond = NonZeroU64::new(nano);
1670                } else {
1671                    let sec = sec
1672                        .parse::<u64>()
1673                        .map_err(|err| datetime_error!(Second, ParseIntError(err)))?;
1674                    let nano = sec
1675                        .checked_mul(NANOSECONDS_PER_SECOND)
1676                        .ok_or(datetime_error!(Second, TooLarge))?;
1677                    nanosecond = NonZeroU64::new(nano);
1678                }
1679            }
1680        }
1681
1682        if !s.is_empty() {
1683            return Err(datetime_error!(Second, InvalidFormat));
1684        }
1685
1686        Ok(Self {
1687            neg,
1688            year,
1689            month,
1690            day,
1691            hour,
1692            minute,
1693            nanosecond,
1694        })
1695    }
1696}
1697
1698fn is_leap(year: NaiveYear) -> bool {
1699    year.0 % 400 == 0 || (year.0 % 100 != 0 && year.0 % 4 == 0)
1700}
1701
1702macro_rules! impl_partial_eq_for_datetime_objects {
1703    ( $( $t:ty ),* ) => {
1704        $(
1705            impl PartialEq for $t {
1706                fn eq(&self, other: &Self) -> bool {
1707                    self.partial_cmp(other).is_some_and(|ret| ret.is_eq())
1708                }
1709            }
1710        )*
1711    };
1712}
1713impl_partial_eq_for_datetime_objects!(
1714    Duration, DateTime, Time, Date, GYearMonth, GYear, GMonthDay, GDay, GMonth
1715);
1716
1717/// If overflow occurs, return [`None`].
1718///
1719/// # Reference
1720/// - [E.1 Algorithm](https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#adding-durations-to-dateTimes)
1721fn maximum_day_in_month_for(year: NaiveYear, month: i8) -> Option<u8> {
1722    let m = (month - 1).rem_euclid(12) + 1;
1723    let mut y = year.0.checked_add((month - 1).div_euclid(12) as i128)?;
1724    if y == 0 {
1725        y = 1;
1726    }
1727    if m == 2 {
1728        Some(28 + is_leap(NaiveYear(y)) as u8)
1729    } else {
1730        Some(NUM_OF_DAYS_IN_A_MONTH[m as usize])
1731    }
1732}
1733
1734#[cfg(test)]
1735mod tests {
1736    use super::*;
1737
1738    #[test]
1739    fn gyear_parse_test() {
1740        assert!("1999".parse::<GYear>().is_ok());
1741        assert!("-1999".parse::<GYear>().is_ok());
1742        assert!("1999Z".parse::<GYear>().is_ok());
1743        assert!("-1999Z".parse::<GYear>().is_ok());
1744        assert!("1999+09:00".parse::<GYear>().is_ok());
1745        assert!("1999-09:00".parse::<GYear>().is_ok());
1746        assert!("-1999+09:00".parse::<GYear>().is_ok());
1747        assert!("-1999-09:00".parse::<GYear>().is_ok());
1748        // edge case
1749        assert!("-1999+14:00".parse::<GYear>().is_ok());
1750        // edge case
1751        assert!("-1999-14:00".parse::<GYear>().is_ok());
1752        // edge case
1753        assert!("1999+09:59".parse::<GYear>().is_ok());
1754
1755        assert!("231999".parse::<GYear>().is_ok());
1756        assert!("-231999".parse::<GYear>().is_ok());
1757        assert!("231999+09:00".parse::<GYear>().is_ok());
1758        assert!("231999-09:00".parse::<GYear>().is_ok());
1759        assert!("-231999+09:00".parse::<GYear>().is_ok());
1760        assert!("-231999-09:00".parse::<GYear>().is_ok());
1761
1762        // '0000' is not allowed.
1763        assert!("0000".parse::<GYear>().is_err());
1764        assert!("0000+09:00".parse::<GYear>().is_err());
1765        // Too large year
1766        assert!(
1767            "1000000000000000000000000000000000000000"
1768                .parse::<GYear>()
1769                .is_err()
1770        );
1771        // Too small year
1772        assert!(
1773            "-1000000000000000000000000000000000000000"
1774                .parse::<GYear>()
1775                .is_err()
1776        );
1777        // invalid timezone
1778        assert!("1999+12:60".parse::<GYear>().is_err());
1779        assert!("1999+12:000".parse::<GYear>().is_err());
1780        assert!("1999+012:00".parse::<GYear>().is_err());
1781        // unallowed positive sign
1782        assert!("+1999".parse::<GYear>().is_err());
1783        assert!("+1999+09:00".parse::<GYear>().is_err());
1784        // Too large timezone
1785        assert!("1999+15:00".parse::<GYear>().is_err());
1786        // Too small timezone
1787        assert!("1999-15:00".parse::<GYear>().is_err());
1788        // Too large timezone (edge case)
1789        assert!("1999+14:01".parse::<GYear>().is_err());
1790        // Too small timezone (edge case)
1791        assert!("1999-14:01".parse::<GYear>().is_err());
1792    }
1793
1794    #[test]
1795    fn gmonth_parse_test() {
1796        assert!("--12".parse::<GMonth>().is_ok());
1797        assert!("--12Z".parse::<GMonth>().is_ok());
1798        assert!("--12+09:00".parse::<GMonth>().is_ok());
1799        assert!("--12-09:00".parse::<GMonth>().is_ok());
1800
1801        // '00' is not allowed.
1802        assert!("--00".parse::<GMonth>().is_err());
1803        assert!("--00+09:00".parse::<GMonth>().is_err());
1804        // Too large month
1805        assert!("--13".parse::<GMonth>().is_err());
1806        // invalid timezone
1807        assert!("--12+12:60".parse::<GMonth>().is_err());
1808        assert!("--12+12:000".parse::<GMonth>().is_err());
1809        assert!("--12+012:00".parse::<GMonth>().is_err());
1810        // unallowed positive sign
1811        assert!("--+12+09:00".parse::<GMonth>().is_err());
1812        // unallowed negative sign
1813        assert!("---12+09:00".parse::<GMonth>().is_err());
1814    }
1815
1816    #[test]
1817    fn gday_parse_test() {
1818        assert!("---08".parse::<GDay>().is_ok());
1819        assert!("---08Z".parse::<GDay>().is_ok());
1820        assert!("---08+09:00".parse::<GDay>().is_ok());
1821        assert!("---08-09:00".parse::<GDay>().is_ok());
1822
1823        // '00' is not allowed.
1824        assert!("---00".parse::<GDay>().is_err());
1825        assert!("---00+09:00".parse::<GDay>().is_err());
1826        // Too large day
1827        assert!("---32".parse::<GDay>().is_err());
1828        // invalid timezone
1829        assert!("---12+12:60".parse::<GDay>().is_err());
1830        assert!("---12+12:000".parse::<GDay>().is_err());
1831        assert!("---12+012:00".parse::<GDay>().is_err());
1832        // unallowed positive sign
1833        assert!("---+12+09:00".parse::<GDay>().is_err());
1834        // unallowed negative sign
1835        assert!("----12+09:00".parse::<GDay>().is_err());
1836    }
1837
1838    #[test]
1839    fn gyearmonth_parse_test() {
1840        assert!("2015-05".parse::<GYearMonth>().is_ok());
1841        assert!("2015-05Z".parse::<GYearMonth>().is_ok());
1842        assert!("2015-05+09:00".parse::<GYearMonth>().is_ok());
1843        assert!("2015-05-09:00".parse::<GYearMonth>().is_ok());
1844        assert!("-0660-02+09:00".parse::<GYearMonth>().is_ok());
1845        assert!("-0660-02-09:00".parse::<GYearMonth>().is_ok());
1846
1847        // invalid timezone
1848        assert!("2015-05+12:60".parse::<GYearMonth>().is_err());
1849        assert!("2015-05+12:000".parse::<GYearMonth>().is_err());
1850        assert!("2025-05+012:00".parse::<GYearMonth>().is_err());
1851        // unallowed positive sign
1852        assert!("+2015-05+09:00".parse::<GYearMonth>().is_err());
1853    }
1854
1855    #[test]
1856    fn gmonthday_parse_test() {
1857        assert!("--05-15".parse::<GMonthDay>().is_ok());
1858        assert!("--05-15Z".parse::<GMonthDay>().is_ok());
1859        assert!("--05-15+09:00".parse::<GMonthDay>().is_ok());
1860        assert!("--05-15-09:00".parse::<GMonthDay>().is_ok());
1861        assert!("--02-11+09:00".parse::<GMonthDay>().is_ok());
1862        assert!("--02-11-09:00".parse::<GMonthDay>().is_ok());
1863        // edge case
1864        assert!("--01-31".parse::<GMonthDay>().is_ok());
1865        assert!("--02-29".parse::<GMonthDay>().is_ok());
1866        assert!("--03-31".parse::<GMonthDay>().is_ok());
1867        assert!("--04-30".parse::<GMonthDay>().is_ok());
1868        assert!("--05-31".parse::<GMonthDay>().is_ok());
1869        assert!("--06-30".parse::<GMonthDay>().is_ok());
1870        assert!("--07-31".parse::<GMonthDay>().is_ok());
1871        assert!("--08-31".parse::<GMonthDay>().is_ok());
1872        assert!("--09-30".parse::<GMonthDay>().is_ok());
1873        assert!("--10-31".parse::<GMonthDay>().is_ok());
1874        assert!("--11-30".parse::<GMonthDay>().is_ok());
1875        assert!("--12-31".parse::<GMonthDay>().is_ok());
1876
1877        // invalid timezone
1878        assert!("--05-15+12:60".parse::<GMonthDay>().is_err());
1879        assert!("--05-15+12:000".parse::<GMonthDay>().is_err());
1880        assert!("--05-15+012:00".parse::<GMonthDay>().is_err());
1881        // unallowed positive sign
1882        assert!("--+05-15+09:00".parse::<GMonthDay>().is_err());
1883        // out of range
1884        assert!("--01-32".parse::<GMonthDay>().is_err());
1885        assert!("--02-30".parse::<GMonthDay>().is_err());
1886        assert!("--03-32".parse::<GMonthDay>().is_err());
1887        assert!("--04-31".parse::<GMonthDay>().is_err());
1888        assert!("--05-32".parse::<GMonthDay>().is_err());
1889        assert!("--06-31".parse::<GMonthDay>().is_err());
1890        assert!("--07-32".parse::<GMonthDay>().is_err());
1891        assert!("--08-32".parse::<GMonthDay>().is_err());
1892        assert!("--09-31".parse::<GMonthDay>().is_err());
1893        assert!("--10-32".parse::<GMonthDay>().is_err());
1894        assert!("--11-31".parse::<GMonthDay>().is_err());
1895        assert!("--12-32".parse::<GMonthDay>().is_err());
1896    }
1897
1898    #[test]
1899    fn date_parse_test() {
1900        assert!("2015-05-15".parse::<Date>().is_ok());
1901        assert!("2015-05-15Z".parse::<Date>().is_ok());
1902        assert!("2015-05-15+09:00".parse::<Date>().is_ok());
1903        assert!("2015-05-15-09:00".parse::<Date>().is_ok());
1904        assert!("-0660-02-11+09:00".parse::<Date>().is_ok());
1905        assert!("-0660-02-11-09:00".parse::<Date>().is_ok());
1906        // leap year
1907        assert!("2024-02-29".parse::<Date>().is_ok());
1908        assert!("2000-02-29".parse::<Date>().is_ok());
1909
1910        // invalid timezone
1911        assert!("2015-05-15+12:60".parse::<Date>().is_err());
1912        assert!("2015-05-15+12:000".parse::<Date>().is_err());
1913        assert!("2015-05-15+012:00".parse::<Date>().is_err());
1914        // unallowed positive sign
1915        assert!("+2015-05-15+09:00".parse::<Date>().is_err());
1916        // non-leap year
1917        assert!("2015-02-29".parse::<Date>().is_err());
1918        assert!("1900-02-29".parse::<Date>().is_err());
1919    }
1920
1921    #[test]
1922    fn time_parse_test() {
1923        assert!("00:00:00".parse::<Time>().is_ok());
1924        assert!("12:00:00".parse::<Time>().is_ok());
1925        // 24:00:00 is allowed
1926        assert!("24:00:00".parse::<Time>().is_ok());
1927        // leap second (inserted)
1928        assert!("23:59:60".parse::<Time>().is_ok());
1929        assert!("12:30:00Z".parse::<Time>().is_ok());
1930        assert!("12:00:30+09:00".parse::<Time>().is_ok());
1931        assert!("12:15:15-09:00".parse::<Time>().is_ok());
1932
1933        assert!("25:00:00".parse::<Time>().is_err());
1934        assert!("12:60:00".parse::<Time>().is_err());
1935        assert!("09:00:60".parse::<Time>().is_err());
1936        assert!("9:00:00".parse::<Time>().is_err());
1937        assert!("+09:00:00".parse::<Time>().is_err());
1938        assert!("-09:00:00".parse::<Time>().is_err());
1939        assert!("+9:00:00".parse::<Time>().is_err());
1940        assert!("-9:00:00".parse::<Time>().is_err());
1941        assert!("09:+0:00".parse::<Time>().is_err());
1942        assert!("09:-0:00".parse::<Time>().is_err());
1943        assert!("09:00:+0".parse::<Time>().is_err());
1944        assert!("09:00:-0".parse::<Time>().is_err());
1945        assert!("09:00:00++1:00".parse::<Time>().is_err());
1946        assert!("09:00:00+-1:00".parse::<Time>().is_err());
1947        assert!("09:00:00-+1:00".parse::<Time>().is_err());
1948        assert!("09:00:00--1:00".parse::<Time>().is_err());
1949        assert!("09:00:00+01:+0".parse::<Time>().is_err());
1950        assert!("09:00:00+01:-0".parse::<Time>().is_err());
1951        // unallowed positive sign
1952        assert!("+09:00:10".parse::<Time>().is_err());
1953        // unallowed negative sign
1954        assert!("-09:00:10".parse::<Time>().is_err());
1955        // invalid leap second
1956        assert!("23:00:60".parse::<Time>().is_err());
1957        // '24' is not allowed as hour other than '24:00:00'
1958        assert!("24:00:01".parse::<Time>().is_err());
1959    }
1960
1961    #[test]
1962    fn datetime_parse_test() {
1963        assert!("2000-01-20T12:00:00-13:00".parse::<DateTime>().is_ok());
1964        assert!("2000-01-20T12:00:00Z".parse::<DateTime>().is_ok());
1965        assert!("2000-01-12T12:13:14Z".parse::<DateTime>().is_ok());
1966        assert!("-0660-02-11T00:00:00+09:00".parse::<DateTime>().is_ok());
1967
1968        assert!("+2000-01-20T12:00:00-13:00".parse::<DateTime>().is_err());
1969        assert!("2000-01-20t12:00:00-13:00".parse::<DateTime>().is_err());
1970        assert!("2000-+1-20T12:00:00-13:00".parse::<DateTime>().is_err());
1971        assert!("+000-01-20T12:00:00-13:00".parse::<DateTime>().is_err());
1972        assert!("0000-01-20T12:00:00-13:00".parse::<DateTime>().is_err());
1973        assert!("2000-01-2012:00:00-13:00".parse::<DateTime>().is_err());
1974        assert!("2000001-20T12:00:00-13:00".parse::<DateTime>().is_err());
1975        assert!("2000-01-20T-12:00".parse::<DateTime>().is_err());
1976    }
1977
1978    #[test]
1979    fn duration_parse_test() {
1980        assert!("P1347Y".parse::<Duration>().is_ok());
1981        assert!("P1347M".parse::<Duration>().is_ok());
1982        assert!("P1Y2MT2H".parse::<Duration>().is_ok());
1983        assert!("P0Y1347M".parse::<Duration>().is_ok());
1984        assert!("P0Y1347M0D".parse::<Duration>().is_ok());
1985        assert!("-P1347M".parse::<Duration>().is_ok());
1986
1987        assert!("P-1347M".parse::<Duration>().is_err());
1988        assert!("P1Y2MT".parse::<Duration>().is_err());
1989    }
1990
1991    #[test]
1992    fn datetime_addition_test() {
1993        let datetime = "2000-01-12T12:13:14Z".parse::<DateTime>().unwrap();
1994        let duration = "P1Y3M5DT7H10M3.3S".parse::<Duration>().unwrap();
1995        let ret = datetime + duration;
1996        assert_eq!(ret.to_string(), "2001-04-17T19:23:17.3Z");
1997
1998        let datetime = "2000-01-01T00:00:00Z".parse::<DateTime>().unwrap();
1999        let duration = "-P3M".parse::<Duration>().unwrap();
2000        let ret = datetime + duration;
2001        assert_eq!(ret.to_string(), "1999-10-01T00:00:00Z");
2002
2003        let datetime = "2000-01-12T00:00:00Z".parse::<DateTime>().unwrap();
2004        let duration = "PT33H".parse::<Duration>().unwrap();
2005        let ret = datetime + duration;
2006        assert_eq!(ret.to_string(), "2000-01-13T09:00:00Z");
2007
2008        let datetime = "2000-03-04T23:00:00+03:00".parse::<DateTime>().unwrap();
2009        let utc = datetime.to_utc();
2010        assert_eq!(utc.to_string(), "2000-03-04T20:00:00Z");
2011    }
2012
2013    #[test]
2014    fn datetime_comparison_test() {
2015        // Determinate
2016        assert!(
2017            "2000-01-15T00:00:00".parse::<DateTime>().unwrap()
2018                < "2000-02-15T00:00:00".parse::<DateTime>().unwrap()
2019        );
2020        assert!(
2021            "2000-01-15T12:00:00".parse::<DateTime>().unwrap()
2022                < "2000-01-16T12:00:00Z".parse::<DateTime>().unwrap()
2023        );
2024
2025        // Indeterminate
2026        assert!(
2027            "2000-01-01T12:00:00"
2028                .parse::<DateTime>()
2029                .unwrap()
2030                .partial_cmp(&"1999-12-31T23:00:00Z".parse().unwrap())
2031                .is_none()
2032        );
2033        assert!(
2034            "2000-01-16T12:00:00"
2035                .parse::<DateTime>()
2036                .unwrap()
2037                .partial_cmp(&"2000-01-16T12:00:00Z".parse().unwrap())
2038                .is_none()
2039        );
2040        assert!(
2041            "2000-01-16T00:00:00"
2042                .parse::<DateTime>()
2043                .unwrap()
2044                .partial_cmp(&"2000-01-16T12:00:00Z".parse().unwrap())
2045                .is_none()
2046        );
2047    }
2048
2049    #[test]
2050    fn duration_comparison_test() {
2051        let p1y = "P1Y".parse::<Duration>().unwrap();
2052        assert!(p1y > "P364D".parse().unwrap());
2053        assert!(p1y < "P367D".parse().unwrap());
2054        assert!(p1y.partial_cmp(&"P365D".parse().unwrap()).is_none());
2055        assert!(p1y.partial_cmp(&"P366D".parse().unwrap()).is_none());
2056
2057        let p1m = "P1M".parse::<Duration>().unwrap();
2058        assert!(p1m > "P27D".parse().unwrap());
2059        assert!(p1m < "P32D".parse().unwrap());
2060        assert!(p1m.partial_cmp(&"P28D".parse().unwrap()).is_none());
2061        assert!(p1m.partial_cmp(&"P29D".parse().unwrap()).is_none());
2062        assert!(p1m.partial_cmp(&"P30D".parse().unwrap()).is_none());
2063        assert!(p1m.partial_cmp(&"P31D".parse().unwrap()).is_none());
2064
2065        let p5m = "P5M".parse::<Duration>().unwrap();
2066        assert!(p5m > "P149D".parse().unwrap());
2067        assert!(p5m < "P154D".parse().unwrap());
2068        assert!(p5m.partial_cmp(&"P150D".parse().unwrap()).is_none());
2069        assert!(p5m.partial_cmp(&"P151D".parse().unwrap()).is_none());
2070        assert!(p5m.partial_cmp(&"P152D".parse().unwrap()).is_none());
2071        assert!(p5m.partial_cmp(&"P153D".parse().unwrap()).is_none());
2072    }
2073}