1use crate::scalar::to_i64_t;
2use crate::util::{fast_digit_parse, le_u64};
3use std::cmp::Ordering;
4use std::convert::TryFrom;
5use std::fmt::{self, Debug, Display};
6use std::str::FromStr;
7
8#[derive(Debug, PartialEq, Eq)]
10pub struct DateError;
11
12impl std::error::Error for DateError {
13 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
14 None
15 }
16}
17
18impl std::fmt::Display for DateError {
19 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20 write!(f, "unable to decode date")
21 }
22}
23
24const DAYS_PER_MONTH: [u8; 13] = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
25
26pub trait PdsDate {
28 fn year(&self) -> i16;
30
31 fn month(&self) -> u8;
33
34 fn day(&self) -> u8;
36
37 fn game_fmt(&self) -> PdsDateFormatter;
39
40 fn iso_8601(&self) -> PdsDateFormatter;
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum DateFormat {
47 Iso8601,
49
50 DotShort,
52
53 DotWide,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub struct PdsDateFormatter {
62 raw: RawDate,
63 format: DateFormat,
64}
65
66impl PdsDateFormatter {
67 pub fn new(raw: RawDate, format: DateFormat) -> Self {
69 Self { raw, format }
70 }
71}
72
73impl Display for PdsDateFormatter {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 if self.format == DateFormat::Iso8601 {
76 write!(
77 f,
78 "{:04}-{:02}-{:02}",
79 self.raw.year(),
80 self.raw.month(),
81 self.raw.day(),
82 )?;
83
84 if self.raw.has_hour() {
85 write!(f, "T{:02}", self.raw.hour() - 1)
86 } else {
87 Ok(())
88 }
89 } else {
90 let fmt = self.format;
91 let width = if fmt == DateFormat::DotWide { 2 } else { 0 };
92 write!(
93 f,
94 "{}.{:03$}.{:03$}",
95 self.raw.year(),
96 self.raw.month(),
97 self.raw.day(),
98 width,
99 )?;
100
101 if self.raw.has_hour() {
102 write!(f, ".{:01$}", self.raw.hour(), width)
103 } else {
104 Ok(())
105 }
106 }
107 }
108}
109
110#[derive(Debug)]
112struct ExpandedRawDate {
113 year: i16,
114 month: u8,
115 day: u8,
116 hour: u8,
117}
118
119impl ExpandedRawDate {
120 #[inline]
121 fn from_binary(mut s: i32) -> Option<Self> {
122 let hour = s % 24;
126 s /= 24;
127
128 let days_since_jan1 = s % 365;
129 if hour < 0 || days_since_jan1 < 0 {
130 return None;
131 }
132
133 s /= 365;
134 let year = s.checked_sub(5000).and_then(|x| i16::try_from(x).ok())?;
135 let (month, day) = month_day_from_julian(days_since_jan1);
136 Some(ExpandedRawDate {
137 year,
138 month,
139 day,
140 hour: hour as u8,
141 })
142 }
143
144 #[inline]
145 fn parse<T: AsRef<[u8]>>(s: T) -> Option<Self> {
146 Self::_parse(s.as_ref())
147 }
148
149 #[inline]
150 fn _parse(data: &[u8]) -> Option<Self> {
151 let (year, data) = to_i64_t(data).ok()?;
152 if data.is_empty() {
153 return i32::try_from(year).ok().and_then(Self::from_binary);
154 }
155
156 let year = i16::try_from(year).ok()?;
157 if *data.first()? != b'.' {
158 return None;
159 }
160
161 let n = data.get(1)?;
162 let month1 = if !n.is_ascii_digit() {
163 return None;
164 } else {
165 n - b'0'
166 };
167
168 let n = data.get(2)?;
169 let (month, offset) = if *n == b'.' {
170 (month1, 2)
171 } else if n.is_ascii_digit() {
172 (month1 * 10 + (n - b'0'), 3)
173 } else {
174 return None;
175 };
176
177 if *data.get(offset)? != b'.' {
178 return None;
179 }
180
181 let n = data.get(offset + 1)?;
182 let day1 = if !n.is_ascii_digit() {
183 return None;
184 } else {
185 n - b'0'
186 };
187
188 let (day, offset) = match data.get(offset + 2) {
189 None => {
190 return Some(ExpandedRawDate {
191 year,
192 month,
193 day: day1,
194 hour: 0,
195 })
196 }
197 Some(b'.') => (day1, offset + 2),
198 Some(n) if n.is_ascii_digit() => {
199 let result = day1 * 10 + (n - b'0');
200 if data.len() != offset + 3 {
201 (result, offset + 3)
202 } else {
203 return Some(ExpandedRawDate {
204 year,
205 month,
206 day: result,
207 hour: 0,
208 });
209 }
210 }
211 _ => return None,
212 };
213
214 if *data.get(offset)? != b'.' {
215 return None;
216 }
217
218 let n = data.get(offset + 1)?;
219 let hour1 = if !n.is_ascii_digit() || *n == b'0' {
220 return None;
221 } else {
222 n - b'0'
223 };
224
225 match data.get(offset + 2) {
226 None => Some(ExpandedRawDate {
227 year,
228 month,
229 day,
230 hour: hour1,
231 }),
232 Some(n) if n.is_ascii_digit() => {
233 let result = hour1 * 10 + (n - b'0');
234 if data.len() != offset + 3 {
235 None
236 } else {
237 Some(ExpandedRawDate {
238 year,
239 month,
240 day,
241 hour: result,
242 })
243 }
244 }
245 _ => None,
246 }
247 }
248}
249
250#[derive(Clone, Copy, PartialEq, Eq, Hash)]
275pub struct RawDate {
276 year: i16,
277
278 data: u16,
283}
284
285impl Debug for RawDate {
286 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287 write!(
288 f,
289 "RawDate {{ year: {} month: {} day: {} hour: {} }}",
290 self.year(),
291 self.month(),
292 self.day(),
293 self.hour()
294 )
295 }
296}
297
298impl PartialOrd for RawDate {
299 fn partial_cmp(&self, other: &RawDate) -> Option<Ordering> {
300 Some(self.cmp(other))
301 }
302}
303
304impl Ord for RawDate {
305 fn cmp(&self, other: &RawDate) -> Ordering {
306 self.year()
307 .cmp(&other.year())
308 .then_with(|| self.data.cmp(&other.data))
309 }
310}
311
312impl RawDate {
313 #[inline]
314 fn from_expanded(data: ExpandedRawDate) -> Option<Self> {
315 Self::from_ymdh_opt(data.year, data.month, data.day, data.hour)
316 }
317
318 #[inline]
329 pub fn from_binary(s: i32) -> Option<Self> {
330 ExpandedRawDate::from_binary(s).and_then(Self::from_expanded)
331 }
332
333 #[inline]
337 pub fn from_ymdh_opt(year: i16, month: u8, day: u8, hour: u8) -> Option<Self> {
338 if month != 0 && month < 13 && day != 0 && day < 32 && hour < 25 {
339 let data = (u16::from(month) << 12) + (u16::from(day) << 7) + (u16::from(hour) << 2);
340 Some(RawDate { year, data })
341 } else {
342 None
343 }
344 }
345
346 #[inline]
350 pub fn from_ymdh(year: i16, month: u8, day: u8, hour: u8) -> Self {
351 Self::from_ymdh_opt(year, month, day, hour).unwrap()
352 }
353
354 #[inline]
356 pub fn hour(&self) -> u8 {
357 ((self.data >> 2) & 0x1f) as u8
358 }
359
360 #[inline]
362 pub fn has_hour(&self) -> bool {
363 self.data & 0x7c != 0
364 }
365
366 #[inline]
379 pub fn parse<T: AsRef<[u8]>>(s: T) -> Result<Self, DateError> {
380 Self::_parse(s.as_ref())
381 }
382
383 #[inline]
384 fn _parse(s: &[u8]) -> Result<Self, DateError> {
385 ExpandedRawDate::parse(s)
386 .and_then(Self::from_expanded)
387 .and_then(|x| {
388 if to_i64_t(s).ok()?.1.is_empty() {
389 None
390 } else {
391 Some(x)
392 }
393 })
394 .ok_or(DateError)
395 }
396}
397
398impl PdsDate for RawDate {
399 #[inline]
401 fn year(&self) -> i16 {
402 self.year
403 }
404
405 #[inline]
407 fn month(&self) -> u8 {
408 (self.data >> 12) as u8
409 }
410
411 #[inline]
413 fn day(&self) -> u8 {
414 ((self.data >> 7) & 0x1f) as u8
415 }
416
417 fn game_fmt(&self) -> PdsDateFormatter {
418 PdsDateFormatter::new(*self, DateFormat::DotShort)
419 }
420
421 fn iso_8601(&self) -> PdsDateFormatter {
422 PdsDateFormatter::new(*self, DateFormat::Iso8601)
423 }
424}
425
426impl FromStr for RawDate {
427 type Err = DateError;
428
429 #[inline]
430 fn from_str(s: &str) -> Result<Self, Self::Err> {
431 Self::parse(s.as_bytes())
432 }
433}
434
435#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
439pub struct Date {
440 raw: RawDate,
441}
442
443impl Debug for Date {
444 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
445 write!(f, "Date {}", self.game_fmt())
446 }
447}
448
449impl Date {
450 #[inline]
451 fn from_expanded(date: ExpandedRawDate) -> Option<Self> {
452 if date.hour != 0 {
453 None
454 } else {
455 Self::from_ymd_opt(date.year, date.month, date.day)
456 }
457 }
458
459 #[inline]
460 fn days(&self) -> i32 {
461 let month_days = julian_ordinal_day(self.month());
462 let year_day = i32::from(self.year()) * 365;
463 if year_day < 0 {
464 year_day - month_days - i32::from(self.day())
465 } else {
466 year_day + month_days + i32::from(self.day())
467 }
468 }
469
470 #[inline]
485 pub fn from_ymd_opt(year: i16, month: u8, day: u8) -> Option<Self> {
486 RawDate::from_ymdh_opt(year, month, day, 0).and_then(|raw| {
487 let days = DAYS_PER_MONTH[usize::from(month)];
488 if day <= days {
489 Some(Date { raw })
490 } else {
491 None
492 }
493 })
494 }
495
496 #[inline]
500 pub fn from_ymd(year: i16, month: u8, day: u8) -> Self {
501 Self::from_ymd_opt(year, month, day).unwrap()
502 }
503
504 #[inline]
516 pub fn parse<T: AsRef<[u8]>>(s: T) -> Result<Self, DateError> {
517 Self::_parse(s.as_ref())
518 }
519
520 #[inline]
521 fn fast_parse(r: [u8; 8]) -> Option<Result<Self, DateError>> {
522 Self::fast_parse_u64(u64::from_le_bytes(r))
523 }
524
525 #[inline]
526 fn fast_parse_u64(r: u64) -> Option<Result<Self, DateError>> {
527 let val = fast_digit_parse(r)?;
528 let day = val % 100;
529 let month = (val / 100) % 100;
530 let val = val / 10_000;
531
532 let result = Self::from_expanded(ExpandedRawDate {
533 year: val as i16,
534 month: month as u8,
535 day: day as u8,
536 hour: 0,
537 })
538 .ok_or(DateError);
539
540 Some(result)
541 }
542
543 #[cold]
544 fn fallback(s: &[u8]) -> Result<Self, DateError> {
545 ExpandedRawDate::parse(s)
546 .and_then(Self::from_expanded)
547 .ok_or(DateError)
548 }
549
550 #[inline]
551 fn _parse(s: &[u8]) -> Result<Self, DateError> {
552 match s {
553 [y1, y2, y3, y4, b'.', m1, m2, b'.', d1, d2] => {
554 let r = [*y1, *y2, *y3, *y4, *m1, *m2, *d1, *d2];
555 Self::fast_parse(r).unwrap_or_else(|| Self::fallback(s))
556 }
557 [y1, y2, y3, y4, b'.', m1, m2, b'.', d1] => {
558 let r = [*y1, *y2, *y3, *y4, *m1, *m2, b'0', *d1];
559 Self::fast_parse(r).unwrap_or_else(|| Self::fallback(s))
560 }
561 [y1, y2, y3, y4, b'.', m1, b'.', d1, d2] => {
562 let r = [*y1, *y2, *y3, *y4, b'0', *m1, *d1, *d2];
563 Self::fast_parse(r).unwrap_or_else(|| Self::fallback(s))
564 }
565 _ => {
566 if s.len() == 8 {
567 let d = le_u64(s);
569 let one_digit_month = d & 0x00FF_00FF_0000_0000 == 0x002E_002E_0000_0000;
570 let e = (d & 0xFF30_FF30_FFFF_FFFF) | 0x0030_0030_0000_0000;
571 if one_digit_month {
572 if let Some(x) = Self::fast_parse_u64(e) {
573 return x;
574 }
575 }
576
577 Self::fallback(s)
578 } else if s.len() < 5 || s.len() > 12 || !matches!(s[0], b'-' | b'0'..=b'9') {
579 Err(DateError)
580 } else {
581 Self::fallback(s)
582 }
583 }
584 }
585 }
586
587 #[inline]
601 pub fn days_until(self, other: &Date) -> i32 {
602 other.days() - self.days()
603 }
604
605 #[inline]
620 pub fn add_days(self, days: i32) -> Date {
621 let new_days = self
622 .days()
623 .checked_add(days)
624 .expect("adding days overflowed");
625
626 let days_since_jan1 = (new_days % 365).abs();
627 let year = new_days / 365;
628 let (month, day) = month_day_from_julian(days_since_jan1);
629
630 let year = i16::try_from(year).expect("year to fit inside signed 16bits");
631 Date {
632 raw: RawDate::from_ymdh(year, month, day, self.raw.hour()),
633 }
634 }
635
636 #[inline]
640 pub fn from_binary(s: i32) -> Option<Self> {
641 ExpandedRawDate::from_binary(s)
645 .map(|x| ExpandedRawDate { hour: 0, ..x })
646 .and_then(Self::from_expanded)
647 }
648
649 #[inline]
660 pub fn from_binary_heuristic(s: i32) -> Option<Self> {
661 ExpandedRawDate::from_binary(s).and_then(|x| {
662 if x.year > -100 {
663 Self::from_expanded(x)
664 } else {
665 None
666 }
667 })
668 }
669
670 #[inline]
678 pub fn to_binary(self) -> i32 {
679 let ordinal_day = julian_ordinal_day(self.month()) + i32::from(self.day());
680 to_binary(self.year(), ordinal_day, 0)
681 }
682}
683
684impl PdsDate for Date {
685 #[inline]
693 fn year(&self) -> i16 {
694 self.raw.year()
695 }
696
697 #[inline]
705 fn month(&self) -> u8 {
706 self.raw.month()
707 }
708
709 #[inline]
717 fn day(&self) -> u8 {
718 self.raw.day()
719 }
720
721 fn iso_8601(&self) -> PdsDateFormatter {
729 PdsDateFormatter::new(self.raw, DateFormat::Iso8601)
730 }
731
732 fn game_fmt(&self) -> PdsDateFormatter {
740 PdsDateFormatter::new(self.raw, DateFormat::DotShort)
741 }
742}
743
744impl FromStr for Date {
745 type Err = DateError;
746
747 #[inline]
748 fn from_str(s: &str) -> Result<Self, Self::Err> {
749 Self::parse(s.as_bytes())
750 }
751}
752
753#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
766pub struct DateHour {
767 raw: RawDate,
768}
769
770impl Debug for DateHour {
771 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
772 write!(f, "DateHour {}", self.game_fmt())
773 }
774}
775
776impl DateHour {
777 #[inline]
778 fn from_expanded(date: ExpandedRawDate) -> Option<Self> {
779 Self::from_ymdh_opt(date.year, date.month, date.day, date.hour)
780 }
781
782 #[inline]
793 pub fn from_ymdh_opt(year: i16, month: u8, day: u8, hour: u8) -> Option<Self> {
794 RawDate::from_ymdh_opt(year, month, day, hour).and_then(|raw| {
795 let days = DAYS_PER_MONTH[usize::from(month)];
796 if hour > 0 && day <= days {
797 Some(Self { raw })
798 } else {
799 None
800 }
801 })
802 }
803
804 #[inline]
810 pub fn from_ymdh(year: i16, month: u8, day: u8, hour: u8) -> Self {
811 Self::from_ymdh_opt(year, month, day, hour).unwrap()
812 }
813
814 pub fn hour(&self) -> u8 {
822 self.raw.hour()
824 }
825
826 #[inline]
834 pub fn parse<T: AsRef<[u8]>>(s: T) -> Result<Self, DateError> {
835 ExpandedRawDate::parse(s)
836 .and_then(Self::from_expanded)
837 .ok_or(DateError)
838 }
839
840 #[inline]
842 pub fn from_binary(s: i32) -> Option<Self> {
843 ExpandedRawDate::from_binary(s).and_then(|mut raw| {
844 raw.hour += 1;
846 Self::from_expanded(raw)
847 })
848 }
849
850 #[inline]
856 pub fn from_binary_heuristic(s: i32) -> Option<Self> {
857 Self::from_binary(s).and_then(|x| {
858 let is_min_year = x.year() == 1 || x.year() == -1;
859 let is_min_date = is_min_year && x.month() == 1 && x.day() == 1 && x.hour() == 1;
860 if x.year() < 1800 && !is_min_date {
861 None
862 } else {
863 Some(x)
864 }
865 })
866 }
867
868 #[inline]
876 pub fn to_binary(self) -> i32 {
877 let ordinal_day = julian_ordinal_day(self.month()) + i32::from(self.day());
878 to_binary(self.year(), ordinal_day, self.hour())
879 }
880}
881
882impl PdsDate for DateHour {
883 #[inline]
891 fn year(&self) -> i16 {
892 self.raw.year()
893 }
894
895 #[inline]
903 fn month(&self) -> u8 {
904 self.raw.month()
905 }
906
907 #[inline]
915 fn day(&self) -> u8 {
916 self.raw.day()
917 }
918
919 fn iso_8601(&self) -> PdsDateFormatter {
929 PdsDateFormatter::new(self.raw, DateFormat::Iso8601)
930 }
931
932 fn game_fmt(&self) -> PdsDateFormatter {
940 PdsDateFormatter::new(self.raw, DateFormat::DotShort)
941 }
942}
943
944impl FromStr for DateHour {
945 type Err = DateError;
946
947 #[inline]
948 fn from_str(s: &str) -> Result<Self, Self::Err> {
949 Self::parse(s.as_bytes())
950 }
951}
952
953#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
959pub struct UniformDate {
960 raw: RawDate,
961}
962
963impl Debug for UniformDate {
964 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
965 write!(f, "UniformDate {}", self.game_fmt())
966 }
967}
968
969impl UniformDate {
970 #[inline]
971 fn from_expanded(date: ExpandedRawDate) -> Option<Self> {
972 if date.hour != 0 {
973 None
974 } else {
975 Self::from_ymd_opt(date.year, date.month, date.day)
976 }
977 }
978
979 #[inline]
994 pub fn from_ymd_opt(year: i16, month: u8, day: u8) -> Option<Self> {
995 if day > 30 {
996 None
997 } else {
998 RawDate::from_ymdh_opt(year, month, day, 0).map(|raw| Self { raw })
999 }
1000 }
1001
1002 #[inline]
1006 pub fn from_ymd(year: i16, month: u8, day: u8) -> Self {
1007 Self::from_ymd_opt(year, month, day).unwrap()
1008 }
1009
1010 #[inline]
1018 pub fn parse<T: AsRef<[u8]>>(s: T) -> Result<Self, DateError> {
1019 Self::_parse(s.as_ref())
1020 }
1021
1022 #[inline]
1023 fn _parse(s: &[u8]) -> Result<Self, DateError> {
1024 ExpandedRawDate::parse(s)
1025 .and_then(Self::from_expanded)
1026 .ok_or(DateError)
1027 }
1028}
1029
1030impl PdsDate for UniformDate {
1031 #[inline]
1039 fn year(&self) -> i16 {
1040 self.raw.year()
1041 }
1042
1043 #[inline]
1051 fn month(&self) -> u8 {
1052 self.raw.month()
1053 }
1054
1055 #[inline]
1063 fn day(&self) -> u8 {
1064 self.raw.day()
1065 }
1066
1067 fn iso_8601(&self) -> PdsDateFormatter {
1075 PdsDateFormatter::new(self.raw, DateFormat::Iso8601)
1076 }
1077
1078 fn game_fmt(&self) -> PdsDateFormatter {
1086 PdsDateFormatter::new(self.raw, DateFormat::DotWide)
1087 }
1088}
1089
1090impl FromStr for UniformDate {
1091 type Err = DateError;
1092
1093 #[inline]
1094 fn from_str(s: &str) -> Result<Self, Self::Err> {
1095 Self::parse(s.as_bytes())
1096 }
1097}
1098
1099#[inline]
1100fn month_day_from_julian(days_since_jan1: i32) -> (u8, u8) {
1101 let (month, day) = match days_since_jan1 {
1104 0..=30 => (1, days_since_jan1 + 1),
1105 31..=58 => (2, days_since_jan1 - 30),
1106 59..=89 => (3, days_since_jan1 - 58),
1107 90..=119 => (4, days_since_jan1 - 89),
1108 120..=150 => (5, days_since_jan1 - 119),
1109 151..=180 => (6, days_since_jan1 - 150),
1110 181..=211 => (7, days_since_jan1 - 180),
1111 212..=242 => (8, days_since_jan1 - 211),
1112 243..=272 => (9, days_since_jan1 - 242),
1113 273..=303 => (10, days_since_jan1 - 272),
1114 304..=333 => (11, days_since_jan1 - 303),
1115 334..=364 => (12, days_since_jan1 - 333),
1116 _ => unreachable!(),
1117 };
1118
1119 debug_assert!(day < 255);
1120 (month, day as u8)
1121}
1122
1123#[inline]
1124fn julian_ordinal_day(month: u8) -> i32 {
1125 match month {
1126 1 => -1,
1127 2 => 30,
1128 3 => 58,
1129 4 => 89,
1130 5 => 119,
1131 6 => 150,
1132 7 => 180,
1133 8 => 211,
1134 9 => 242,
1135 10 => 272,
1136 11 => 303,
1137 12 => 333,
1138 _ => unreachable!(),
1139 }
1140}
1141
1142#[inline]
1143fn to_binary(year: i16, ordinal_day: i32, hour: u8) -> i32 {
1144 let year_part = (i32::from(year) + 5000) * 365;
1145 let hour = i32::from(hour.saturating_sub(1));
1146 (year_part + ordinal_day) * 24 + hour
1147}
1148
1149#[cfg(feature = "derive")]
1150mod datederive {
1151 use super::{Date, DateHour, PdsDate, UniformDate};
1152 use serde::{de, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
1153 use std::fmt;
1154
1155 impl Serialize for Date {
1156 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1157 where
1158 S: Serializer,
1159 {
1160 serializer.serialize_str(self.iso_8601().to_string().as_str())
1161 }
1162 }
1163
1164 struct DateVisitor;
1165
1166 impl Visitor<'_> for DateVisitor {
1167 type Value = Date;
1168
1169 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1170 formatter.write_str("a date")
1171 }
1172
1173 fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
1174 where
1175 E: de::Error,
1176 {
1177 Date::from_binary(v)
1178 .ok_or_else(|| de::Error::custom(format!("invalid binary date: {}", v)))
1179 }
1180
1181 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1182 where
1183 E: de::Error,
1184 {
1185 Date::parse(v).map_err(|_e| de::Error::custom(format!("invalid date: {}", v)))
1186 }
1187
1188 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
1189 where
1190 E: de::Error,
1191 {
1192 self.visit_str(v.as_str())
1193 }
1194 }
1195
1196 impl<'de> Deserialize<'de> for Date {
1197 fn deserialize<D>(deserializer: D) -> Result<Date, D::Error>
1198 where
1199 D: Deserializer<'de>,
1200 {
1201 deserializer.deserialize_any(DateVisitor)
1202 }
1203 }
1204
1205 impl Serialize for DateHour {
1206 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1207 where
1208 S: Serializer,
1209 {
1210 serializer.serialize_str(self.iso_8601().to_string().as_str())
1211 }
1212 }
1213
1214 struct DateHourVisitor;
1215
1216 impl Visitor<'_> for DateHourVisitor {
1217 type Value = DateHour;
1218
1219 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1220 formatter.write_str("a date hour")
1221 }
1222
1223 fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
1224 where
1225 E: de::Error,
1226 {
1227 DateHour::from_binary(v)
1228 .ok_or_else(|| de::Error::custom(format!("invalid binary date hour: {}", v)))
1229 }
1230
1231 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1232 where
1233 E: de::Error,
1234 {
1235 DateHour::parse(v).map_err(|_e| de::Error::custom(format!("invalid date hour: {}", v)))
1236 }
1237
1238 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
1239 where
1240 E: de::Error,
1241 {
1242 self.visit_str(v.as_str())
1243 }
1244 }
1245
1246 impl<'de> Deserialize<'de> for DateHour {
1247 fn deserialize<D>(deserializer: D) -> Result<DateHour, D::Error>
1248 where
1249 D: Deserializer<'de>,
1250 {
1251 deserializer.deserialize_any(DateHourVisitor)
1252 }
1253 }
1254
1255 struct UniformDateVisitor;
1256
1257 impl Visitor<'_> for UniformDateVisitor {
1258 type Value = UniformDate;
1259
1260 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1261 formatter.write_str("a uniform date")
1262 }
1263
1264 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1265 where
1266 E: de::Error,
1267 {
1268 UniformDate::parse(v)
1269 .map_err(|_e| de::Error::custom(format!("invalid uniform date: {}", v)))
1270 }
1271
1272 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
1273 where
1274 E: de::Error,
1275 {
1276 self.visit_str(v.as_str())
1277 }
1278 }
1279
1280 impl<'de> Deserialize<'de> for UniformDate {
1281 fn deserialize<D>(deserializer: D) -> Result<UniformDate, D::Error>
1282 where
1283 D: Deserializer<'de>,
1284 {
1285 deserializer.deserialize_any(UniformDateVisitor)
1286 }
1287 }
1288}
1289
1290#[cfg(not(feature = "derive"))]
1291mod datederive {}
1292
1293#[cfg(test)]
1294mod tests {
1295 use super::*;
1296 use quickcheck_macros::quickcheck;
1297 use rstest::*;
1298
1299 #[test]
1300 fn test_date_iso() {
1301 let date = Date::parse("1400.1.2").unwrap();
1302 assert_eq!(date.iso_8601().to_string(), String::from("1400-01-02"));
1303 }
1304
1305 #[test]
1306 fn test_date_parse() {
1307 assert_eq!(Date::parse("1.01.01").unwrap(), Date::from_ymd(1, 1, 1));
1308 }
1309
1310 #[test]
1311 fn test_first_bin_date() {
1312 let date = Date::from_binary(56379360).unwrap();
1313 assert_eq!(date.iso_8601().to_string(), String::from("1436-01-01"));
1314 }
1315
1316 #[test]
1317 fn test_text_date_overflow() {
1318 assert!(Date::parse("1444.257.1").is_err());
1319 assert!(Date::parse("1444.1.257").is_err());
1320 assert!(Date::parse("60000.1.1").is_err());
1321 assert!(Date::parse("-60000.1.1").is_err());
1322 }
1323
1324 #[test]
1325 fn test_binary_date_overflow() {
1326 assert_eq!(Date::from_binary(999379360), None);
1327 }
1328
1329 #[test]
1330 #[should_panic]
1331 fn test_add_adds_year_overflow() {
1332 let date = Date::parse("1400.1.2").unwrap();
1333 let _ = date.add_days(100000000);
1334 }
1335
1336 #[test]
1337 #[should_panic]
1338 fn test_add_adds_day_overflow() {
1339 let date = Date::parse("1400.1.2").unwrap();
1340 let _ = date.add_days(i32::MAX);
1341 }
1342
1343 #[test]
1344 fn test_ignore_bin_dates() {
1345 assert_eq!(Date::from_binary_heuristic(0), None);
1347 assert_eq!(Date::from_binary_heuristic(380947), None);
1348 assert_eq!(Date::from_binary_heuristic(21282204), None);
1349 assert_eq!(Date::from_binary_heuristic(33370842), None);
1350 assert_eq!(Date::from_binary_heuristic(42267422), None);
1351 assert_eq!(Date::from_binary_heuristic(693362154), None);
1352 }
1353
1354 #[test]
1355 fn test_negative_date() {
1356 let date = Date::parse("-17.1.1").unwrap();
1358 assert_eq!(date.game_fmt().to_string(), String::from("-17.1.1"));
1359
1360 let date2 = Date::from_binary(43651080).unwrap();
1361 assert_eq!(date.game_fmt().to_string(), String::from("-17.1.1"));
1362
1363 assert_eq!(date, date2);
1364 }
1365
1366 #[rstest]
1367 #[case("-1.1.1")]
1368 #[case("-1.1.12")]
1369 #[case("-1.11.1")]
1370 #[case("-1.11.12")]
1371 #[case("-10.1.1")]
1372 #[case("-10.1.12")]
1373 #[case("-10.11.1")]
1374 #[case("-10.11.12")]
1375 #[case("-100.1.1")]
1376 #[case("-100.1.12")]
1377 #[case("-100.11.1")]
1378 #[case("-100.11.12")]
1379 #[case("-1000.1.1")]
1380 #[case("-1000.1.12")]
1381 #[case("-1000.11.1")]
1382 #[case("-1000.11.12")]
1383 #[case("1.1.1")]
1384 #[case("1.1.12")]
1385 #[case("1.11.1")]
1386 #[case("1.11.12")]
1387 #[case("10.1.1")]
1388 #[case("10.1.12")]
1389 #[case("10.11.1")]
1390 #[case("10.11.12")]
1391 #[case("100.1.1")]
1392 #[case("100.1.12")]
1393 #[case("100.11.1")]
1394 #[case("100.11.12")]
1395 #[case("1000.1.1")]
1396 #[case("1000.1.12")]
1397 #[case("1000.11.1")]
1398 #[case("1000.11.12")]
1399 #[case("1400.1.2")]
1400 #[case("1457.3.5")]
1401 #[case("1.1.1")]
1402 #[case("1444.11.11")]
1403 #[case("1444.11.30")]
1404 #[case("1444.2.19")]
1405 #[case("1444.12.3")]
1406 fn test_date_game_fmt_roundtrip(#[case] input: &str) {
1407 let s = Date::parse(input).unwrap().game_fmt().to_string();
1408 assert_eq!(&s, input);
1409 }
1410
1411 #[test]
1412 fn test_zero_date() {
1413 let date = Date::from_binary(43800000).unwrap();
1414 assert_eq!(date.iso_8601().to_string(), String::from("0000-01-01"));
1415 }
1416
1417 #[test]
1418 fn test_negative_datehour_binary() {
1419 let date = DateHour::from_binary(43791240).unwrap();
1420 assert_eq!(date.game_fmt().to_string(), String::from("-1.1.1.1"));
1421 assert_eq!(Some(date), DateHour::from_binary_heuristic(43791240));
1422 }
1423
1424 #[test]
1425 fn test_very_negative_date() {
1426 let date = Date::parse("-2500.1.1").unwrap();
1428 assert_eq!(date.game_fmt().to_string(), String::from("-2500.1.1"));
1429
1430 let date2 = Date::from_binary(21900000).unwrap();
1431 assert_eq!(date2.game_fmt().to_string(), String::from("-2500.1.1"));
1432 assert_eq!(date, date2);
1433 }
1434
1435 #[test]
1436 fn test_very_negative_date2() {
1437 let date = Date::parse("-10000.1.1").unwrap();
1439 assert_eq!(date.game_fmt().to_string(), String::from("-10000.1.1"));
1440
1441 let date2 = Date::from_binary(-43800000).unwrap();
1442 assert_eq!(date2.game_fmt().to_string(), String::from("-10000.1.1"));
1443 assert_eq!(date, date2);
1444 }
1445
1446 #[test]
1447 fn test_november_date_regression() {
1448 let date = Date::from_binary(56379360).unwrap().add_days(303);
1449 assert_eq!(date.iso_8601().to_string(), String::from("1436-10-31"));
1450 let date = Date::from_binary(56379360).unwrap().add_days(304);
1451 assert_eq!(date.iso_8601().to_string(), String::from("1436-11-01"));
1452 let date = Date::from_binary(56379360).unwrap().add_days(303 - 30);
1453 assert_eq!(date.iso_8601().to_string(), String::from("1436-10-01"));
1454 let date = Date::from_binary(56379360).unwrap().add_days(303 - 31);
1455 assert_eq!(date.iso_8601().to_string(), String::from("1436-09-30"));
1456 let date = Date::from_binary(56379360).unwrap().add_days(303 - 31 - 29);
1457 assert_eq!(date.iso_8601().to_string(), String::from("1436-09-01"));
1458 let date = Date::from_binary(56379360).unwrap().add_days(303 - 31 - 30);
1459 assert_eq!(date.iso_8601().to_string(), String::from("1436-08-31"));
1460 }
1461
1462 #[test]
1463 fn test_past_leap_year_bin_date() {
1464 let date = Date::from_binary(59611248).unwrap();
1465 assert_eq!(date.iso_8601().to_string(), String::from("1804-12-09"));
1466 }
1467
1468 #[test]
1469 fn test_early_leap_year_bin_date() {
1470 let date = Date::from_binary(57781584).unwrap();
1471 assert_eq!(date.iso_8601().to_string(), String::from("1596-01-27"));
1472 }
1473
1474 #[test]
1475 fn test_non_leap_year_bin_date() {
1476 let date = Date::from_binary(57775944).unwrap();
1477 assert_eq!(date.iso_8601().to_string(), String::from("1595-06-06"));
1478 }
1479
1480 #[test]
1481 fn test_early_date() {
1482 let date = Date::from_binary(43808760).unwrap();
1483 assert_eq!(date.iso_8601().to_string(), String::from("0001-01-01"));
1484 }
1485
1486 #[test]
1487 fn test_days_until() {
1488 let date = Date::parse("1400.1.2").unwrap();
1489 let date2 = Date::parse("1400.1.3").unwrap();
1490 assert_eq!(1, date.days_until(&date2));
1491 }
1492
1493 #[test]
1494 fn test_days_until2() {
1495 let date = Date::parse("1400.1.2").unwrap();
1496 let date2 = Date::parse("1401.1.2").unwrap();
1497 assert_eq!(365, date.days_until(&date2));
1498 }
1499
1500 #[test]
1501 fn test_days_until3() {
1502 let date = Date::parse("1400.1.1").unwrap();
1503 let date2 = Date::parse("1401.12.31").unwrap();
1504 assert_eq!(729, date.days_until(&date2));
1505 }
1506
1507 #[test]
1508 fn test_days_until4() {
1509 let date = Date::parse("1400.1.2").unwrap();
1510 let date2 = Date::parse("1400.1.2").unwrap();
1511 assert_eq!(0, date.days_until(&date2));
1512 }
1513
1514 #[test]
1515 fn test_days_until5() {
1516 let date = Date::parse("1400.1.1").unwrap();
1517 let date2 = Date::parse("1401.12.31").unwrap();
1518 assert_eq!(-729, date2.days_until(&date));
1519 }
1520
1521 #[test]
1522 fn test_add_days() {
1523 let date = Date::parse("1400.1.2").unwrap();
1524 let actual = date.add_days(1);
1525 let expected = Date::parse("1400.1.3").unwrap();
1526 assert_eq!(actual, expected);
1527 }
1528
1529 #[test]
1530 fn test_add_days2() {
1531 let date = Date::parse("1400.1.2").unwrap();
1532 let actual = date.add_days(365);
1533 let expected = Date::parse("1401.1.2").unwrap();
1534 assert_eq!(actual, expected);
1535 }
1536
1537 #[test]
1538 fn test_add_days3() {
1539 let date = Date::parse("1400.1.1").unwrap();
1540 let actual = date.add_days(729);
1541 let expected = Date::parse("1401.12.31").unwrap();
1542 assert_eq!(actual, expected);
1543 }
1544
1545 #[test]
1546 fn test_add_days4() {
1547 let date = Date::parse("1400.1.2").unwrap();
1548 let actual = date.add_days(0);
1549 let expected = Date::parse("1400.1.2").unwrap();
1550 assert_eq!(actual, expected);
1551 }
1552
1553 #[test]
1554 fn test_all_days() {
1555 let start = Date::parse("1400.1.1").unwrap();
1556 for i in 0..364 {
1557 let (month, day) = month_day_from_julian(i);
1558 let next = Date::parse(format!("1400.{}.{}", month, day)).unwrap();
1559 assert_eq!(start.add_days(i), next);
1560 assert_eq!(start.days_until(&next), i);
1561 }
1562 }
1563
1564 #[test]
1565 fn test_cmp() {
1566 let date = Date::parse("1457.3.5").unwrap();
1567 let date2 = Date::parse("1457.3.4").unwrap();
1568 assert!(date2 < date);
1569 }
1570
1571 #[test]
1572 fn test_binary_date_regression() {
1573 let input = i32::from_le_bytes([14, 54, 43, 253]);
1574 let _ = Date::from_binary(input);
1575 }
1576
1577 #[test]
1578 fn test_day_overflow_regression() {
1579 let _ = Date::from_ymd_opt(1222, 12, 222);
1580 }
1581
1582 #[test]
1583 fn test_date_days() {
1584 let date = Date::parse("1.1.1").unwrap();
1585 assert_eq!(date.days(), 365);
1586
1587 let date = Date::parse("-1.1.1").unwrap();
1588 assert_eq!(date.days(), -365);
1589
1590 let date = Date::parse("-1.1.2").unwrap();
1591 assert_eq!(date.days(), -366);
1592
1593 let date = Date::parse("-1.2.2").unwrap();
1594 assert_eq!(date.days(), -397);
1595 }
1596
1597 #[test]
1598 fn test_negative_date_math() {
1599 let date = Date::parse("-1.1.2").unwrap();
1600 let d1 = date.add_days(1);
1601 assert_eq!(d1.game_fmt().to_string(), "-1.1.1");
1602 assert_eq!(date.days_until(&d1), 1);
1603
1604 let date = Date::parse("-3.6.3").unwrap();
1605 let d1 = date.add_days(1);
1606 assert_eq!(d1.game_fmt().to_string(), "-3.6.2");
1607 assert_eq!(date.days_until(&d1), 1);
1608 }
1609
1610 #[test]
1611 fn test_datehour_roundtrip() {
1612 let date = DateHour::parse("1936.1.1.24").unwrap();
1613 assert_eq!(date.iso_8601().to_string(), String::from("1936-01-01T23"));
1614 }
1615
1616 #[test]
1617 fn test_date_zeros_hour() {
1618 let data = i32::from_le_bytes([0x4b, 0x1d, 0x9f, 0x03]);
1619 let date = Date::from_binary(data).unwrap();
1620 let date_hour = DateHour::from_binary(data).unwrap();
1621 assert_eq!(date.iso_8601().to_string(), String::from("1936-01-01"));
1622 assert_eq!(
1623 date_hour.iso_8601().to_string(),
1624 String::from("1936-01-01T11")
1625 );
1626 }
1627
1628 #[test]
1629 fn test_non_zero_binary_hours_are_not_heuristic_dates() {
1630 let data = i32::from_le_bytes([0x4b, 0x1d, 0x9f, 0x03]);
1631 assert_eq!(Date::from_binary_heuristic(data), None);
1632 }
1633
1634 #[test]
1635 fn test_date_disallow_hour_parse_str() {
1636 assert!(Date::parse("1936.1.1.0").is_err())
1637 }
1638
1639 #[test]
1640 fn test_date_state_of_wide_number() {
1641 assert_eq!(Date::parse("1936.01.01"), Ok(Date::from_ymd(1936, 1, 1)));
1642 assert_eq!(
1643 DateHour::parse("1936.01.01.12"),
1644 Ok(DateHour::from_ymdh(1936, 1, 1, 12))
1645 );
1646 }
1647
1648 #[test]
1649 fn test_date_to_binary() {
1650 let date = Date::from_ymd(1, 1, 1);
1651 let bin = date.to_binary();
1652 assert_eq!(Date::from_binary(bin).unwrap(), date);
1653 assert_eq!(Date::from_binary_heuristic(bin).unwrap(), date);
1654 }
1655
1656 #[test]
1657 fn test_date_hour_to_binary() {
1658 let date = DateHour::from_ymdh(1, 1, 1, 1);
1659 let bin = date.to_binary();
1660 assert_eq!(DateHour::from_binary(bin).unwrap(), date);
1661 assert_eq!(DateHour::from_binary_heuristic(bin).unwrap(), date);
1662
1663 assert_eq!(DateHour::from_binary_heuristic(1), None);
1664 }
1665
1666 #[test]
1667 fn test_uniform_date() {
1668 let date = UniformDate::from_ymd(2205, 2, 30);
1669 assert_eq!(date.iso_8601().to_string(), String::from("2205-02-30"));
1670 assert_eq!(date.game_fmt().to_string(), String::from("2205.02.30"));
1671
1672 let date2 = UniformDate::parse("2205.02.30").unwrap();
1673 assert_eq!(date, date2);
1674
1675 let date3 = UniformDate::parse("1.01.01").unwrap();
1676 assert_eq!(date3.game_fmt().to_string(), String::from("1.01.01"));
1677 }
1678
1679 #[test]
1680 fn test_date_converted_into_number() {
1681 assert!(RawDate::parse(b"43808760").is_err());
1684 assert_eq!(Date::parse(b"43808760").unwrap(), Date::from_ymd(1, 1, 1));
1685 }
1686
1687 #[test]
1688 fn test_from_str_impl() {
1689 let _date: RawDate = "1444.11.11".parse().unwrap();
1690 let _date: Date = "1444.11.11".parse().unwrap();
1691 let _date: DateHour = "1936.1.1.1".parse().unwrap();
1692 let _date: UniformDate = "2200.01.01".parse().unwrap();
1693 }
1694
1695 #[test]
1696 fn test_date_hour_negative_regression() {
1697 assert!(Date::from_binary(-1).is_none());
1698 assert!(DateHour::from_binary(-1).is_none());
1699 assert!(Date::from_binary(-24).is_none());
1700 }
1701
1702 #[test]
1703 fn test_date_parse_edge_cases() {
1704 assert!(Date::parse("05.5.3`.3").is_err());
1705 }
1706
1707 #[test]
1708 fn test_memory_size() {
1709 assert!(std::mem::size_of::<Date>() <= 2 * std::mem::size_of::<usize>());
1711 }
1712
1713 #[quickcheck]
1714 fn test_binary_date_equality(data: i32) -> bool {
1715 Date::from_binary(data)
1716 .map(|x| x == Date::from_binary(x.to_binary()).unwrap())
1717 .unwrap_or(true)
1718 }
1719
1720 #[quickcheck]
1721 fn test_binary_date_hour_equality(data: i32) -> bool {
1722 DateHour::from_binary(data)
1723 .map(|x| x == DateHour::from_binary(x.to_binary()).unwrap())
1724 .unwrap_or(true)
1725 }
1726}