1use chrono::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone};
5use snafu::{Backtrace, OptionExt, ResultExt, Snafu};
6
7use crate::value::deserialize::{
8 parse_date_partial, parse_datetime_partial, parse_time_partial, Error as DeserializeError,
9};
10use crate::value::partial::{DicomDate, DicomDateTime, DicomTime, PreciseDateTime};
11
12#[derive(Debug, Snafu)]
13#[non_exhaustive]
14pub enum Error {
15 #[snafu(display("Unexpected end of element"))]
16 UnexpectedEndOfElement { backtrace: Backtrace },
17 #[snafu(display("Failed to parse value"))]
18 Parse {
19 #[snafu(backtrace)]
20 source: DeserializeError,
21 },
22 #[snafu(display("End {} is before start {}", end, start))]
23 RangeInversion {
24 start: String,
25 end: String,
26 backtrace: Backtrace,
27 },
28 #[snafu(display("No range separator present"))]
29 NoRangeSeparator { backtrace: Backtrace },
30 #[snafu(display("Date-time range can contain 1-3 '-' characters, {} were found", value))]
31 SeparatorCount { value: usize, backtrace: Backtrace },
32 #[snafu(display("Converting a time-zone naive value '{naive}' to a time-zone '{offset}' leads to invalid date-time or ambiguous results."))]
33 InvalidDateTime {
34 naive: NaiveDateTime,
35 offset: FixedOffset,
36 backtrace: Backtrace,
37 },
38 #[snafu(display(
39 "Cannot convert from an imprecise value. This value represents a date / time range"
40 ))]
41 ImpreciseValue { backtrace: Backtrace },
42 #[snafu(display("Failed to construct Date from '{y}-{m}-{d}'"))]
43 InvalidDate {
44 y: i32,
45 m: u32,
46 d: u32,
47 backtrace: Backtrace,
48 },
49 #[snafu(display("Failed to construct Time from {h}:{m}:{s}"))]
50 InvalidTime {
51 h: u32,
52 m: u32,
53 s: u32,
54 backtrace: Backtrace,
55 },
56 #[snafu(display("Failed to construct Time from {h}:{m}:{s}:{f}"))]
57 InvalidTimeMicro {
58 h: u32,
59 m: u32,
60 s: u32,
61 f: u32,
62 backtrace: Backtrace,
63 },
64 #[snafu(display("Use 'to_precise_datetime' to retrieve a precise value from a date-time"))]
65 ToPreciseDateTime { backtrace: Backtrace },
66 #[snafu(display(
67 "Parsing a date-time range from '{start}' to '{end}' with only one time-zone '{time_zone} value, second time-zone is missing.'"
68 ))]
69 AmbiguousDtRange {
70 start: NaiveDateTime,
71 end: NaiveDateTime,
72 time_zone: FixedOffset,
73 backtrace: Backtrace,
74 },
75}
76type Result<T, E = Error> = std::result::Result<T, E>;
77
78pub trait AsRange {
158 type PreciseValue: PartialEq + PartialOrd;
159 type Range;
160
161 fn is_precise(&self) -> bool;
163
164 fn exact(&self) -> Result<Self::PreciseValue> {
166 if self.is_precise() {
167 Ok(self.earliest()?)
168 } else {
169 ImpreciseValueSnafu.fail()
170 }
171 }
172
173 fn earliest(&self) -> Result<Self::PreciseValue>;
177
178 fn latest(&self) -> Result<Self::PreciseValue>;
181
182 fn range(&self) -> Result<Self::Range>;
184}
185
186impl AsRange for DicomDate {
187 type PreciseValue = NaiveDate;
188 type Range = DateRange;
189
190 fn is_precise(&self) -> bool {
191 self.day().is_some()
192 }
193
194 fn earliest(&self) -> Result<Self::PreciseValue> {
195 let (y, m, d) = {
196 (
197 *self.year() as i32,
198 *self.month().unwrap_or(&1) as u32,
199 *self.day().unwrap_or(&1) as u32,
200 )
201 };
202 NaiveDate::from_ymd_opt(y, m, d).context(InvalidDateSnafu { y, m, d })
203 }
204
205 fn latest(&self) -> Result<Self::PreciseValue> {
206 let (y, m, d) = (
207 self.year(),
208 self.month().unwrap_or(&12),
209 match self.day() {
210 Some(d) => *d as u32,
211 None => {
212 let y = self.year();
213 let m = self.month().unwrap_or(&12);
214 if m == &12 {
215 NaiveDate::from_ymd_opt(*y as i32 + 1, 1, 1).context(InvalidDateSnafu {
216 y: *y as i32,
217 m: 1u32,
218 d: 1u32,
219 })?
220 } else {
221 NaiveDate::from_ymd_opt(*y as i32, *m as u32 + 1, 1).context(
222 InvalidDateSnafu {
223 y: *y as i32,
224 m: *m as u32,
225 d: 1u32,
226 },
227 )?
228 }
229 .signed_duration_since(
230 NaiveDate::from_ymd_opt(*y as i32, *m as u32, 1).context(
231 InvalidDateSnafu {
232 y: *y as i32,
233 m: *m as u32,
234 d: 1u32,
235 },
236 )?,
237 )
238 .num_days() as u32
239 }
240 },
241 );
242
243 NaiveDate::from_ymd_opt(*y as i32, *m as u32, d).context(InvalidDateSnafu {
244 y: *y as i32,
245 m: *m as u32,
246 d,
247 })
248 }
249
250 fn range(&self) -> Result<Self::Range> {
251 let start = self.earliest()?;
252 let end = self.latest()?;
253 DateRange::from_start_to_end(start, end)
254 }
255}
256
257impl AsRange for DicomTime {
258 type PreciseValue = NaiveTime;
259 type Range = TimeRange;
260
261 fn is_precise(&self) -> bool {
262 matches!(self.fraction_and_precision(), Some((_fr_, precision)) if precision == 6)
263 }
264
265 fn earliest(&self) -> Result<Self::PreciseValue> {
266 let (h, m, s, f) = (
267 self.hour(),
268 self.minute().unwrap_or(&0),
269 self.second().unwrap_or(&0),
270 match self.fraction_and_precision() {
271 None => 0,
272 Some((f, fp)) => f * u32::pow(10, 6 - <u32>::from(fp)),
273 },
274 );
275
276 NaiveTime::from_hms_micro_opt((*h).into(), (*m).into(), (*s).into(), f).context(
277 InvalidTimeMicroSnafu {
278 h: *h as u32,
279 m: *m as u32,
280 s: *s as u32,
281 f,
282 },
283 )
284 }
285 fn latest(&self) -> Result<Self::PreciseValue> {
286 let (h, m, s, f) = (
287 self.hour(),
288 self.minute().unwrap_or(&59),
289 self.second().unwrap_or(&59),
290 match self.fraction_and_precision() {
291 None => 999_999,
292 Some((f, fp)) => {
293 (f * u32::pow(10, 6 - u32::from(fp))) + (u32::pow(10, 6 - u32::from(fp))) - 1
294 }
295 },
296 );
297 NaiveTime::from_hms_micro_opt((*h).into(), (*m).into(), (*s).into(), f).context(
298 InvalidTimeMicroSnafu {
299 h: *h as u32,
300 m: *m as u32,
301 s: *s as u32,
302 f,
303 },
304 )
305 }
306 fn range(&self) -> Result<Self::Range> {
307 let start = self.earliest()?;
308 let end = self.latest()?;
309 TimeRange::from_start_to_end(start, end)
310 }
311}
312
313impl AsRange for DicomDateTime {
314 type PreciseValue = PreciseDateTime;
315 type Range = DateTimeRange;
316
317 fn is_precise(&self) -> bool {
318 match self.time() {
319 Some(dicom_time) => dicom_time.is_precise(),
320 None => false,
321 }
322 }
323
324 fn earliest(&self) -> Result<Self::PreciseValue> {
325 let date = self.date().earliest()?;
326 let time = match self.time() {
327 Some(time) => time.earliest()?,
328 None => NaiveTime::from_hms_opt(0, 0, 0).context(InvalidTimeSnafu {
329 h: 0u32,
330 m: 0u32,
331 s: 0u32,
332 })?,
333 };
334
335 match self.time_zone() {
336 Some(offset) => Ok(PreciseDateTime::TimeZone(
337 offset
338 .from_local_datetime(&NaiveDateTime::new(date, time))
339 .single()
340 .context(InvalidDateTimeSnafu {
341 naive: NaiveDateTime::new(date, time),
342 offset: *offset,
343 })?,
344 )),
345 None => Ok(PreciseDateTime::Naive(NaiveDateTime::new(date, time))),
346 }
347 }
348
349 fn latest(&self) -> Result<Self::PreciseValue> {
350 let date = self.date().latest()?;
351 let time = match self.time() {
352 Some(time) => time.latest()?,
353 None => NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).context(
354 InvalidTimeMicroSnafu {
355 h: 23u32,
356 m: 59u32,
357 s: 59u32,
358 f: 999_999u32,
359 },
360 )?,
361 };
362
363 match self.time_zone() {
364 Some(offset) => Ok(PreciseDateTime::TimeZone(
365 offset
366 .from_local_datetime(&NaiveDateTime::new(date, time))
367 .single()
368 .context(InvalidDateTimeSnafu {
369 naive: NaiveDateTime::new(date, time),
370 offset: *offset,
371 })?,
372 )),
373 None => Ok(PreciseDateTime::Naive(NaiveDateTime::new(date, time))),
374 }
375 }
376 fn range(&self) -> Result<Self::Range> {
377 let start = self.earliest()?;
378 let end = self.latest()?;
379
380 match (start, end) {
381 (PreciseDateTime::Naive(start), PreciseDateTime::Naive(end)) => {
382 DateTimeRange::from_start_to_end(start, end)
383 }
384 (PreciseDateTime::TimeZone(start), PreciseDateTime::TimeZone(end)) => {
385 DateTimeRange::from_start_to_end_with_time_zone(start, end)
386 }
387
388 _ => unreachable!(),
389 }
390 }
391}
392
393impl DicomDate {
394 pub fn to_naive_date(self) -> Result<NaiveDate> {
397 self.exact()
398 }
399}
400
401impl DicomTime {
402 pub fn to_naive_time(self) -> Result<NaiveTime> {
407 if self.second().is_some() {
408 self.earliest()
409 } else {
410 ImpreciseValueSnafu.fail()
411 }
412 }
413}
414
415impl DicomDateTime {
416 pub fn to_precise_datetime(&self) -> Result<PreciseDateTime> {
420 self.exact()
421 }
422}
423
424#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
437pub struct DateRange {
438 start: Option<NaiveDate>,
439 end: Option<NaiveDate>,
440}
441#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
454pub struct TimeRange {
455 start: Option<NaiveTime>,
456 end: Option<NaiveTime>,
457}
458#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
488pub enum DateTimeRange {
489 Naive {
491 start: Option<NaiveDateTime>,
492 end: Option<NaiveDateTime>,
493 },
494 TimeZone {
496 start: Option<DateTime<FixedOffset>>,
497 end: Option<DateTime<FixedOffset>>,
498 },
499}
500
501impl DateRange {
502 pub fn from_start_to_end(start: NaiveDate, end: NaiveDate) -> Result<DateRange> {
505 if start > end {
506 RangeInversionSnafu {
507 start: start.to_string(),
508 end: end.to_string(),
509 }
510 .fail()
511 } else {
512 Ok(DateRange {
513 start: Some(start),
514 end: Some(end),
515 })
516 }
517 }
518
519 pub fn from_start(start: NaiveDate) -> DateRange {
522 DateRange {
523 start: Some(start),
524 end: None,
525 }
526 }
527
528 pub fn from_end(end: NaiveDate) -> DateRange {
530 DateRange {
531 start: None,
532 end: Some(end),
533 }
534 }
535
536 pub fn start(&self) -> Option<&NaiveDate> {
538 self.start.as_ref()
539 }
540
541 pub fn end(&self) -> Option<&NaiveDate> {
543 self.end.as_ref()
544 }
545}
546
547impl TimeRange {
548 pub fn from_start_to_end(start: NaiveTime, end: NaiveTime) -> Result<TimeRange> {
551 if start > end {
552 RangeInversionSnafu {
553 start: start.to_string(),
554 end: end.to_string(),
555 }
556 .fail()
557 } else {
558 Ok(TimeRange {
559 start: Some(start),
560 end: Some(end),
561 })
562 }
563 }
564
565 pub fn from_start(start: NaiveTime) -> TimeRange {
568 TimeRange {
569 start: Some(start),
570 end: None,
571 }
572 }
573
574 pub fn from_end(end: NaiveTime) -> TimeRange {
576 TimeRange {
577 start: None,
578 end: Some(end),
579 }
580 }
581
582 pub fn start(&self) -> Option<&NaiveTime> {
584 self.start.as_ref()
585 }
586
587 pub fn end(&self) -> Option<&NaiveTime> {
589 self.end.as_ref()
590 }
591}
592
593impl DateTimeRange {
594 pub fn from_start_to_end_with_time_zone(
597 start: DateTime<FixedOffset>,
598 end: DateTime<FixedOffset>,
599 ) -> Result<DateTimeRange> {
600 if start > end {
601 RangeInversionSnafu {
602 start: start.to_string(),
603 end: end.to_string(),
604 }
605 .fail()
606 } else {
607 Ok(DateTimeRange::TimeZone {
608 start: Some(start),
609 end: Some(end),
610 })
611 }
612 }
613
614 pub fn from_start_to_end(start: NaiveDateTime, end: NaiveDateTime) -> Result<DateTimeRange> {
617 if start > end {
618 RangeInversionSnafu {
619 start: start.to_string(),
620 end: end.to_string(),
621 }
622 .fail()
623 } else {
624 Ok(DateTimeRange::Naive {
625 start: Some(start),
626 end: Some(end),
627 })
628 }
629 }
630
631 pub fn from_start_with_time_zone(start: DateTime<FixedOffset>) -> DateTimeRange {
634 DateTimeRange::TimeZone {
635 start: Some(start),
636 end: None,
637 }
638 }
639
640 pub fn from_start(start: NaiveDateTime) -> DateTimeRange {
643 DateTimeRange::Naive {
644 start: Some(start),
645 end: None,
646 }
647 }
648
649 pub fn from_end_with_time_zone(end: DateTime<FixedOffset>) -> DateTimeRange {
651 DateTimeRange::TimeZone {
652 start: None,
653 end: Some(end),
654 }
655 }
656
657 pub fn from_end(end: NaiveDateTime) -> DateTimeRange {
659 DateTimeRange::Naive {
660 start: None,
661 end: Some(end),
662 }
663 }
664
665 pub fn start(&self) -> Option<PreciseDateTime> {
667 match self {
668 DateTimeRange::Naive { start, .. } => start.map(PreciseDateTime::Naive),
669 DateTimeRange::TimeZone { start, .. } => start.map(PreciseDateTime::TimeZone),
670 }
671 }
672
673 pub fn end(&self) -> Option<PreciseDateTime> {
675 match self {
676 DateTimeRange::Naive { start: _, end } => end.map(PreciseDateTime::Naive),
677 DateTimeRange::TimeZone { start: _, end } => end.map(PreciseDateTime::TimeZone),
678 }
679 }
680
681 pub fn from_date_and_time_range(dr: DateRange, tr: TimeRange) -> Result<DateTimeRange> {
686 let start_date = dr.start();
687 let end_date = dr.end();
688
689 let start_time = *tr
690 .start()
691 .unwrap_or(&NaiveTime::from_hms_opt(0, 0, 0).context(InvalidTimeSnafu {
692 h: 0u32,
693 m: 0u32,
694 s: 0u32,
695 })?);
696 let end_time =
697 *tr.end()
698 .unwrap_or(&NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).context(
699 InvalidTimeMicroSnafu {
700 h: 23u32,
701 m: 59u32,
702 s: 59u32,
703 f: 999_999u32,
704 },
705 )?);
706
707 match start_date {
708 Some(sd) => match end_date {
709 Some(ed) => Ok(DateTimeRange::from_start_to_end(
710 NaiveDateTime::new(*sd, start_time),
711 NaiveDateTime::new(*ed, end_time),
712 )?),
713 None => Ok(DateTimeRange::from_start(NaiveDateTime::new(
714 *sd, start_time,
715 ))),
716 },
717 None => match end_date {
718 Some(ed) => Ok(DateTimeRange::from_end(NaiveDateTime::new(*ed, end_time))),
719 None => panic!("Impossible combination of two None values for a date range."),
720 },
721 }
722 }
723}
724
725pub fn parse_date_range(buf: &[u8]) -> Result<DateRange> {
730 if buf.len() < 5 {
732 return UnexpectedEndOfElementSnafu.fail();
733 }
734
735 if let Some(separator) = buf.iter().position(|e| *e == b'-') {
736 let (start, end) = buf.split_at(separator);
737 let end = &end[1..];
738 match separator {
739 0 => Ok(DateRange::from_end(
740 parse_date_partial(end).context(ParseSnafu)?.0.latest()?,
741 )),
742 i if i == buf.len() - 1 => Ok(DateRange::from_start(
743 parse_date_partial(start)
744 .context(ParseSnafu)?
745 .0
746 .earliest()?,
747 )),
748 _ => Ok(DateRange::from_start_to_end(
749 parse_date_partial(start)
750 .context(ParseSnafu)?
751 .0
752 .earliest()?,
753 parse_date_partial(end).context(ParseSnafu)?.0.latest()?,
754 )?),
755 }
756 } else {
757 NoRangeSeparatorSnafu.fail()
758 }
759}
760
761pub fn parse_time_range(buf: &[u8]) -> Result<TimeRange> {
764 if buf.len() < 3 {
766 return UnexpectedEndOfElementSnafu.fail();
767 }
768
769 if let Some(separator) = buf.iter().position(|e| *e == b'-') {
770 let (start, end) = buf.split_at(separator);
771 let end = &end[1..];
772 match separator {
773 0 => Ok(TimeRange::from_end(
774 parse_time_partial(end).context(ParseSnafu)?.0.latest()?,
775 )),
776 i if i == buf.len() - 1 => Ok(TimeRange::from_start(
777 parse_time_partial(start)
778 .context(ParseSnafu)?
779 .0
780 .earliest()?,
781 )),
782 _ => Ok(TimeRange::from_start_to_end(
783 parse_time_partial(start)
784 .context(ParseSnafu)?
785 .0
786 .earliest()?,
787 parse_time_partial(end).context(ParseSnafu)?.0.latest()?,
788 )?),
789 }
790 } else {
791 NoRangeSeparatorSnafu.fail()
792 }
793}
794
795pub trait AmbiguousDtRangeParser {
810 fn parse_with_ambiguous_start(
812 ambiguous_start: NaiveDateTime,
813 end: DateTime<FixedOffset>,
814 ) -> Result<DateTimeRange>;
815 fn parse_with_ambiguous_end(
817 start: DateTime<FixedOffset>,
818 ambiguous_end: NaiveDateTime,
819 ) -> Result<DateTimeRange>;
820}
821
822#[derive(Debug)]
834pub struct ToLocalTimeZone;
835
836#[derive(Debug)]
839pub struct ToKnownTimeZone;
840
841#[derive(Debug)]
843pub struct FailOnAmbiguousRange;
844
845#[derive(Debug)]
848pub struct IgnoreTimeZone;
849
850impl AmbiguousDtRangeParser for ToKnownTimeZone {
851 fn parse_with_ambiguous_start(
852 ambiguous_start: NaiveDateTime,
853 end: DateTime<FixedOffset>,
854 ) -> Result<DateTimeRange> {
855 let start = end
856 .offset()
857 .from_local_datetime(&ambiguous_start)
858 .single()
859 .context(InvalidDateTimeSnafu {
860 naive: ambiguous_start,
861 offset: *end.offset(),
862 })?;
863 if start > end {
864 RangeInversionSnafu {
865 start: ambiguous_start.to_string(),
866 end: end.to_string(),
867 }
868 .fail()
869 } else {
870 Ok(DateTimeRange::TimeZone {
871 start: Some(start),
872 end: Some(end),
873 })
874 }
875 }
876 fn parse_with_ambiguous_end(
877 start: DateTime<FixedOffset>,
878 ambiguous_end: NaiveDateTime,
879 ) -> Result<DateTimeRange> {
880 let end = start
881 .offset()
882 .from_local_datetime(&ambiguous_end)
883 .single()
884 .context(InvalidDateTimeSnafu {
885 naive: ambiguous_end,
886 offset: *start.offset(),
887 })?;
888 if start > end {
889 RangeInversionSnafu {
890 start: start.to_string(),
891 end: ambiguous_end.to_string(),
892 }
893 .fail()
894 } else {
895 Ok(DateTimeRange::TimeZone {
896 start: Some(start),
897 end: Some(end),
898 })
899 }
900 }
901}
902
903impl AmbiguousDtRangeParser for FailOnAmbiguousRange {
904 fn parse_with_ambiguous_end(
905 start: DateTime<FixedOffset>,
906 end: NaiveDateTime,
907 ) -> Result<DateTimeRange> {
908 let time_zone = *start.offset();
909 let start = start.naive_local();
910 AmbiguousDtRangeSnafu {
911 start,
912 end,
913 time_zone,
914 }
915 .fail()
916 }
917 fn parse_with_ambiguous_start(
918 start: NaiveDateTime,
919 end: DateTime<FixedOffset>,
920 ) -> Result<DateTimeRange> {
921 let time_zone = *end.offset();
922 let end = end.naive_local();
923 AmbiguousDtRangeSnafu {
924 start,
925 end,
926 time_zone,
927 }
928 .fail()
929 }
930}
931
932impl AmbiguousDtRangeParser for ToLocalTimeZone {
933 fn parse_with_ambiguous_start(
934 ambiguous_start: NaiveDateTime,
935 end: DateTime<FixedOffset>,
936 ) -> Result<DateTimeRange> {
937 let start = Local::now()
938 .offset()
939 .from_local_datetime(&ambiguous_start)
940 .single()
941 .context(InvalidDateTimeSnafu {
942 naive: ambiguous_start,
943 offset: *end.offset(),
944 })?;
945 if start > end {
946 RangeInversionSnafu {
947 start: ambiguous_start.to_string(),
948 end: end.to_string(),
949 }
950 .fail()
951 } else {
952 Ok(DateTimeRange::TimeZone {
953 start: Some(start),
954 end: Some(end),
955 })
956 }
957 }
958 fn parse_with_ambiguous_end(
959 start: DateTime<FixedOffset>,
960 ambiguous_end: NaiveDateTime,
961 ) -> Result<DateTimeRange> {
962 let end = Local::now()
963 .offset()
964 .from_local_datetime(&ambiguous_end)
965 .single()
966 .context(InvalidDateTimeSnafu {
967 naive: ambiguous_end,
968 offset: *start.offset(),
969 })?;
970 if start > end {
971 RangeInversionSnafu {
972 start: start.to_string(),
973 end: ambiguous_end.to_string(),
974 }
975 .fail()
976 } else {
977 Ok(DateTimeRange::TimeZone {
978 start: Some(start),
979 end: Some(end),
980 })
981 }
982 }
983}
984
985impl AmbiguousDtRangeParser for IgnoreTimeZone {
986 fn parse_with_ambiguous_start(
987 ambiguous_start: NaiveDateTime,
988 end: DateTime<FixedOffset>,
989 ) -> Result<DateTimeRange> {
990 let end = end.naive_local();
991 if ambiguous_start > end {
992 RangeInversionSnafu {
993 start: ambiguous_start.to_string(),
994 end: end.to_string(),
995 }
996 .fail()
997 } else {
998 Ok(DateTimeRange::Naive {
999 start: Some(ambiguous_start),
1000 end: Some(end),
1001 })
1002 }
1003 }
1004 fn parse_with_ambiguous_end(
1005 start: DateTime<FixedOffset>,
1006 ambiguous_end: NaiveDateTime,
1007 ) -> Result<DateTimeRange> {
1008 let start = start.naive_local();
1009 if start > ambiguous_end {
1010 RangeInversionSnafu {
1011 start: start.to_string(),
1012 end: ambiguous_end.to_string(),
1013 }
1014 .fail()
1015 } else {
1016 Ok(DateTimeRange::Naive {
1017 start: Some(start),
1018 end: Some(ambiguous_end),
1019 })
1020 }
1021 }
1022}
1023
1024pub fn parse_datetime_range(buf: &[u8]) -> Result<DateTimeRange> {
1049 parse_datetime_range_impl::<ToLocalTimeZone>(buf)
1050}
1051
1052pub fn parse_datetime_range_custom<T: AmbiguousDtRangeParser>(buf: &[u8]) -> Result<DateTimeRange> {
1055 parse_datetime_range_impl::<T>(buf)
1056}
1057
1058pub fn parse_datetime_range_impl<T: AmbiguousDtRangeParser>(buf: &[u8]) -> Result<DateTimeRange> {
1059 if buf.len() < 5 {
1061 return UnexpectedEndOfElementSnafu.fail();
1062 }
1063 if buf[0] == b'-' {
1065 let buf = &buf[1..];
1067 match parse_datetime_partial(buf).context(ParseSnafu)?.latest()? {
1068 PreciseDateTime::Naive(end) => Ok(DateTimeRange::from_end(end)),
1069 PreciseDateTime::TimeZone(end_tz) => Ok(DateTimeRange::from_end_with_time_zone(end_tz)),
1070 }
1071 } else if buf[buf.len() - 1] == b'-' {
1072 let buf = &buf[0..(buf.len() - 1)];
1074 match parse_datetime_partial(buf)
1075 .context(ParseSnafu)?
1076 .earliest()?
1077 {
1078 PreciseDateTime::Naive(start) => Ok(DateTimeRange::from_start(start)),
1079 PreciseDateTime::TimeZone(start_tz) => {
1080 Ok(DateTimeRange::from_start_with_time_zone(start_tz))
1081 }
1082 }
1083 } else {
1084 let dashes: Vec<usize> = buf
1086 .iter()
1087 .enumerate()
1088 .filter(|(_i, c)| **c == b'-')
1089 .map(|(i, _c)| i)
1090 .collect();
1091
1092 let separator = match dashes.len() {
1093 0 => return NoRangeSeparatorSnafu.fail(), 1 => dashes[0], 2 => {
1096 let (start1, end1) = buf.split_at(dashes[0]);
1098
1099 let first = (
1100 parse_datetime_partial(start1),
1101 parse_datetime_partial(&end1[1..]),
1102 );
1103 match first {
1104 (Ok(s), Ok(e)) => {
1106 let dtr = match (s.earliest()?, e.latest()?) {
1108 (PreciseDateTime::Naive(start), PreciseDateTime::Naive(end)) => {
1109 DateTimeRange::from_start_to_end(start, end)
1110 }
1111 (PreciseDateTime::TimeZone(start), PreciseDateTime::TimeZone(end)) => {
1112 DateTimeRange::from_start_to_end_with_time_zone(start, end)
1113 }
1114 (
1115 PreciseDateTime::Naive(start),
1117 PreciseDateTime::TimeZone(end),
1118 ) => T::parse_with_ambiguous_start(start, end),
1119 (
1120 PreciseDateTime::TimeZone(start),
1121 PreciseDateTime::Naive(end),
1123 ) => T::parse_with_ambiguous_end(start, end),
1124 };
1125 match dtr {
1126 Ok(val) => return Ok(val),
1127 Err(_) => dashes[1],
1128 }
1129 }
1130 _ => dashes[1],
1131 }
1132 }
1133 3 => dashes[1], len => return SeparatorCountSnafu { value: len }.fail(),
1135 };
1136
1137 let (start, end) = buf.split_at(separator);
1138 let end = &end[1..];
1139
1140 match (
1141 parse_datetime_partial(start)
1142 .context(ParseSnafu)?
1143 .earliest()?,
1144 parse_datetime_partial(end).context(ParseSnafu)?.latest()?,
1145 ) {
1146 (PreciseDateTime::Naive(start), PreciseDateTime::Naive(end)) => {
1147 DateTimeRange::from_start_to_end(start, end)
1148 }
1149 (PreciseDateTime::TimeZone(start), PreciseDateTime::TimeZone(end)) => {
1150 DateTimeRange::from_start_to_end_with_time_zone(start, end)
1151 }
1152 (PreciseDateTime::Naive(start), PreciseDateTime::TimeZone(end)) => {
1154 T::parse_with_ambiguous_start(start, end)
1155 }
1156 (PreciseDateTime::TimeZone(start), PreciseDateTime::Naive(end)) => {
1158 T::parse_with_ambiguous_end(start, end)
1159 }
1160 }
1161 }
1162}
1163
1164#[cfg(test)]
1165mod tests {
1166 use super::*;
1167
1168 #[test]
1169 fn test_date_range() {
1170 assert_eq!(
1171 DateRange::from_start(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap()).start(),
1172 Some(&NaiveDate::from_ymd_opt(2020, 1, 1).unwrap())
1173 );
1174 assert_eq!(
1175 DateRange::from_end(NaiveDate::from_ymd_opt(2020, 12, 31).unwrap()).end(),
1176 Some(&NaiveDate::from_ymd_opt(2020, 12, 31).unwrap())
1177 );
1178 assert_eq!(
1179 DateRange::from_start_to_end(
1180 NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
1181 NaiveDate::from_ymd_opt(2020, 12, 31).unwrap()
1182 )
1183 .unwrap()
1184 .start(),
1185 Some(&NaiveDate::from_ymd_opt(2020, 1, 1).unwrap())
1186 );
1187 assert_eq!(
1188 DateRange::from_start_to_end(
1189 NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
1190 NaiveDate::from_ymd_opt(2020, 12, 31).unwrap()
1191 )
1192 .unwrap()
1193 .end(),
1194 Some(&NaiveDate::from_ymd_opt(2020, 12, 31).unwrap())
1195 );
1196 assert!(matches!(
1197 DateRange::from_start_to_end(
1198 NaiveDate::from_ymd_opt(2020, 12, 1).unwrap(),
1199 NaiveDate::from_ymd_opt(2020, 1, 1).unwrap()
1200 ),
1201 Err(Error::RangeInversion {
1202 start, end ,.. }) if start == "2020-12-01" && end == "2020-01-01"
1203 ));
1204 }
1205
1206 #[test]
1207 fn test_time_range() {
1208 assert_eq!(
1209 TimeRange::from_start(NaiveTime::from_hms_opt(5, 5, 5).unwrap()).start(),
1210 Some(&NaiveTime::from_hms_opt(5, 5, 5).unwrap())
1211 );
1212 assert_eq!(
1213 TimeRange::from_end(NaiveTime::from_hms_opt(5, 5, 5).unwrap()).end(),
1214 Some(&NaiveTime::from_hms_opt(5, 5, 5).unwrap())
1215 );
1216 assert_eq!(
1217 TimeRange::from_start_to_end(
1218 NaiveTime::from_hms_opt(5, 5, 5).unwrap(),
1219 NaiveTime::from_hms_opt(5, 5, 6).unwrap()
1220 )
1221 .unwrap()
1222 .start(),
1223 Some(&NaiveTime::from_hms_opt(5, 5, 5).unwrap())
1224 );
1225 assert_eq!(
1226 TimeRange::from_start_to_end(
1227 NaiveTime::from_hms_opt(5, 5, 5).unwrap(),
1228 NaiveTime::from_hms_opt(5, 5, 6).unwrap()
1229 )
1230 .unwrap()
1231 .end(),
1232 Some(&NaiveTime::from_hms_opt(5, 5, 6).unwrap())
1233 );
1234 assert!(matches!(
1235 TimeRange::from_start_to_end(
1236 NaiveTime::from_hms_micro_opt(5, 5, 5, 123_456).unwrap(),
1237 NaiveTime::from_hms_micro_opt(5, 5, 5, 123_450).unwrap()
1238 ),
1239 Err(Error::RangeInversion {
1240 start, end ,.. }) if start == "05:05:05.123456" && end == "05:05:05.123450"
1241 ));
1242 }
1243
1244 #[test]
1245 fn test_datetime_range_with_time_zone() {
1246 let offset = FixedOffset::west_opt(3600).unwrap();
1247
1248 assert_eq!(
1249 DateTimeRange::from_start_with_time_zone(
1250 offset
1251 .from_local_datetime(&NaiveDateTime::new(
1252 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1253 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1254 ))
1255 .unwrap()
1256 )
1257 .start(),
1258 Some(PreciseDateTime::TimeZone(
1259 offset
1260 .from_local_datetime(&NaiveDateTime::new(
1261 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1262 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1263 ))
1264 .unwrap()
1265 ))
1266 );
1267 assert_eq!(
1268 DateTimeRange::from_end_with_time_zone(
1269 offset
1270 .from_local_datetime(&NaiveDateTime::new(
1271 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1272 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1273 ))
1274 .unwrap()
1275 )
1276 .end(),
1277 Some(PreciseDateTime::TimeZone(
1278 offset
1279 .from_local_datetime(&NaiveDateTime::new(
1280 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1281 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1282 ))
1283 .unwrap()
1284 ))
1285 );
1286 assert_eq!(
1287 DateTimeRange::from_start_to_end_with_time_zone(
1288 offset
1289 .from_local_datetime(&NaiveDateTime::new(
1290 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1291 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1292 ))
1293 .unwrap(),
1294 offset
1295 .from_local_datetime(&NaiveDateTime::new(
1296 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1297 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1298 ))
1299 .unwrap()
1300 )
1301 .unwrap()
1302 .start(),
1303 Some(PreciseDateTime::TimeZone(
1304 offset
1305 .from_local_datetime(&NaiveDateTime::new(
1306 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1307 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1308 ))
1309 .unwrap()
1310 ))
1311 );
1312 assert_eq!(
1313 DateTimeRange::from_start_to_end_with_time_zone(
1314 offset
1315 .from_local_datetime(&NaiveDateTime::new(
1316 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1317 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1318 ))
1319 .unwrap(),
1320 offset
1321 .from_local_datetime(&NaiveDateTime::new(
1322 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1323 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1324 ))
1325 .unwrap()
1326 )
1327 .unwrap()
1328 .end(),
1329 Some(PreciseDateTime::TimeZone(
1330 offset
1331 .from_local_datetime(&NaiveDateTime::new(
1332 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1333 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1334 ))
1335 .unwrap()
1336 ))
1337 );
1338 assert!(matches!(
1339 DateTimeRange::from_start_to_end_with_time_zone(
1340 offset
1341 .from_local_datetime(&NaiveDateTime::new(
1342 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1343 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1344 ))
1345 .unwrap(),
1346 offset
1347 .from_local_datetime(&NaiveDateTime::new(
1348 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1349 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1350 ))
1351 .unwrap()
1352 )
1353 ,
1354 Err(Error::RangeInversion {
1355 start, end ,.. })
1356 if start == "1990-01-01 01:01:01.000005 -01:00" &&
1357 end == "1990-01-01 01:01:01.000001 -01:00"
1358 ));
1359 }
1360
1361 #[test]
1362 fn test_datetime_range_naive() {
1363 assert_eq!(
1364 DateTimeRange::from_start(NaiveDateTime::new(
1365 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1366 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1367 ))
1368 .start(),
1369 Some(PreciseDateTime::Naive(NaiveDateTime::new(
1370 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1371 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1372 )))
1373 );
1374 assert_eq!(
1375 DateTimeRange::from_end(NaiveDateTime::new(
1376 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1377 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1378 ))
1379 .end(),
1380 Some(PreciseDateTime::Naive(NaiveDateTime::new(
1381 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1382 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1383 )))
1384 );
1385 assert_eq!(
1386 DateTimeRange::from_start_to_end(
1387 NaiveDateTime::new(
1388 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1389 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1390 ),
1391 NaiveDateTime::new(
1392 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1393 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1394 )
1395 )
1396 .unwrap()
1397 .start(),
1398 Some(PreciseDateTime::Naive(NaiveDateTime::new(
1399 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1400 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1401 )))
1402 );
1403 assert_eq!(
1404 DateTimeRange::from_start_to_end(
1405 NaiveDateTime::new(
1406 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1407 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1408 ),
1409 NaiveDateTime::new(
1410 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1411 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1412 )
1413 )
1414 .unwrap()
1415 .end(),
1416 Some(PreciseDateTime::Naive(NaiveDateTime::new(
1417 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1418 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1419 )))
1420 );
1421 assert!(matches!(
1422 DateTimeRange::from_start_to_end(
1423 NaiveDateTime::new(
1424 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1425 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1426 ),
1427 NaiveDateTime::new(
1428 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1429 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1430 )
1431 )
1432 ,
1433 Err(Error::RangeInversion {
1434 start, end ,.. })
1435 if start == "1990-01-01 01:01:01.000005" &&
1436 end == "1990-01-01 01:01:01.000001"
1437 ));
1438 }
1439
1440 #[test]
1441 fn test_parse_date_range() {
1442 assert_eq!(
1443 parse_date_range(b"-19900201").ok(),
1444 Some(DateRange {
1445 start: None,
1446 end: Some(NaiveDate::from_ymd_opt(1990, 2, 1).unwrap())
1447 })
1448 );
1449 assert_eq!(
1450 parse_date_range(b"-202002").ok(),
1451 Some(DateRange {
1452 start: None,
1453 end: Some(NaiveDate::from_ymd_opt(2020, 2, 29).unwrap())
1454 })
1455 );
1456 assert_eq!(
1457 parse_date_range(b"-0020").ok(),
1458 Some(DateRange {
1459 start: None,
1460 end: Some(NaiveDate::from_ymd_opt(20, 12, 31).unwrap())
1461 })
1462 );
1463 assert_eq!(
1464 parse_date_range(b"0002-").ok(),
1465 Some(DateRange {
1466 start: Some(NaiveDate::from_ymd_opt(2, 1, 1).unwrap()),
1467 end: None
1468 })
1469 );
1470 assert_eq!(
1471 parse_date_range(b"000203-").ok(),
1472 Some(DateRange {
1473 start: Some(NaiveDate::from_ymd_opt(2, 3, 1).unwrap()),
1474 end: None
1475 })
1476 );
1477 assert_eq!(
1478 parse_date_range(b"00020307-").ok(),
1479 Some(DateRange {
1480 start: Some(NaiveDate::from_ymd_opt(2, 3, 7).unwrap()),
1481 end: None
1482 })
1483 );
1484 assert_eq!(
1485 parse_date_range(b"0002-202002 ").ok(),
1486 Some(DateRange {
1487 start: Some(NaiveDate::from_ymd_opt(2, 1, 1).unwrap()),
1488 end: Some(NaiveDate::from_ymd_opt(2020, 2, 29).unwrap())
1489 })
1490 );
1491 assert!(parse_date_range(b"0002").is_err());
1492 assert!(parse_date_range(b"0002x").is_err());
1493 assert!(parse_date_range(b" 2010-2020").is_err());
1494 }
1495
1496 #[test]
1497 fn test_parse_time_range() {
1498 assert_eq!(
1499 parse_time_range(b"-101010.123456789").ok(),
1500 Some(TimeRange {
1501 start: None,
1502 end: Some(NaiveTime::from_hms_micro_opt(10, 10, 10, 123_456).unwrap())
1503 })
1504 );
1505 assert_eq!(
1506 parse_time_range(b"-101010.123 ").ok(),
1507 Some(TimeRange {
1508 start: None,
1509 end: Some(NaiveTime::from_hms_micro_opt(10, 10, 10, 123_999).unwrap())
1510 })
1511 );
1512 assert_eq!(
1513 parse_time_range(b"-01 ").ok(),
1514 Some(TimeRange {
1515 start: None,
1516 end: Some(NaiveTime::from_hms_micro_opt(1, 59, 59, 999_999).unwrap())
1517 })
1518 );
1519 assert_eq!(
1520 parse_time_range(b"101010.123456-").ok(),
1521 Some(TimeRange {
1522 start: Some(NaiveTime::from_hms_micro_opt(10, 10, 10, 123_456).unwrap()),
1523 end: None
1524 })
1525 );
1526 assert_eq!(
1527 parse_time_range(b"101010.123-").ok(),
1528 Some(TimeRange {
1529 start: Some(NaiveTime::from_hms_micro_opt(10, 10, 10, 123_000).unwrap()),
1530 end: None
1531 })
1532 );
1533 assert_eq!(
1534 parse_time_range(b"1010-").ok(),
1535 Some(TimeRange {
1536 start: Some(NaiveTime::from_hms_opt(10, 10, 0).unwrap()),
1537 end: None
1538 })
1539 );
1540 assert_eq!(
1541 parse_time_range(b"00-").ok(),
1542 Some(TimeRange {
1543 start: Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap()),
1544 end: None
1545 })
1546 );
1547 }
1548
1549 #[test]
1550 fn test_parse_datetime_range() {
1551 assert_eq!(
1552 parse_datetime_range(b"-20200229153420.123456").ok(),
1553 Some(DateTimeRange::Naive {
1554 start: None,
1555 end: Some(NaiveDateTime::new(
1556 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1557 NaiveTime::from_hms_micro_opt(15, 34, 20, 123_456).unwrap()
1558 ))
1559 })
1560 );
1561 assert_eq!(
1562 parse_datetime_range(b"-20200229153420.123").ok(),
1563 Some(DateTimeRange::Naive {
1564 start: None,
1565 end: Some(NaiveDateTime::new(
1566 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1567 NaiveTime::from_hms_micro_opt(15, 34, 20, 123_999).unwrap()
1568 ))
1569 })
1570 );
1571 assert_eq!(
1572 parse_datetime_range(b"-20200229153420").ok(),
1573 Some(DateTimeRange::Naive {
1574 start: None,
1575 end: Some(NaiveDateTime::new(
1576 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1577 NaiveTime::from_hms_micro_opt(15, 34, 20, 999_999).unwrap()
1578 ))
1579 })
1580 );
1581 assert_eq!(
1582 parse_datetime_range(b"-2020022915").ok(),
1583 Some(DateTimeRange::Naive {
1584 start: None,
1585 end: Some(NaiveDateTime::new(
1586 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1587 NaiveTime::from_hms_micro_opt(15, 59, 59, 999_999).unwrap()
1588 ))
1589 })
1590 );
1591 assert_eq!(
1592 parse_datetime_range(b"-202002").ok(),
1593 Some(DateTimeRange::Naive {
1594 start: None,
1595 end: Some(NaiveDateTime::new(
1596 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1597 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1598 ))
1599 })
1600 );
1601 assert_eq!(
1602 parse_datetime_range(b"0002-").ok(),
1603 Some(DateTimeRange::Naive {
1604 start: Some(NaiveDateTime::new(
1605 NaiveDate::from_ymd_opt(2, 1, 1).unwrap(),
1606 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1607 )),
1608 end: None
1609 })
1610 );
1611 assert_eq!(
1612 parse_datetime_range(b"00021231-").ok(),
1613 Some(DateTimeRange::Naive {
1614 start: Some(NaiveDateTime::new(
1615 NaiveDate::from_ymd_opt(2, 12, 31).unwrap(),
1616 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1617 )),
1618 end: None
1619 })
1620 );
1621 assert_eq!(
1623 parse_datetime_range(b"19900101+0500-1999+1400").ok(),
1624 Some(DateTimeRange::TimeZone {
1625 start: Some(
1626 FixedOffset::east_opt(5 * 3600)
1627 .unwrap()
1628 .from_local_datetime(&NaiveDateTime::new(
1629 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1630 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1631 ))
1632 .unwrap()
1633 ),
1634 end: Some(
1635 FixedOffset::east_opt(14 * 3600)
1636 .unwrap()
1637 .from_local_datetime(&NaiveDateTime::new(
1638 NaiveDate::from_ymd_opt(1999, 12, 31).unwrap(),
1639 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1640 ))
1641 .unwrap()
1642 )
1643 })
1644 );
1645 assert_eq!(
1647 parse_datetime_range(b"19900101-0500-1999-1200").ok(),
1648 Some(DateTimeRange::TimeZone {
1649 start: Some(
1650 FixedOffset::west_opt(5 * 3600)
1651 .unwrap()
1652 .from_local_datetime(&NaiveDateTime::new(
1653 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1654 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1655 ))
1656 .unwrap()
1657 ),
1658 end: Some(
1659 FixedOffset::west_opt(12 * 3600)
1660 .unwrap()
1661 .from_local_datetime(&NaiveDateTime::new(
1662 NaiveDate::from_ymd_opt(1999, 12, 31).unwrap(),
1663 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1664 ))
1665 .unwrap()
1666 )
1667 })
1668 );
1669 assert_eq!(
1671 parse_datetime_range(b"19900101+1400-1999-1200").ok(),
1672 Some(DateTimeRange::TimeZone {
1673 start: Some(
1674 FixedOffset::east_opt(14 * 3600)
1675 .unwrap()
1676 .from_local_datetime(&NaiveDateTime::new(
1677 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1678 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1679 ))
1680 .unwrap()
1681 ),
1682 end: Some(
1683 FixedOffset::west_opt(12 * 3600)
1684 .unwrap()
1685 .from_local_datetime(&NaiveDateTime::new(
1686 NaiveDate::from_ymd_opt(1999, 12, 31).unwrap(),
1687 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1688 ))
1689 .unwrap()
1690 )
1691 })
1692 );
1693 assert_eq!(
1696 parse_datetime_range(b"19900101-1200-1999").unwrap(),
1697 DateTimeRange::TimeZone {
1698 start: Some(
1699 FixedOffset::west_opt(12 * 3600)
1700 .unwrap()
1701 .from_local_datetime(&NaiveDateTime::new(
1702 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1703 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1704 ))
1705 .unwrap()
1706 ),
1707 end: Some(
1708 Local::now()
1709 .offset()
1710 .from_local_datetime(&NaiveDateTime::new(
1711 NaiveDate::from_ymd_opt(1999, 12, 31).unwrap(),
1712 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1713 ))
1714 .unwrap()
1715 )
1716 }
1717 );
1718 assert_eq!(
1722 parse_datetime_range(b"0050-0500-1000").unwrap(),
1723 DateTimeRange::TimeZone {
1724 start: Some(
1725 Local::now()
1726 .offset()
1727 .from_local_datetime(&NaiveDateTime::new(
1728 NaiveDate::from_ymd_opt(50, 1, 1).unwrap(),
1729 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1730 ))
1731 .unwrap()
1732 ),
1733 end: Some(
1734 FixedOffset::west_opt(10 * 3600)
1735 .unwrap()
1736 .from_local_datetime(&NaiveDateTime::new(
1737 NaiveDate::from_ymd_opt(500, 12, 31).unwrap(),
1738 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1739 ))
1740 .unwrap()
1741 )
1742 }
1743 );
1744 assert!(matches!(
1746 parse_datetime_range(b"0001-00021231-2021-0100-0100"),
1747 Err(Error::SeparatorCount { .. })
1748 ));
1749 assert!(matches!(
1751 parse_datetime_range(b"00021231+0500"),
1752 Err(Error::NoRangeSeparator { .. })
1753 ));
1754 }
1755}