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