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