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 #[deprecated(since = "0.7.0", note = "Use `to_precise_date_time()`")]
424 pub fn to_chrono_datetime(self) -> Result<DateTime<FixedOffset>> {
425 ToPreciseDateTimeSnafu.fail()
426 }
427}
428
429#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
442pub struct DateRange {
443 start: Option<NaiveDate>,
444 end: Option<NaiveDate>,
445}
446#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
459pub struct TimeRange {
460 start: Option<NaiveTime>,
461 end: Option<NaiveTime>,
462}
463#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
493pub enum DateTimeRange {
494 Naive {
496 start: Option<NaiveDateTime>,
497 end: Option<NaiveDateTime>,
498 },
499 TimeZone {
501 start: Option<DateTime<FixedOffset>>,
502 end: Option<DateTime<FixedOffset>>,
503 },
504}
505
506impl DateRange {
507 pub fn from_start_to_end(start: NaiveDate, end: NaiveDate) -> Result<DateRange> {
510 if start > end {
511 RangeInversionSnafu {
512 start: start.to_string(),
513 end: end.to_string(),
514 }
515 .fail()
516 } else {
517 Ok(DateRange {
518 start: Some(start),
519 end: Some(end),
520 })
521 }
522 }
523
524 pub fn from_start(start: NaiveDate) -> DateRange {
527 DateRange {
528 start: Some(start),
529 end: None,
530 }
531 }
532
533 pub fn from_end(end: NaiveDate) -> DateRange {
535 DateRange {
536 start: None,
537 end: Some(end),
538 }
539 }
540
541 pub fn start(&self) -> Option<&NaiveDate> {
543 self.start.as_ref()
544 }
545
546 pub fn end(&self) -> Option<&NaiveDate> {
548 self.end.as_ref()
549 }
550}
551
552impl TimeRange {
553 pub fn from_start_to_end(start: NaiveTime, end: NaiveTime) -> Result<TimeRange> {
556 if start > end {
557 RangeInversionSnafu {
558 start: start.to_string(),
559 end: end.to_string(),
560 }
561 .fail()
562 } else {
563 Ok(TimeRange {
564 start: Some(start),
565 end: Some(end),
566 })
567 }
568 }
569
570 pub fn from_start(start: NaiveTime) -> TimeRange {
573 TimeRange {
574 start: Some(start),
575 end: None,
576 }
577 }
578
579 pub fn from_end(end: NaiveTime) -> TimeRange {
581 TimeRange {
582 start: None,
583 end: Some(end),
584 }
585 }
586
587 pub fn start(&self) -> Option<&NaiveTime> {
589 self.start.as_ref()
590 }
591
592 pub fn end(&self) -> Option<&NaiveTime> {
594 self.end.as_ref()
595 }
596}
597
598impl DateTimeRange {
599 pub fn from_start_to_end_with_time_zone(
602 start: DateTime<FixedOffset>,
603 end: DateTime<FixedOffset>,
604 ) -> Result<DateTimeRange> {
605 if start > end {
606 RangeInversionSnafu {
607 start: start.to_string(),
608 end: end.to_string(),
609 }
610 .fail()
611 } else {
612 Ok(DateTimeRange::TimeZone {
613 start: Some(start),
614 end: Some(end),
615 })
616 }
617 }
618
619 pub fn from_start_to_end(start: NaiveDateTime, end: NaiveDateTime) -> Result<DateTimeRange> {
622 if start > end {
623 RangeInversionSnafu {
624 start: start.to_string(),
625 end: end.to_string(),
626 }
627 .fail()
628 } else {
629 Ok(DateTimeRange::Naive {
630 start: Some(start),
631 end: Some(end),
632 })
633 }
634 }
635
636 pub fn from_start_with_time_zone(start: DateTime<FixedOffset>) -> DateTimeRange {
639 DateTimeRange::TimeZone {
640 start: Some(start),
641 end: None,
642 }
643 }
644
645 pub fn from_start(start: NaiveDateTime) -> DateTimeRange {
648 DateTimeRange::Naive {
649 start: Some(start),
650 end: None,
651 }
652 }
653
654 pub fn from_end_with_time_zone(end: DateTime<FixedOffset>) -> DateTimeRange {
656 DateTimeRange::TimeZone {
657 start: None,
658 end: Some(end),
659 }
660 }
661
662 pub fn from_end(end: NaiveDateTime) -> DateTimeRange {
664 DateTimeRange::Naive {
665 start: None,
666 end: Some(end),
667 }
668 }
669
670 pub fn start(&self) -> Option<PreciseDateTime> {
672 match self {
673 DateTimeRange::Naive { start, .. } => start.map(PreciseDateTime::Naive),
674 DateTimeRange::TimeZone { start, .. } => start.map(PreciseDateTime::TimeZone),
675 }
676 }
677
678 pub fn end(&self) -> Option<PreciseDateTime> {
680 match self {
681 DateTimeRange::Naive { start: _, end } => end.map(PreciseDateTime::Naive),
682 DateTimeRange::TimeZone { start: _, end } => end.map(PreciseDateTime::TimeZone),
683 }
684 }
685
686 pub fn from_date_and_time_range(dr: DateRange, tr: TimeRange) -> Result<DateTimeRange> {
691 let start_date = dr.start();
692 let end_date = dr.end();
693
694 let start_time = *tr
695 .start()
696 .unwrap_or(&NaiveTime::from_hms_opt(0, 0, 0).context(InvalidTimeSnafu {
697 h: 0u32,
698 m: 0u32,
699 s: 0u32,
700 })?);
701 let end_time =
702 *tr.end()
703 .unwrap_or(&NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).context(
704 InvalidTimeMicroSnafu {
705 h: 23u32,
706 m: 59u32,
707 s: 59u32,
708 f: 999_999u32,
709 },
710 )?);
711
712 match start_date {
713 Some(sd) => match end_date {
714 Some(ed) => Ok(DateTimeRange::from_start_to_end(
715 NaiveDateTime::new(*sd, start_time),
716 NaiveDateTime::new(*ed, end_time),
717 )?),
718 None => Ok(DateTimeRange::from_start(NaiveDateTime::new(
719 *sd, start_time,
720 ))),
721 },
722 None => match end_date {
723 Some(ed) => Ok(DateTimeRange::from_end(NaiveDateTime::new(*ed, end_time))),
724 None => panic!("Impossible combination of two None values for a date range."),
725 },
726 }
727 }
728}
729
730pub fn parse_date_range(buf: &[u8]) -> Result<DateRange> {
735 if buf.len() < 5 {
737 return UnexpectedEndOfElementSnafu.fail();
738 }
739
740 if let Some(separator) = buf.iter().position(|e| *e == b'-') {
741 let (start, end) = buf.split_at(separator);
742 let end = &end[1..];
743 match separator {
744 0 => Ok(DateRange::from_end(
745 parse_date_partial(end).context(ParseSnafu)?.0.latest()?,
746 )),
747 i if i == buf.len() - 1 => Ok(DateRange::from_start(
748 parse_date_partial(start)
749 .context(ParseSnafu)?
750 .0
751 .earliest()?,
752 )),
753 _ => Ok(DateRange::from_start_to_end(
754 parse_date_partial(start)
755 .context(ParseSnafu)?
756 .0
757 .earliest()?,
758 parse_date_partial(end).context(ParseSnafu)?.0.latest()?,
759 )?),
760 }
761 } else {
762 NoRangeSeparatorSnafu.fail()
763 }
764}
765
766pub fn parse_time_range(buf: &[u8]) -> Result<TimeRange> {
769 if buf.len() < 3 {
771 return UnexpectedEndOfElementSnafu.fail();
772 }
773
774 if let Some(separator) = buf.iter().position(|e| *e == b'-') {
775 let (start, end) = buf.split_at(separator);
776 let end = &end[1..];
777 match separator {
778 0 => Ok(TimeRange::from_end(
779 parse_time_partial(end).context(ParseSnafu)?.0.latest()?,
780 )),
781 i if i == buf.len() - 1 => Ok(TimeRange::from_start(
782 parse_time_partial(start)
783 .context(ParseSnafu)?
784 .0
785 .earliest()?,
786 )),
787 _ => Ok(TimeRange::from_start_to_end(
788 parse_time_partial(start)
789 .context(ParseSnafu)?
790 .0
791 .earliest()?,
792 parse_time_partial(end).context(ParseSnafu)?.0.latest()?,
793 )?),
794 }
795 } else {
796 NoRangeSeparatorSnafu.fail()
797 }
798}
799
800pub trait AmbiguousDtRangeParser {
815 fn parse_with_ambiguous_start(
817 ambiguous_start: NaiveDateTime,
818 end: DateTime<FixedOffset>,
819 ) -> Result<DateTimeRange>;
820 fn parse_with_ambiguous_end(
822 start: DateTime<FixedOffset>,
823 ambiguous_end: NaiveDateTime,
824 ) -> Result<DateTimeRange>;
825}
826
827#[derive(Debug)]
839pub struct ToLocalTimeZone;
840
841#[derive(Debug)]
844pub struct ToKnownTimeZone;
845
846#[derive(Debug)]
848pub struct FailOnAmbiguousRange;
849
850#[derive(Debug)]
853pub struct IgnoreTimeZone;
854
855impl AmbiguousDtRangeParser for ToKnownTimeZone {
856 fn parse_with_ambiguous_start(
857 ambiguous_start: NaiveDateTime,
858 end: DateTime<FixedOffset>,
859 ) -> Result<DateTimeRange> {
860 let start = end
861 .offset()
862 .from_local_datetime(&ambiguous_start)
863 .single()
864 .context(InvalidDateTimeSnafu {
865 naive: ambiguous_start,
866 offset: *end.offset(),
867 })?;
868 if start > end {
869 RangeInversionSnafu {
870 start: ambiguous_start.to_string(),
871 end: end.to_string(),
872 }
873 .fail()
874 } else {
875 Ok(DateTimeRange::TimeZone {
876 start: Some(start),
877 end: Some(end),
878 })
879 }
880 }
881 fn parse_with_ambiguous_end(
882 start: DateTime<FixedOffset>,
883 ambiguous_end: NaiveDateTime,
884 ) -> Result<DateTimeRange> {
885 let end = start
886 .offset()
887 .from_local_datetime(&ambiguous_end)
888 .single()
889 .context(InvalidDateTimeSnafu {
890 naive: ambiguous_end,
891 offset: *start.offset(),
892 })?;
893 if start > end {
894 RangeInversionSnafu {
895 start: start.to_string(),
896 end: ambiguous_end.to_string(),
897 }
898 .fail()
899 } else {
900 Ok(DateTimeRange::TimeZone {
901 start: Some(start),
902 end: Some(end),
903 })
904 }
905 }
906}
907
908impl AmbiguousDtRangeParser for FailOnAmbiguousRange {
909 fn parse_with_ambiguous_end(
910 start: DateTime<FixedOffset>,
911 end: NaiveDateTime,
912 ) -> Result<DateTimeRange> {
913 let time_zone = *start.offset();
914 let start = start.naive_local();
915 AmbiguousDtRangeSnafu {
916 start,
917 end,
918 time_zone,
919 }
920 .fail()
921 }
922 fn parse_with_ambiguous_start(
923 start: NaiveDateTime,
924 end: DateTime<FixedOffset>,
925 ) -> Result<DateTimeRange> {
926 let time_zone = *end.offset();
927 let end = end.naive_local();
928 AmbiguousDtRangeSnafu {
929 start,
930 end,
931 time_zone,
932 }
933 .fail()
934 }
935}
936
937impl AmbiguousDtRangeParser for ToLocalTimeZone {
938 fn parse_with_ambiguous_start(
939 ambiguous_start: NaiveDateTime,
940 end: DateTime<FixedOffset>,
941 ) -> Result<DateTimeRange> {
942 let start = Local::now()
943 .offset()
944 .from_local_datetime(&ambiguous_start)
945 .single()
946 .context(InvalidDateTimeSnafu {
947 naive: ambiguous_start,
948 offset: *end.offset(),
949 })?;
950 if start > end {
951 RangeInversionSnafu {
952 start: ambiguous_start.to_string(),
953 end: end.to_string(),
954 }
955 .fail()
956 } else {
957 Ok(DateTimeRange::TimeZone {
958 start: Some(start),
959 end: Some(end),
960 })
961 }
962 }
963 fn parse_with_ambiguous_end(
964 start: DateTime<FixedOffset>,
965 ambiguous_end: NaiveDateTime,
966 ) -> Result<DateTimeRange> {
967 let end = Local::now()
968 .offset()
969 .from_local_datetime(&ambiguous_end)
970 .single()
971 .context(InvalidDateTimeSnafu {
972 naive: ambiguous_end,
973 offset: *start.offset(),
974 })?;
975 if start > end {
976 RangeInversionSnafu {
977 start: start.to_string(),
978 end: ambiguous_end.to_string(),
979 }
980 .fail()
981 } else {
982 Ok(DateTimeRange::TimeZone {
983 start: Some(start),
984 end: Some(end),
985 })
986 }
987 }
988}
989
990impl AmbiguousDtRangeParser for IgnoreTimeZone {
991 fn parse_with_ambiguous_start(
992 ambiguous_start: NaiveDateTime,
993 end: DateTime<FixedOffset>,
994 ) -> Result<DateTimeRange> {
995 let end = end.naive_local();
996 if ambiguous_start > end {
997 RangeInversionSnafu {
998 start: ambiguous_start.to_string(),
999 end: end.to_string(),
1000 }
1001 .fail()
1002 } else {
1003 Ok(DateTimeRange::Naive {
1004 start: Some(ambiguous_start),
1005 end: Some(end),
1006 })
1007 }
1008 }
1009 fn parse_with_ambiguous_end(
1010 start: DateTime<FixedOffset>,
1011 ambiguous_end: NaiveDateTime,
1012 ) -> Result<DateTimeRange> {
1013 let start = start.naive_local();
1014 if start > ambiguous_end {
1015 RangeInversionSnafu {
1016 start: start.to_string(),
1017 end: ambiguous_end.to_string(),
1018 }
1019 .fail()
1020 } else {
1021 Ok(DateTimeRange::Naive {
1022 start: Some(start),
1023 end: Some(ambiguous_end),
1024 })
1025 }
1026 }
1027}
1028
1029pub fn parse_datetime_range(buf: &[u8]) -> Result<DateTimeRange> {
1054 parse_datetime_range_impl::<ToLocalTimeZone>(buf)
1055}
1056
1057pub fn parse_datetime_range_custom<T: AmbiguousDtRangeParser>(buf: &[u8]) -> Result<DateTimeRange> {
1060 parse_datetime_range_impl::<T>(buf)
1061}
1062
1063pub fn parse_datetime_range_impl<T: AmbiguousDtRangeParser>(buf: &[u8]) -> Result<DateTimeRange> {
1064 if buf.len() < 5 {
1066 return UnexpectedEndOfElementSnafu.fail();
1067 }
1068 if buf[0] == b'-' {
1070 let buf = &buf[1..];
1072 match parse_datetime_partial(buf).context(ParseSnafu)?.latest()? {
1073 PreciseDateTime::Naive(end) => Ok(DateTimeRange::from_end(end)),
1074 PreciseDateTime::TimeZone(end_tz) => Ok(DateTimeRange::from_end_with_time_zone(end_tz)),
1075 }
1076 } else if buf[buf.len() - 1] == b'-' {
1077 let buf = &buf[0..(buf.len() - 1)];
1079 match parse_datetime_partial(buf)
1080 .context(ParseSnafu)?
1081 .earliest()?
1082 {
1083 PreciseDateTime::Naive(start) => Ok(DateTimeRange::from_start(start)),
1084 PreciseDateTime::TimeZone(start_tz) => {
1085 Ok(DateTimeRange::from_start_with_time_zone(start_tz))
1086 }
1087 }
1088 } else {
1089 let dashes: Vec<usize> = buf
1091 .iter()
1092 .enumerate()
1093 .filter(|(_i, c)| **c == b'-')
1094 .map(|(i, _c)| i)
1095 .collect();
1096
1097 let separator = match dashes.len() {
1098 0 => return NoRangeSeparatorSnafu.fail(), 1 => dashes[0], 2 => {
1101 let (start1, end1) = buf.split_at(dashes[0]);
1103
1104 let first = (
1105 parse_datetime_partial(start1),
1106 parse_datetime_partial(&end1[1..]),
1107 );
1108 match first {
1109 (Ok(s), Ok(e)) => {
1111 let dtr = match (s.earliest()?, e.latest()?) {
1113 (PreciseDateTime::Naive(start), PreciseDateTime::Naive(end)) => {
1114 DateTimeRange::from_start_to_end(start, end)
1115 }
1116 (PreciseDateTime::TimeZone(start), PreciseDateTime::TimeZone(end)) => {
1117 DateTimeRange::from_start_to_end_with_time_zone(start, end)
1118 }
1119 (
1120 PreciseDateTime::Naive(start),
1122 PreciseDateTime::TimeZone(end),
1123 ) => T::parse_with_ambiguous_start(start, end),
1124 (
1125 PreciseDateTime::TimeZone(start),
1126 PreciseDateTime::Naive(end),
1128 ) => T::parse_with_ambiguous_end(start, end),
1129 };
1130 match dtr {
1131 Ok(val) => return Ok(val),
1132 Err(_) => dashes[1],
1133 }
1134 }
1135 _ => dashes[1],
1136 }
1137 }
1138 3 => dashes[1], len => return SeparatorCountSnafu { value: len }.fail(),
1140 };
1141
1142 let (start, end) = buf.split_at(separator);
1143 let end = &end[1..];
1144
1145 match (
1146 parse_datetime_partial(start)
1147 .context(ParseSnafu)?
1148 .earliest()?,
1149 parse_datetime_partial(end).context(ParseSnafu)?.latest()?,
1150 ) {
1151 (PreciseDateTime::Naive(start), PreciseDateTime::Naive(end)) => {
1152 DateTimeRange::from_start_to_end(start, end)
1153 }
1154 (PreciseDateTime::TimeZone(start), PreciseDateTime::TimeZone(end)) => {
1155 DateTimeRange::from_start_to_end_with_time_zone(start, end)
1156 }
1157 (PreciseDateTime::Naive(start), PreciseDateTime::TimeZone(end)) => {
1159 T::parse_with_ambiguous_start(start, end)
1160 }
1161 (PreciseDateTime::TimeZone(start), PreciseDateTime::Naive(end)) => {
1163 T::parse_with_ambiguous_end(start, end)
1164 }
1165 }
1166 }
1167}
1168
1169#[cfg(test)]
1170mod tests {
1171 use super::*;
1172
1173 #[test]
1174 fn test_date_range() {
1175 assert_eq!(
1176 DateRange::from_start(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap()).start(),
1177 Some(&NaiveDate::from_ymd_opt(2020, 1, 1).unwrap())
1178 );
1179 assert_eq!(
1180 DateRange::from_end(NaiveDate::from_ymd_opt(2020, 12, 31).unwrap()).end(),
1181 Some(&NaiveDate::from_ymd_opt(2020, 12, 31).unwrap())
1182 );
1183 assert_eq!(
1184 DateRange::from_start_to_end(
1185 NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
1186 NaiveDate::from_ymd_opt(2020, 12, 31).unwrap()
1187 )
1188 .unwrap()
1189 .start(),
1190 Some(&NaiveDate::from_ymd_opt(2020, 1, 1).unwrap())
1191 );
1192 assert_eq!(
1193 DateRange::from_start_to_end(
1194 NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
1195 NaiveDate::from_ymd_opt(2020, 12, 31).unwrap()
1196 )
1197 .unwrap()
1198 .end(),
1199 Some(&NaiveDate::from_ymd_opt(2020, 12, 31).unwrap())
1200 );
1201 assert!(matches!(
1202 DateRange::from_start_to_end(
1203 NaiveDate::from_ymd_opt(2020, 12, 1).unwrap(),
1204 NaiveDate::from_ymd_opt(2020, 1, 1).unwrap()
1205 ),
1206 Err(Error::RangeInversion {
1207 start, end ,.. }) if start == "2020-12-01" && end == "2020-01-01"
1208 ));
1209 }
1210
1211 #[test]
1212 fn test_time_range() {
1213 assert_eq!(
1214 TimeRange::from_start(NaiveTime::from_hms_opt(05, 05, 05).unwrap()).start(),
1215 Some(&NaiveTime::from_hms_opt(05, 05, 05).unwrap())
1216 );
1217 assert_eq!(
1218 TimeRange::from_end(NaiveTime::from_hms_opt(05, 05, 05).unwrap()).end(),
1219 Some(&NaiveTime::from_hms_opt(05, 05, 05).unwrap())
1220 );
1221 assert_eq!(
1222 TimeRange::from_start_to_end(
1223 NaiveTime::from_hms_opt(05, 05, 05).unwrap(),
1224 NaiveTime::from_hms_opt(05, 05, 06).unwrap()
1225 )
1226 .unwrap()
1227 .start(),
1228 Some(&NaiveTime::from_hms_opt(05, 05, 05).unwrap())
1229 );
1230 assert_eq!(
1231 TimeRange::from_start_to_end(
1232 NaiveTime::from_hms_opt(05, 05, 05).unwrap(),
1233 NaiveTime::from_hms_opt(05, 05, 06).unwrap()
1234 )
1235 .unwrap()
1236 .end(),
1237 Some(&NaiveTime::from_hms_opt(05, 05, 06).unwrap())
1238 );
1239 assert!(matches!(
1240 TimeRange::from_start_to_end(
1241 NaiveTime::from_hms_micro_opt(05, 05, 05, 123_456).unwrap(),
1242 NaiveTime::from_hms_micro_opt(05, 05, 05, 123_450).unwrap()
1243 ),
1244 Err(Error::RangeInversion {
1245 start, end ,.. }) if start == "05:05:05.123456" && end == "05:05:05.123450"
1246 ));
1247 }
1248
1249 #[test]
1250 fn test_datetime_range_with_time_zone() {
1251 let offset = FixedOffset::west_opt(3600).unwrap();
1252
1253 assert_eq!(
1254 DateTimeRange::from_start_with_time_zone(
1255 offset
1256 .from_local_datetime(&NaiveDateTime::new(
1257 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1258 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1259 ))
1260 .unwrap()
1261 )
1262 .start(),
1263 Some(PreciseDateTime::TimeZone(
1264 offset
1265 .from_local_datetime(&NaiveDateTime::new(
1266 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1267 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1268 ))
1269 .unwrap()
1270 ))
1271 );
1272 assert_eq!(
1273 DateTimeRange::from_end_with_time_zone(
1274 offset
1275 .from_local_datetime(&NaiveDateTime::new(
1276 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1277 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1278 ))
1279 .unwrap()
1280 )
1281 .end(),
1282 Some(PreciseDateTime::TimeZone(
1283 offset
1284 .from_local_datetime(&NaiveDateTime::new(
1285 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1286 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1287 ))
1288 .unwrap()
1289 ))
1290 );
1291 assert_eq!(
1292 DateTimeRange::from_start_to_end_with_time_zone(
1293 offset
1294 .from_local_datetime(&NaiveDateTime::new(
1295 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1296 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1297 ))
1298 .unwrap(),
1299 offset
1300 .from_local_datetime(&NaiveDateTime::new(
1301 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1302 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1303 ))
1304 .unwrap()
1305 )
1306 .unwrap()
1307 .start(),
1308 Some(PreciseDateTime::TimeZone(
1309 offset
1310 .from_local_datetime(&NaiveDateTime::new(
1311 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1312 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1313 ))
1314 .unwrap()
1315 ))
1316 );
1317 assert_eq!(
1318 DateTimeRange::from_start_to_end_with_time_zone(
1319 offset
1320 .from_local_datetime(&NaiveDateTime::new(
1321 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1322 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1323 ))
1324 .unwrap(),
1325 offset
1326 .from_local_datetime(&NaiveDateTime::new(
1327 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1328 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1329 ))
1330 .unwrap()
1331 )
1332 .unwrap()
1333 .end(),
1334 Some(PreciseDateTime::TimeZone(
1335 offset
1336 .from_local_datetime(&NaiveDateTime::new(
1337 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1338 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1339 ))
1340 .unwrap()
1341 ))
1342 );
1343 assert!(matches!(
1344 DateTimeRange::from_start_to_end_with_time_zone(
1345 offset
1346 .from_local_datetime(&NaiveDateTime::new(
1347 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1348 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1349 ))
1350 .unwrap(),
1351 offset
1352 .from_local_datetime(&NaiveDateTime::new(
1353 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1354 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1355 ))
1356 .unwrap()
1357 )
1358 ,
1359 Err(Error::RangeInversion {
1360 start, end ,.. })
1361 if start == "1990-01-01 01:01:01.000005 -01:00" &&
1362 end == "1990-01-01 01:01:01.000001 -01:00"
1363 ));
1364 }
1365
1366 #[test]
1367 fn test_datetime_range_naive() {
1368 assert_eq!(
1369 DateTimeRange::from_start(NaiveDateTime::new(
1370 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1371 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1372 ))
1373 .start(),
1374 Some(PreciseDateTime::Naive(NaiveDateTime::new(
1375 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1376 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1377 )))
1378 );
1379 assert_eq!(
1380 DateTimeRange::from_end(NaiveDateTime::new(
1381 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1382 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1383 ))
1384 .end(),
1385 Some(PreciseDateTime::Naive(NaiveDateTime::new(
1386 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1387 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1388 )))
1389 );
1390 assert_eq!(
1391 DateTimeRange::from_start_to_end(
1392 NaiveDateTime::new(
1393 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1394 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1395 ),
1396 NaiveDateTime::new(
1397 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1398 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1399 )
1400 )
1401 .unwrap()
1402 .start(),
1403 Some(PreciseDateTime::Naive(NaiveDateTime::new(
1404 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1405 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1406 )))
1407 );
1408 assert_eq!(
1409 DateTimeRange::from_start_to_end(
1410 NaiveDateTime::new(
1411 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1412 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1413 ),
1414 NaiveDateTime::new(
1415 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1416 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1417 )
1418 )
1419 .unwrap()
1420 .end(),
1421 Some(PreciseDateTime::Naive(NaiveDateTime::new(
1422 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1423 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1424 )))
1425 );
1426 assert!(matches!(
1427 DateTimeRange::from_start_to_end(
1428 NaiveDateTime::new(
1429 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1430 NaiveTime::from_hms_micro_opt(1, 1, 1, 5).unwrap()
1431 ),
1432 NaiveDateTime::new(
1433 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1434 NaiveTime::from_hms_micro_opt(1, 1, 1, 1).unwrap()
1435 )
1436 )
1437 ,
1438 Err(Error::RangeInversion {
1439 start, end ,.. })
1440 if start == "1990-01-01 01:01:01.000005" &&
1441 end == "1990-01-01 01:01:01.000001"
1442 ));
1443 }
1444
1445 #[test]
1446 fn test_parse_date_range() {
1447 assert_eq!(
1448 parse_date_range(b"-19900201").ok(),
1449 Some(DateRange {
1450 start: None,
1451 end: Some(NaiveDate::from_ymd_opt(1990, 2, 1).unwrap())
1452 })
1453 );
1454 assert_eq!(
1455 parse_date_range(b"-202002").ok(),
1456 Some(DateRange {
1457 start: None,
1458 end: Some(NaiveDate::from_ymd_opt(2020, 2, 29).unwrap())
1459 })
1460 );
1461 assert_eq!(
1462 parse_date_range(b"-0020").ok(),
1463 Some(DateRange {
1464 start: None,
1465 end: Some(NaiveDate::from_ymd_opt(20, 12, 31).unwrap())
1466 })
1467 );
1468 assert_eq!(
1469 parse_date_range(b"0002-").ok(),
1470 Some(DateRange {
1471 start: Some(NaiveDate::from_ymd_opt(2, 1, 1).unwrap()),
1472 end: None
1473 })
1474 );
1475 assert_eq!(
1476 parse_date_range(b"000203-").ok(),
1477 Some(DateRange {
1478 start: Some(NaiveDate::from_ymd_opt(2, 3, 1).unwrap()),
1479 end: None
1480 })
1481 );
1482 assert_eq!(
1483 parse_date_range(b"00020307-").ok(),
1484 Some(DateRange {
1485 start: Some(NaiveDate::from_ymd_opt(2, 3, 7).unwrap()),
1486 end: None
1487 })
1488 );
1489 assert_eq!(
1490 parse_date_range(b"0002-202002 ").ok(),
1491 Some(DateRange {
1492 start: Some(NaiveDate::from_ymd_opt(2, 1, 1).unwrap()),
1493 end: Some(NaiveDate::from_ymd_opt(2020, 2, 29).unwrap())
1494 })
1495 );
1496 assert!(parse_date_range(b"0002").is_err());
1497 assert!(parse_date_range(b"0002x").is_err());
1498 assert!(parse_date_range(b" 2010-2020").is_err());
1499 }
1500
1501 #[test]
1502 fn test_parse_time_range() {
1503 assert_eq!(
1504 parse_time_range(b"-101010.123456789").ok(),
1505 Some(TimeRange {
1506 start: None,
1507 end: Some(NaiveTime::from_hms_micro_opt(10, 10, 10, 123_456).unwrap())
1508 })
1509 );
1510 assert_eq!(
1511 parse_time_range(b"-101010.123 ").ok(),
1512 Some(TimeRange {
1513 start: None,
1514 end: Some(NaiveTime::from_hms_micro_opt(10, 10, 10, 123_999).unwrap())
1515 })
1516 );
1517 assert_eq!(
1518 parse_time_range(b"-01 ").ok(),
1519 Some(TimeRange {
1520 start: None,
1521 end: Some(NaiveTime::from_hms_micro_opt(01, 59, 59, 999_999).unwrap())
1522 })
1523 );
1524 assert_eq!(
1525 parse_time_range(b"101010.123456-").ok(),
1526 Some(TimeRange {
1527 start: Some(NaiveTime::from_hms_micro_opt(10, 10, 10, 123_456).unwrap()),
1528 end: None
1529 })
1530 );
1531 assert_eq!(
1532 parse_time_range(b"101010.123-").ok(),
1533 Some(TimeRange {
1534 start: Some(NaiveTime::from_hms_micro_opt(10, 10, 10, 123_000).unwrap()),
1535 end: None
1536 })
1537 );
1538 assert_eq!(
1539 parse_time_range(b"1010-").ok(),
1540 Some(TimeRange {
1541 start: Some(NaiveTime::from_hms_opt(10, 10, 0).unwrap()),
1542 end: None
1543 })
1544 );
1545 assert_eq!(
1546 parse_time_range(b"00-").ok(),
1547 Some(TimeRange {
1548 start: Some(NaiveTime::from_hms_opt(0, 0, 0).unwrap()),
1549 end: None
1550 })
1551 );
1552 }
1553
1554 #[test]
1555 fn test_parse_datetime_range() {
1556 assert_eq!(
1557 parse_datetime_range(b"-20200229153420.123456").ok(),
1558 Some(DateTimeRange::Naive {
1559 start: None,
1560 end: Some(NaiveDateTime::new(
1561 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1562 NaiveTime::from_hms_micro_opt(15, 34, 20, 123_456).unwrap()
1563 ))
1564 })
1565 );
1566 assert_eq!(
1567 parse_datetime_range(b"-20200229153420.123").ok(),
1568 Some(DateTimeRange::Naive {
1569 start: None,
1570 end: Some(NaiveDateTime::new(
1571 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1572 NaiveTime::from_hms_micro_opt(15, 34, 20, 123_999).unwrap()
1573 ))
1574 })
1575 );
1576 assert_eq!(
1577 parse_datetime_range(b"-20200229153420").ok(),
1578 Some(DateTimeRange::Naive {
1579 start: None,
1580 end: Some(NaiveDateTime::new(
1581 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1582 NaiveTime::from_hms_micro_opt(15, 34, 20, 999_999).unwrap()
1583 ))
1584 })
1585 );
1586 assert_eq!(
1587 parse_datetime_range(b"-2020022915").ok(),
1588 Some(DateTimeRange::Naive {
1589 start: None,
1590 end: Some(NaiveDateTime::new(
1591 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1592 NaiveTime::from_hms_micro_opt(15, 59, 59, 999_999).unwrap()
1593 ))
1594 })
1595 );
1596 assert_eq!(
1597 parse_datetime_range(b"-202002").ok(),
1598 Some(DateTimeRange::Naive {
1599 start: None,
1600 end: Some(NaiveDateTime::new(
1601 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1602 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1603 ))
1604 })
1605 );
1606 assert_eq!(
1607 parse_datetime_range(b"0002-").ok(),
1608 Some(DateTimeRange::Naive {
1609 start: Some(NaiveDateTime::new(
1610 NaiveDate::from_ymd_opt(2, 1, 1).unwrap(),
1611 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1612 )),
1613 end: None
1614 })
1615 );
1616 assert_eq!(
1617 parse_datetime_range(b"00021231-").ok(),
1618 Some(DateTimeRange::Naive {
1619 start: Some(NaiveDateTime::new(
1620 NaiveDate::from_ymd_opt(2, 12, 31).unwrap(),
1621 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1622 )),
1623 end: None
1624 })
1625 );
1626 assert_eq!(
1628 parse_datetime_range(b"19900101+0500-1999+1400").ok(),
1629 Some(DateTimeRange::TimeZone {
1630 start: Some(
1631 FixedOffset::east_opt(5 * 3600)
1632 .unwrap()
1633 .from_local_datetime(&NaiveDateTime::new(
1634 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1635 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1636 ))
1637 .unwrap()
1638 ),
1639 end: Some(
1640 FixedOffset::east_opt(14 * 3600)
1641 .unwrap()
1642 .from_local_datetime(&NaiveDateTime::new(
1643 NaiveDate::from_ymd_opt(1999, 12, 31).unwrap(),
1644 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1645 ))
1646 .unwrap()
1647 )
1648 })
1649 );
1650 assert_eq!(
1652 parse_datetime_range(b"19900101-0500-1999-1200").ok(),
1653 Some(DateTimeRange::TimeZone {
1654 start: Some(
1655 FixedOffset::west_opt(5 * 3600)
1656 .unwrap()
1657 .from_local_datetime(&NaiveDateTime::new(
1658 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1659 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1660 ))
1661 .unwrap()
1662 ),
1663 end: Some(
1664 FixedOffset::west_opt(12 * 3600)
1665 .unwrap()
1666 .from_local_datetime(&NaiveDateTime::new(
1667 NaiveDate::from_ymd_opt(1999, 12, 31).unwrap(),
1668 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1669 ))
1670 .unwrap()
1671 )
1672 })
1673 );
1674 assert_eq!(
1676 parse_datetime_range(b"19900101+1400-1999-1200").ok(),
1677 Some(DateTimeRange::TimeZone {
1678 start: Some(
1679 FixedOffset::east_opt(14 * 3600)
1680 .unwrap()
1681 .from_local_datetime(&NaiveDateTime::new(
1682 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1683 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1684 ))
1685 .unwrap()
1686 ),
1687 end: Some(
1688 FixedOffset::west_opt(12 * 3600)
1689 .unwrap()
1690 .from_local_datetime(&NaiveDateTime::new(
1691 NaiveDate::from_ymd_opt(1999, 12, 31).unwrap(),
1692 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1693 ))
1694 .unwrap()
1695 )
1696 })
1697 );
1698 assert_eq!(
1701 parse_datetime_range(b"19900101-1200-1999").unwrap(),
1702 DateTimeRange::TimeZone {
1703 start: Some(
1704 FixedOffset::west_opt(12 * 3600)
1705 .unwrap()
1706 .from_local_datetime(&NaiveDateTime::new(
1707 NaiveDate::from_ymd_opt(1990, 1, 1).unwrap(),
1708 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1709 ))
1710 .unwrap()
1711 ),
1712 end: Some(
1713 Local::now()
1714 .offset()
1715 .from_local_datetime(&NaiveDateTime::new(
1716 NaiveDate::from_ymd_opt(1999, 12, 31).unwrap(),
1717 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1718 ))
1719 .unwrap()
1720 )
1721 }
1722 );
1723 assert_eq!(
1727 parse_datetime_range(b"0050-0500-1000").unwrap(),
1728 DateTimeRange::TimeZone {
1729 start: Some(
1730 Local::now()
1731 .offset()
1732 .from_local_datetime(&NaiveDateTime::new(
1733 NaiveDate::from_ymd_opt(50, 1, 1).unwrap(),
1734 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1735 ))
1736 .unwrap()
1737 ),
1738 end: Some(
1739 FixedOffset::west_opt(10 * 3600)
1740 .unwrap()
1741 .from_local_datetime(&NaiveDateTime::new(
1742 NaiveDate::from_ymd_opt(500, 12, 31).unwrap(),
1743 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1744 ))
1745 .unwrap()
1746 )
1747 }
1748 );
1749 assert!(matches!(
1751 parse_datetime_range(b"0001-00021231-2021-0100-0100"),
1752 Err(Error::SeparatorCount { .. })
1753 ));
1754 assert!(matches!(
1756 parse_datetime_range(b"00021231+0500"),
1757 Err(Error::NoRangeSeparator { .. })
1758 ));
1759 }
1760}