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