1use core::cell::Cell;
80
81use crate::{
82 civil::{Date, DateTime, Time, Weekday},
83 error::{err, Error, ErrorContext},
84 timestamp::Timestamp,
85 tz::{
86 AmbiguousOffset, Dst, Offset, TimeZoneAbbreviation,
87 TimeZoneOffsetInfo, TimeZoneTransition,
88 },
89 util::{
90 array_str::Abbreviation,
91 escape::{Byte, Bytes},
92 parse,
93 rangeint::{ri16, ri8, RFrom, RInto},
94 t::{self, Minute, Month, Second, Sign, SpanZoneOffset, Year, C},
95 },
96 SignedDuration,
97};
98
99type PosixHour = ri8<0, 25>;
103type IanaHour = ri16<0, 167>;
104type PosixJulianDayNoLeap = ri16<1, 365>;
105type PosixJulianDayWithLeap = ri16<0, 365>;
106type PosixWeek = ri8<1, 5>;
107
108#[cfg(feature = "tz-system")]
124#[derive(Debug, Eq, PartialEq)]
125pub(crate) enum PosixTz {
126 Rule(PosixTimeZone),
128 Implementation(alloc::boxed::Box<str>),
131}
132
133#[cfg(feature = "tz-system")]
134impl PosixTz {
135 pub(crate) fn parse(bytes: impl AsRef<[u8]>) -> Result<PosixTz, Error> {
137 let bytes = bytes.as_ref();
138 if bytes.get(0) == Some(&b':') {
139 let Ok(string) = core::str::from_utf8(&bytes[1..]) else {
140 return Err(err!(
141 "POSIX time zone string with a ':' prefix contains \
142 invalid UTF-8: {:?}",
143 Bytes(&bytes[1..]),
144 ));
145 };
146 Ok(PosixTz::Implementation(string.into()))
147 } else {
148 PosixTimeZone::parse(bytes).map(PosixTz::Rule)
149 }
150 }
151
152 pub(crate) fn parse_os_str(
154 osstr: impl AsRef<std::ffi::OsStr>,
155 ) -> Result<PosixTz, Error> {
156 PosixTz::parse(parse::os_str_bytes(osstr.as_ref())?)
157 }
158}
159
160#[cfg(feature = "tz-system")]
161impl core::fmt::Display for PosixTz {
162 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
163 match *self {
164 PosixTz::Rule(ref tz) => write!(f, "{tz}"),
165 PosixTz::Implementation(ref imp) => write!(f, ":{imp}"),
166 }
167 }
168}
169
170#[derive(Debug, Eq, PartialEq)]
179pub(crate) struct IanaTz(ReasonablePosixTimeZone);
180
181impl IanaTz {
182 pub(crate) fn parse_v3plus(
184 bytes: impl AsRef<[u8]>,
185 ) -> Result<IanaTz, Error> {
186 let bytes = bytes.as_ref();
187 let posix_tz = PosixTimeZone::parse(bytes).map_err(|e| {
188 e.context(err!("invalid POSIX TZ string {:?}", Bytes(bytes)))
189 })?;
190 let Ok(reasonable) = posix_tz.reasonable() else {
191 return Err(err!(
192 "TZ string {:?} in v3+ tzfile has DST but no transition rules",
193 Bytes(bytes),
194 ));
195 };
196 Ok(IanaTz(reasonable))
197 }
198
199 pub(crate) fn parse_v3plus_prefix<'b, B: AsRef<[u8]> + ?Sized + 'b>(
202 bytes: &'b B,
203 ) -> Result<(IanaTz, &'b [u8]), Error> {
204 let bytes = bytes.as_ref();
205 let (posix_tz, remaining) = PosixTimeZone::parse_prefix(bytes)
206 .map_err(|e| {
207 e.context(err!("invalid POSIX TZ string {:?}", Bytes(bytes)))
208 })?;
209 let Ok(reasonable) = posix_tz.reasonable() else {
210 return Err(err!(
211 "TZ string {:?} in v3+ tzfile has DST but no transition rules",
212 Bytes(bytes),
213 ));
214 };
215 Ok((IanaTz(reasonable), remaining))
216 }
217
218 pub(crate) fn into_tz(self) -> ReasonablePosixTimeZone {
224 self.0
225 }
226}
227
228impl core::fmt::Display for IanaTz {
229 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
230 write!(f, "{}", self.0)
231 }
232}
233
234#[derive(Clone, Debug, Eq, PartialEq)]
250pub(crate) struct ReasonablePosixTimeZone {
251 std_abbrev: Abbreviation,
252 std_offset: PosixOffset,
253 dst: Option<ReasonablePosixDst>,
254}
255
256impl ReasonablePosixTimeZone {
257 pub(crate) fn to_offset(&self, timestamp: Timestamp) -> Offset {
264 if self.dst.is_none() {
265 return self.std_offset();
266 }
267
268 let dt = Offset::UTC.to_datetime(timestamp);
269 self.dst_info_utc(dt.date().year_ranged())
270 .filter(|dst_info| dst_info.in_dst(dt))
271 .map(|dst_info| dst_info.offset)
272 .unwrap_or_else(|| self.std_offset())
273 }
274
275 pub(crate) fn to_offset_info(
282 &self,
283 timestamp: Timestamp,
284 ) -> TimeZoneOffsetInfo<'_> {
285 if self.dst.is_none() {
286 let abbreviation =
287 TimeZoneAbbreviation::Borrowed(self.std_abbrev.as_str());
288 return TimeZoneOffsetInfo {
289 offset: self.std_offset(),
290 dst: Dst::No,
291 abbreviation,
292 };
293 }
294
295 let dt = Offset::UTC.to_datetime(timestamp);
296 self.dst_info_utc(dt.date().year_ranged())
297 .filter(|dst_info| dst_info.in_dst(dt))
298 .map(|dst_info| {
299 let abbreviation = TimeZoneAbbreviation::Borrowed(
300 dst_info.dst.abbrev.as_str(),
301 );
302 TimeZoneOffsetInfo {
303 offset: dst_info.offset,
304 dst: Dst::Yes,
305 abbreviation,
306 }
307 })
308 .unwrap_or_else(|| {
309 let abbreviation =
310 TimeZoneAbbreviation::Borrowed(self.std_abbrev.as_str());
311 TimeZoneOffsetInfo {
312 offset: self.std_offset(),
313 dst: Dst::No,
314 abbreviation,
315 }
316 })
317 }
318
319 pub(crate) fn to_ambiguous_kind(&self, dt: DateTime) -> AmbiguousOffset {
331 let year = dt.date().year_ranged();
332 let std_offset = self.std_offset();
333 let Some(dst_info) = self.dst_info_wall(year) else {
334 return AmbiguousOffset::Unambiguous { offset: std_offset };
335 };
336 let diff = dst_info.offset - std_offset;
337 if diff.get_seconds_ranged() == 0 {
352 debug_assert_eq!(std_offset, dst_info.offset);
353 AmbiguousOffset::Unambiguous { offset: std_offset }
354 } else if diff.is_negative() {
355 if dst_info.in_dst(dt) {
359 AmbiguousOffset::Unambiguous { offset: dst_info.offset }
360 } else {
361 let fold_start = dst_info.start.saturating_add(diff);
362 let gap_end = dst_info.end.saturating_sub(diff);
363 if fold_start <= dt && dt < dst_info.start {
364 AmbiguousOffset::Fold {
365 before: std_offset,
366 after: dst_info.offset,
367 }
368 } else if dst_info.end <= dt && dt < gap_end {
369 AmbiguousOffset::Gap {
370 before: dst_info.offset,
371 after: std_offset,
372 }
373 } else {
374 AmbiguousOffset::Unambiguous { offset: std_offset }
375 }
376 }
377 } else {
378 if !dst_info.in_dst(dt) {
382 AmbiguousOffset::Unambiguous { offset: std_offset }
383 } else {
384 let gap_end = dst_info.start.saturating_add(diff);
389 let fold_start = dst_info.end.saturating_sub(diff);
390 if dst_info.start <= dt && dt < gap_end {
391 AmbiguousOffset::Gap {
392 before: std_offset,
393 after: dst_info.offset,
394 }
395 } else if fold_start <= dt && dt < dst_info.end {
396 AmbiguousOffset::Fold {
397 before: dst_info.offset,
398 after: std_offset,
399 }
400 } else {
401 AmbiguousOffset::Unambiguous { offset: dst_info.offset }
402 }
403 }
404 }
405 }
406
407 pub(crate) fn previous_transition(
410 &self,
411 timestamp: Timestamp,
412 ) -> Option<TimeZoneTransition> {
413 let dt = Offset::UTC.to_datetime(timestamp);
414 let dst_info = self.dst_info_utc(dt.date().year_ranged())?;
415 let (earlier, later) = dst_info.ordered();
416 let (prev, dst_info) = if dt > later {
417 (later, dst_info)
418 } else if dt > earlier {
419 (earlier, dst_info)
420 } else {
421 let prev_year = dt.date().year_ranged().checked_sub(C(1))?;
422 let dst_info = self.dst_info_utc(prev_year)?;
423 let (_, later) = dst_info.ordered();
424 (later, dst_info)
425 };
426
427 let timestamp = Offset::UTC.to_timestamp(prev).ok()?;
428 let dt = Offset::UTC.to_datetime(timestamp);
429 let (offset, abbrev, dst) = if dst_info.in_dst(dt) {
430 (dst_info.offset, dst_info.dst.abbrev.as_str(), Dst::Yes)
431 } else {
432 (self.std_offset(), self.std_abbrev.as_str(), Dst::No)
433 };
434 Some(TimeZoneTransition { timestamp, offset, abbrev, dst })
435 }
436
437 pub(crate) fn next_transition(
440 &self,
441 timestamp: Timestamp,
442 ) -> Option<TimeZoneTransition> {
443 let dt = Offset::UTC.to_datetime(timestamp);
444 let dst_info = self.dst_info_utc(dt.date().year_ranged())?;
445 let (earlier, later) = dst_info.ordered();
446 let (next, dst_info) = if dt < earlier {
447 (earlier, dst_info)
448 } else if dt < later {
449 (later, dst_info)
450 } else {
451 let next_year = dt.date().year_ranged().checked_add(C(1))?;
452 let dst_info = self.dst_info_utc(next_year)?;
453 let (earlier, _) = dst_info.ordered();
454 (earlier, dst_info)
455 };
456
457 let timestamp = Offset::UTC.to_timestamp(next).ok()?;
458 let dt = Offset::UTC.to_datetime(timestamp);
459 let (offset, abbrev, dst) = if dst_info.in_dst(dt) {
460 (dst_info.offset, dst_info.dst.abbrev.as_str(), Dst::Yes)
461 } else {
462 (self.std_offset(), self.std_abbrev.as_str(), Dst::No)
463 };
464 Some(TimeZoneTransition { timestamp, offset, abbrev, dst })
465 }
466
467 fn std_offset(&self) -> Offset {
469 self.std_offset.to_offset()
470 }
471
472 fn dst_info_utc(&self, year: impl RInto<Year>) -> Option<DstInfo<'_>> {
477 let year = year.rinto();
478 let dst = self.dst.as_ref()?;
479 let std_offset = self.std_offset.to_offset();
480 let dst_offset = dst.posix_offset(&self.std_offset).to_offset();
481 let start = dst.rule.start.to_datetime(year, std_offset);
484 let end = dst.rule.end.to_datetime(year, dst_offset);
487 Some(DstInfo { dst, offset: dst_offset, start, end })
488 }
489
490 fn dst_info_wall(&self, year: impl RInto<Year>) -> Option<DstInfo<'_>> {
496 let year = year.rinto();
497 let dst = self.dst.as_ref()?;
498 let dst_offset = dst.posix_offset(&self.std_offset).to_offset();
499 let start = dst.rule.start.to_datetime(year, Offset::ZERO);
503 let end = dst.rule.end.to_datetime(year, Offset::ZERO);
504 Some(DstInfo { dst, offset: dst_offset, start, end })
505 }
506
507 #[cfg(test)]
510 fn rule(&self) -> Rule {
511 self.dst.as_ref().unwrap().rule
512 }
513}
514
515impl core::fmt::Display for ReasonablePosixTimeZone {
516 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
517 write!(
518 f,
519 "{}{}",
520 AbbreviationDisplay(self.std_abbrev),
521 self.std_offset
522 )?;
523 if let Some(ref dst) = self.dst {
524 write!(f, "{dst}")?;
525 }
526 Ok(())
527 }
528}
529
530#[derive(Debug, Eq, PartialEq)]
533struct DstInfo<'a> {
534 dst: &'a ReasonablePosixDst,
536 offset: Offset,
542 start: DateTime,
551 end: DateTime,
560}
561
562impl<'a> DstInfo<'a> {
563 fn in_dst(&self, utc_dt: DateTime) -> bool {
566 if self.start <= self.end {
567 self.start <= utc_dt && utc_dt < self.end
568 } else {
569 !(self.end <= utc_dt && utc_dt < self.start)
570 }
571 }
572
573 fn ordered(&self) -> (DateTime, DateTime) {
575 if self.start <= self.end {
576 (self.start, self.end)
577 } else {
578 (self.end, self.start)
579 }
580 }
581}
582
583#[derive(Clone, Debug, Eq, PartialEq)]
587struct ReasonablePosixDst {
588 abbrev: Abbreviation,
589 offset: Option<PosixOffset>,
590 rule: Rule,
592}
593
594impl ReasonablePosixDst {
595 fn posix_offset(&self, std_offset: &PosixOffset) -> PosixOffset {
600 if let Some(ref offset) = self.offset {
601 return *offset;
602 }
603 PosixOffset {
607 hour: std_offset.hour - (std_offset.sign() * C(1)),
608 ..*std_offset
609 }
610 }
611}
612
613impl core::fmt::Display for ReasonablePosixDst {
614 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
615 write!(f, "{}", AbbreviationDisplay(self.abbrev))?;
616 if let Some(offset) = self.offset {
617 write!(f, "{offset}")?;
618 }
619 write!(f, ",{}", self.rule)?;
620 Ok(())
621 }
622}
623
624#[derive(Debug, Eq, PartialEq)]
626pub(crate) struct PosixTimeZone {
627 std_abbrev: Abbreviation,
628 std_offset: PosixOffset,
629 dst: Option<PosixDst>,
630}
631
632impl PosixTimeZone {
633 fn parse(bytes: impl AsRef<[u8]>) -> Result<PosixTimeZone, Error> {
636 let parser =
642 Parser { ianav3plus: true, ..Parser::new(bytes.as_ref()) };
643 parser.parse()
644 }
645
646 fn parse_prefix<'b, B: AsRef<[u8]> + ?Sized + 'b>(
649 bytes: &'b B,
650 ) -> Result<(PosixTimeZone, &'b [u8]), Error> {
651 let parser =
652 Parser { ianav3plus: true, ..Parser::new(bytes.as_ref()) };
653 parser.parse_prefix()
654 }
655
656 pub(crate) fn reasonable(
665 mut self,
666 ) -> Result<ReasonablePosixTimeZone, PosixTimeZone> {
667 if let Some(mut dst) = self.dst.take() {
668 if let Some(rule) = dst.rule.take() {
669 Ok(ReasonablePosixTimeZone {
670 std_abbrev: self.std_abbrev,
671 std_offset: self.std_offset,
672 dst: Some(ReasonablePosixDst {
673 abbrev: dst.abbrev,
674 offset: dst.offset,
675 rule,
676 }),
677 })
678 } else {
679 Err(PosixTimeZone { dst: Some(dst), ..self })
683 }
684 } else {
685 Ok(ReasonablePosixTimeZone {
689 std_abbrev: self.std_abbrev,
690 std_offset: self.std_offset,
691 dst: None,
692 })
693 }
694 }
695}
696
697impl core::fmt::Display for PosixTimeZone {
698 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
699 write!(
700 f,
701 "{}{}",
702 AbbreviationDisplay(self.std_abbrev),
703 self.std_offset
704 )?;
705 if let Some(ref dst) = self.dst {
706 write!(f, "{dst}")?;
707 }
708 Ok(())
709 }
710}
711
712#[derive(Debug, Eq, PartialEq)]
714struct PosixDst {
715 abbrev: Abbreviation,
716 offset: Option<PosixOffset>,
717 rule: Option<Rule>,
718}
719
720impl core::fmt::Display for PosixDst {
721 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
722 write!(f, "{}", AbbreviationDisplay(self.abbrev))?;
723 if let Some(offset) = self.offset {
724 write!(f, "{offset}")?;
725 }
726 if let Some(rule) = self.rule {
727 write!(f, ",{rule}")?;
728 }
729 Ok(())
730 }
731}
732
733#[derive(Clone, Copy, Debug, Eq, PartialEq)]
736struct PosixOffset {
737 sign: Option<Sign>,
738 hour: PosixHour,
739 minute: Option<Minute>,
740 second: Option<Second>,
741}
742
743impl PosixOffset {
744 fn to_offset(&self) -> Offset {
755 let sign = SpanZoneOffset::rfrom(-self.sign());
756 let hour = SpanZoneOffset::rfrom(self.hour);
757 let minute =
758 SpanZoneOffset::rfrom(self.minute.unwrap_or(C(0).rinto()));
759 let second =
760 SpanZoneOffset::rfrom(self.second.unwrap_or(C(0).rinto()));
761 let seconds = (hour * t::SECONDS_PER_HOUR)
762 + (minute * t::SECONDS_PER_MINUTE)
763 + second;
764 Offset::from_seconds_ranged(sign * seconds)
765 }
766
767 fn sign(&self) -> Sign {
773 self.sign.unwrap_or(Sign::N::<1>())
774 }
775}
776
777impl core::fmt::Display for PosixOffset {
778 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
779 if let Some(sign) = self.sign {
780 if sign < 0 {
781 write!(f, "-")?;
782 } else {
783 write!(f, "+")?;
784 }
785 }
786 write!(f, "{}", self.hour)?;
787 if let Some(minute) = self.minute {
788 write!(f, ":{minute:02}")?;
789 if let Some(second) = self.second {
790 write!(f, ":{second:02}")?;
791 }
792 }
793 Ok(())
794 }
795}
796
797#[derive(Clone, Copy, Debug, Eq, PartialEq)]
799struct Rule {
800 start: PosixDateTimeSpec,
801 end: PosixDateTimeSpec,
802}
803
804impl core::fmt::Display for Rule {
805 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
806 write!(f, "{},{}", self.start, self.end)
807 }
808}
809
810#[derive(Clone, Copy, Debug, Eq, PartialEq)]
813struct PosixDateTimeSpec {
814 date: PosixDateSpec,
815 time: Option<PosixTimeSpec>,
816}
817
818impl PosixDateTimeSpec {
819 fn to_datetime(&self, year: impl RInto<Year>, offset: Offset) -> DateTime {
831 let year = year.rinto();
832 let mkmin = || {
833 Date::new_ranged(year, C(1), C(1)).unwrap().to_datetime(Time::MIN)
834 };
835 let mkmax = || {
836 Date::new_ranged(year, C(12), C(31))
837 .unwrap()
838 .to_datetime(Time::MAX)
839 };
840
841 let Some(date) = self.date.to_civil_date(year) else { return mkmax() };
842 let mut dt = date.to_datetime(Time::MIN);
843 let dur_transition = self.time().to_duration();
844 let dur_offset = SignedDuration::from(offset);
845 dt = dt.checked_add(dur_transition).unwrap_or_else(|_| {
846 if dur_transition.is_negative() {
847 mkmin()
848 } else {
849 mkmax()
850 }
851 });
852 dt = dt.checked_sub(dur_offset).unwrap_or_else(|_| {
853 if dur_transition.is_negative() {
854 mkmax()
855 } else {
856 mkmin()
857 }
858 });
859 if dt.date().year() < year {
860 mkmin()
861 } else if dt.date().year() > year {
862 mkmax()
863 } else {
864 dt
865 }
866 }
867
868 fn time(self) -> PosixTimeSpec {
871 const DEFAULT: PosixTimeSpec = PosixTimeSpec {
872 sign: None,
873 hour: IanaHour::N::<2>(),
874 minute: None,
875 second: None,
876 };
877 self.time.unwrap_or(DEFAULT)
878 }
879}
880
881impl core::fmt::Display for PosixDateTimeSpec {
882 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
883 write!(f, "{}", self.date)?;
884 if let Some(time) = self.time {
885 write!(f, "/{time}")?;
886 }
887 Ok(())
888 }
889}
890
891#[derive(Clone, Copy, Debug, Eq, PartialEq)]
893enum PosixDateSpec {
894 JulianOne(PosixJulianDayNoLeap),
901 JulianZero(PosixJulianDayWithLeap),
906 WeekdayOfMonth(WeekdayOfMonth),
908}
909
910impl PosixDateSpec {
911 fn to_civil_date(&self, year: impl RInto<Year>) -> Option<Date> {
918 match *self {
919 PosixDateSpec::JulianOne(day) => {
920 let first = Date::new_ranged(year, C(1), C(1)).unwrap();
921 Some(
925 first
926 .with()
927 .day_of_year_no_leap(day.get())
928 .build()
929 .expect("Julian 'J day' should be in bounds"),
930 )
931 }
932 PosixDateSpec::JulianZero(day) => {
933 let first = Date::new_ranged(year, C(1), C(1)).unwrap();
934 let off1 = day.get().checked_add(1).unwrap();
937 first.with().day_of_year(off1).build().ok()
943 }
944 PosixDateSpec::WeekdayOfMonth(wom) => {
945 let first = Date::new_ranged(year, wom.month, C(1)).unwrap();
946 let week = wom.week();
959 debug_assert!(week == -1 || (1..=4).contains(&week));
960 Some(
961 first
962 .nth_weekday_of_month(week, wom.weekday)
963 .expect("nth weekday always exists"),
964 )
965 }
966 }
967 }
968}
969
970impl core::fmt::Display for PosixDateSpec {
971 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
972 match *self {
973 PosixDateSpec::JulianOne(n) => write!(f, "J{n}"),
974 PosixDateSpec::JulianZero(n) => write!(f, "{n}"),
975 PosixDateSpec::WeekdayOfMonth(wk) => write!(f, "{wk}"),
976 }
977 }
978}
979
980#[derive(Clone, Copy, Debug, Eq, PartialEq)]
992struct WeekdayOfMonth {
993 month: Month,
994 week: PosixWeek,
995 weekday: Weekday,
996}
997
998impl WeekdayOfMonth {
999 fn week(&self) -> i8 {
1004 if self.week == 5 {
1005 -1
1006 } else {
1007 self.week.get()
1008 }
1009 }
1010}
1011
1012impl core::fmt::Display for WeekdayOfMonth {
1013 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1014 write!(
1015 f,
1016 "M{month}.{week}.{weekday}",
1017 month = self.month,
1018 week = self.week,
1019 weekday = self.weekday.to_sunday_zero_offset(),
1020 )
1021 }
1022}
1023
1024#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1031struct PosixTimeSpec {
1032 sign: Option<Sign>,
1033 hour: IanaHour,
1037 minute: Option<Minute>,
1038 second: Option<Second>,
1039}
1040
1041impl PosixTimeSpec {
1042 fn to_duration(&self) -> SignedDuration {
1044 let sign = i64::from(self.sign());
1045 let hour = sign * i64::from(self.hour);
1046 let minute = sign * i64::from(self.minute.unwrap_or(C(0).rinto()));
1047 let second = sign * i64::from(self.second.unwrap_or(C(0).rinto()));
1048 SignedDuration::from_secs(second + (60 * minute) + (60 * 60 * hour))
1049 }
1050
1051 fn sign(&self) -> Sign {
1054 self.sign.unwrap_or(Sign::N::<1>())
1055 }
1056}
1057
1058impl core::fmt::Display for PosixTimeSpec {
1059 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1060 if let Some(sign) = self.sign {
1061 if sign < 0 {
1062 write!(f, "-")?;
1063 } else {
1064 write!(f, "+")?;
1065 }
1066 }
1067 write!(f, "{}", self.hour)?;
1068 if let Some(minute) = self.minute {
1069 write!(f, ":{minute:02}")?;
1070 if let Some(second) = self.second {
1071 write!(f, ":{second:02}")?;
1072 }
1073 }
1074 Ok(())
1075 }
1076}
1077
1078#[derive(Debug)]
1079struct Parser<'s> {
1080 tz: &'s [u8],
1082 pos: Cell<usize>,
1084 ianav3plus: bool,
1098}
1099
1100impl<'s> Parser<'s> {
1101 fn new<B: ?Sized + AsRef<[u8]>>(tz: &'s B) -> Parser<'s> {
1102 Parser { tz: tz.as_ref(), pos: Cell::new(0), ianav3plus: false }
1103 }
1104
1105 fn parse(&self) -> Result<PosixTimeZone, Error> {
1109 let (time_zone, remaining) = self.parse_prefix()?;
1110 if !remaining.is_empty() {
1111 return Err(err!(
1112 "expected entire TZ string to be a valid POSIX \
1113 time zone, but found '{}' after what would otherwise \
1114 be a valid POSIX TZ string",
1115 Bytes(remaining),
1116 ));
1117 }
1118 Ok(time_zone)
1119 }
1120
1121 fn parse_prefix(&self) -> Result<(PosixTimeZone, &'s [u8]), Error> {
1124 let time_zone = self.parse_posix_time_zone()?;
1125 Ok((time_zone, self.remaining()))
1126 }
1127
1128 fn parse_posix_time_zone(&self) -> Result<PosixTimeZone, Error> {
1133 let std_abbrev = self
1134 .parse_abbreviation()
1135 .map_err(|e| e.context("failed to parse standard abbreviation"))?;
1136 let std_offset = self
1137 .parse_posix_offset()
1138 .map_err(|e| e.context("failed to parse standard offset"))?;
1139 let mut dst = None;
1140 if !self.is_done()
1141 && (self.byte().is_ascii_alphabetic() || self.byte() == b'<')
1142 {
1143 dst = Some(self.parse_posix_dst()?);
1144 }
1145 Ok(PosixTimeZone { std_abbrev, std_offset, dst })
1146 }
1147
1148 fn parse_posix_dst(&self) -> Result<PosixDst, Error> {
1157 let abbrev = self
1158 .parse_abbreviation()
1159 .map_err(|e| e.context("failed to parse DST abbreviation"))?;
1160 let mut dst = PosixDst { abbrev, offset: None, rule: None };
1161 if self.is_done() {
1162 return Ok(dst);
1163 }
1164 if self.byte() != b',' {
1165 dst.offset = Some(
1166 self.parse_posix_offset()
1167 .map_err(|e| e.context("failed to parse DST offset"))?,
1168 );
1169 if self.is_done() {
1170 return Ok(dst);
1171 }
1172 }
1173 if self.byte() != b',' {
1174 return Err(err!(
1175 "after parsing DST offset in POSIX time zone string, \
1176 found '{}' but expected a ','",
1177 Byte(self.byte()),
1178 ));
1179 }
1180 if !self.bump() {
1181 return Err(err!(
1182 "after parsing DST offset in POSIX time zone string, \
1183 found end of string after a trailing ','",
1184 ));
1185 }
1186 dst.rule = Some(self.parse_rule()?);
1187 Ok(dst)
1188 }
1189
1190 fn parse_abbreviation(&self) -> Result<Abbreviation, Error> {
1199 if self.byte() == b'<' {
1200 if !self.bump() {
1201 return Err(err!(
1202 "found opening '<' quote for abbreviation in \
1203 POSIX time zone string, and expected a name \
1204 following it, but found the end of string instead"
1205 ));
1206 }
1207 self.parse_quoted_abbreviation()
1208 } else {
1209 self.parse_unquoted_abbreviation()
1210 }
1211 }
1212
1213 fn parse_unquoted_abbreviation(&self) -> Result<Abbreviation, Error> {
1221 let start = self.pos();
1222 for i in 0.. {
1223 if !self.byte().is_ascii_alphabetic() {
1224 break;
1225 }
1226 if i >= Abbreviation::capacity() {
1227 return Err(err!(
1228 "expected abbreviation with at most {} bytes, \
1229 but found a longer abbreviation beginning with '{}'",
1230 Abbreviation::capacity(),
1231 Bytes(&self.tz[start..i]),
1232 ));
1233 }
1234 if !self.bump() {
1235 break;
1236 }
1237 }
1238 let end = self.pos();
1239 let abbrev =
1240 core::str::from_utf8(&self.tz[start..end]).map_err(|_| {
1241 err!(
1247 "found abbreviation '{}', but it is not valid UTF-8",
1248 Bytes(&self.tz[start..end]),
1249 )
1250 })?;
1251 if abbrev.len() < 3 {
1252 return Err(err!(
1253 "expected abbreviation with 3 or more bytes, but found \
1254 abbreviation {:?} with {} bytes",
1255 abbrev,
1256 abbrev.len(),
1257 ));
1258 }
1259 Ok(Abbreviation::new(abbrev).unwrap())
1262 }
1263
1264 fn parse_quoted_abbreviation(&self) -> Result<Abbreviation, Error> {
1272 let start = self.pos();
1273 for i in 0.. {
1274 if !self.byte().is_ascii_alphanumeric()
1275 && self.byte() != b'+'
1276 && self.byte() != b'-'
1277 {
1278 break;
1279 }
1280 if i >= Abbreviation::capacity() {
1281 return Err(err!(
1282 "expected abbreviation with at most {} bytes, \
1283 but found a longer abbreviation beginning with '{}'",
1284 Abbreviation::capacity(),
1285 Bytes(&self.tz[start..i]),
1286 ));
1287 }
1288 if !self.bump() {
1289 break;
1290 }
1291 }
1292 let end = self.pos();
1293 let abbrev =
1294 core::str::from_utf8(&self.tz[start..end]).map_err(|_| {
1295 err!(
1301 "found abbreviation '{}', but it is not valid UTF-8",
1302 Bytes(&self.tz[start..end]),
1303 )
1304 })?;
1305 if self.is_done() {
1306 return Err(err!(
1307 "found non-empty quoted abbreviation {abbrev:?}, but \
1308 did not find expected end-of-quoted abbreviation \
1309 '>' character",
1310 ));
1311 }
1312 if self.byte() != b'>' {
1313 return Err(err!(
1314 "found non-empty quoted abbreviation {abbrev:?}, but \
1315 found '{}' instead of end-of-quoted abbreviation '>' \
1316 character",
1317 Byte(self.byte()),
1318 ));
1319 }
1320 self.bump();
1321 if abbrev.len() < 3 {
1322 return Err(err!(
1323 "expected abbreviation with 3 or more bytes, but found \
1324 abbreviation {abbrev:?} with {} bytes",
1325 abbrev.len(),
1326 ));
1327 }
1328 Ok(Abbreviation::new(abbrev).unwrap())
1331 }
1332
1333 fn parse_posix_offset(&self) -> Result<PosixOffset, Error> {
1342 let sign = self.parse_optional_sign().map_err(|e| {
1343 e.context(
1344 "failed to parse sign for time offset \
1345 in POSIX time zone string",
1346 )
1347 })?;
1348 let hour = self.parse_hour_posix()?;
1349 let offset = PosixOffset { sign, hour, minute: None, second: None };
1350 if self.maybe_byte() != Some(b':') {
1351 return Ok(offset);
1352 }
1353 if !self.bump() {
1354 return Err(err!(
1355 "incomplete time in POSIX timezone (missing minutes)",
1356 ));
1357 }
1358 let minute = Some(self.parse_minute()?);
1359 if self.maybe_byte() != Some(b':') {
1360 return Ok(PosixOffset { sign, hour, minute, second: None });
1361 }
1362 if !self.bump() {
1363 return Err(err!(
1364 "incomplete time in POSIX timezone (missing seconds)",
1365 ));
1366 }
1367 let second = Some(self.parse_second()?);
1368 Ok(PosixOffset { sign, hour, minute, second })
1369 }
1370
1371 fn parse_rule(&self) -> Result<Rule, Error> {
1381 let start = self.parse_posix_datetime_spec().map_err(|e| {
1382 e.context("failed to parse start of DST transition rule")
1383 })?;
1384 if self.maybe_byte() != Some(b',') || !self.bump() {
1385 return Err(err!(
1386 "expected end of DST rule after parsing the start \
1387 of the DST rule"
1388 ));
1389 }
1390 let end = self.parse_posix_datetime_spec().map_err(|e| {
1391 e.context("failed to parse end of DST transition rule")
1392 })?;
1393 Ok(Rule { start, end })
1394 }
1395
1396 fn parse_posix_datetime_spec(&self) -> Result<PosixDateTimeSpec, Error> {
1405 let date = self.parse_posix_date_spec()?;
1406 let mut spec = PosixDateTimeSpec { date, time: None };
1407 if self.maybe_byte() != Some(b'/') {
1408 return Ok(spec);
1409 }
1410 if !self.bump() {
1411 return Err(err!(
1412 "expected time specification after '/' following a date
1413 specification in a POSIX time zone DST transition rule",
1414 ));
1415 }
1416 spec.time = Some(self.parse_posix_time_spec()?);
1417 Ok(spec)
1418 }
1419
1420 fn parse_posix_date_spec(&self) -> Result<PosixDateSpec, Error> {
1434 match self.byte() {
1435 b'J' => {
1436 if !self.bump() {
1437 return Err(err!(
1438 "expected one-based Julian day after 'J' in date \
1439 specification of a POSIX time zone DST transition \
1440 rule, but got the end of the string instead"
1441 ));
1442 }
1443 Ok(PosixDateSpec::JulianOne(
1444 self.parse_posix_julian_day_no_leap()?,
1445 ))
1446 }
1447 b'0'..=b'9' => Ok(PosixDateSpec::JulianZero(
1448 self.parse_posix_julian_day_with_leap()?,
1449 )),
1450 b'M' => {
1451 if !self.bump() {
1452 return Err(err!(
1453 "expected month-week-weekday after 'M' in date \
1454 specification of a POSIX time zone DST transition \
1455 rule, but got the end of the string instead"
1456 ));
1457 }
1458 Ok(PosixDateSpec::WeekdayOfMonth(
1459 self.parse_weekday_of_month()?,
1460 ))
1461 }
1462 _ => Err(err!(
1463 "expected 'J', a digit or 'M' at the beginning of a date \
1464 specification of a POSIX time zone DST transition rule, \
1465 but got '{}' instead",
1466 Byte(self.byte()),
1467 )),
1468 }
1469 }
1470
1471 fn parse_posix_julian_day_no_leap(
1478 &self,
1479 ) -> Result<PosixJulianDayNoLeap, Error> {
1480 let number = self
1481 .parse_number_with_upto_n_digits(3)
1482 .map_err(|e| e.context("invalid one based Julian day"))?;
1483 let day = PosixJulianDayNoLeap::new(number).ok_or_else(|| {
1484 err!("invalid one based Julian day (must be in range 1..=365")
1485 })?;
1486 Ok(day)
1487 }
1488
1489 fn parse_posix_julian_day_with_leap(
1495 &self,
1496 ) -> Result<PosixJulianDayWithLeap, Error> {
1497 let number = self
1498 .parse_number_with_upto_n_digits(3)
1499 .map_err(|e| e.context("invalid zero based Julian day"))?;
1500 let day = PosixJulianDayWithLeap::new(number).ok_or_else(|| {
1501 err!("invalid zero based Julian day (must be in range 0..=365")
1502 })?;
1503 Ok(day)
1504 }
1505
1506 fn parse_weekday_of_month(&self) -> Result<WeekdayOfMonth, Error> {
1513 let month = self.parse_month()?;
1514 if self.maybe_byte() != Some(b'.') {
1515 return Err(err!(
1516 "expected '.' after month '{month}' in POSIX time zone rule"
1517 ));
1518 }
1519 if !self.bump() {
1520 return Err(err!(
1521 "expected week after month '{month}' in POSIX time zone rule"
1522 ));
1523 }
1524 let week = self.parse_week()?;
1525 if self.maybe_byte() != Some(b'.') {
1526 return Err(err!(
1527 "expected '.' after week '{week}' in POSIX time zone rule"
1528 ));
1529 }
1530 if !self.bump() {
1531 return Err(err!(
1532 "expected day-of-week after week '{week}' in \
1533 POSIX time zone rule"
1534 ));
1535 }
1536 let weekday = self.parse_weekday()?;
1537 Ok(WeekdayOfMonth { month, week, weekday })
1538 }
1539
1540 fn parse_posix_time_spec(&self) -> Result<PosixTimeSpec, Error> {
1547 let (sign, hour) = if self.ianav3plus {
1548 let sign = self.parse_optional_sign().map_err(|e| {
1549 e.context(
1550 "failed to parse sign for transition time \
1551 in POSIX time zone string",
1552 )
1553 })?;
1554 let hour = self.parse_hour_ianav3plus()?;
1555 (sign, hour)
1556 } else {
1557 (None, self.parse_hour_posix()?.rinto())
1558 };
1559 let spec = PosixTimeSpec { sign, hour, minute: None, second: None };
1560 if self.maybe_byte() != Some(b':') {
1561 return Ok(spec);
1562 }
1563 if !self.bump() {
1564 return Err(err!(
1565 "incomplete transition time in \
1566 POSIX time zone string (missing minutes)",
1567 ));
1568 }
1569 let minute = Some(self.parse_minute()?);
1570 if self.maybe_byte() != Some(b':') {
1571 return Ok(PosixTimeSpec { sign, hour, minute, second: None });
1572 }
1573 if !self.bump() {
1574 return Err(err!(
1575 "incomplete transition time in \
1576 POSIX time zone string (missing seconds)",
1577 ));
1578 }
1579 let second = Some(self.parse_second()?);
1580 Ok(PosixTimeSpec { sign, hour, minute, second })
1581 }
1582
1583 fn parse_month(&self) -> Result<Month, Error> {
1589 let number = self.parse_number_with_upto_n_digits(2)?;
1590 let month = Month::new(number).ok_or_else(|| {
1591 err!("month in POSIX time zone must be in range 1..=12")
1592 })?;
1593 Ok(month)
1594 }
1595
1596 fn parse_week(&self) -> Result<PosixWeek, Error> {
1601 let number = self.parse_number_with_exactly_n_digits(1)?;
1602 let week = PosixWeek::new(number).ok_or_else(|| {
1603 err!("week in POSIX time zone must be in range 1..=5")
1604 })?;
1605 Ok(week)
1606 }
1607
1608 fn parse_weekday(&self) -> Result<Weekday, Error> {
1613 let number = self.parse_number_with_exactly_n_digits(1)?;
1614 let number8 = i8::try_from(number).map_err(|_| {
1615 err!(
1616 "weekday '{number}' in POSIX time zone \
1617 does not fit into 8-bit integer"
1618 )
1619 })?;
1620 let weekday =
1621 Weekday::from_sunday_zero_offset(number8).map_err(|_| {
1622 err!(
1623 "weekday in POSIX time zone must be in range 0..=6 \
1624 (with 0 corresponding to Sunday), but got {number8}",
1625 )
1626 })?;
1627 Ok(weekday)
1628 }
1629
1630 fn parse_hour_ianav3plus(&self) -> Result<IanaHour, Error> {
1641 assert!(self.ianav3plus);
1644 let number = self
1645 .parse_number_with_upto_n_digits(3)
1646 .map_err(|e| e.context("invalid hour digits"))?;
1647 let hour = IanaHour::new(number).ok_or_else(|| {
1648 err!(
1649 "hour in POSIX (IANA v3+ style) \
1650 time zone must be in range -167..=167"
1651 )
1652 })?;
1653 Ok(hour)
1654 }
1655
1656 fn parse_hour_posix(&self) -> Result<PosixHour, Error> {
1665 type PosixHour24 = ri8<0, 24>;
1666
1667 let number = self
1668 .parse_number_with_upto_n_digits(2)
1669 .map_err(|e| e.context("invalid hour digits"))?;
1670 let hour = PosixHour24::new(number).ok_or_else(|| {
1671 err!("hour in POSIX time zone must be in range 0..=24")
1672 })?;
1673 Ok(hour.rinto())
1674 }
1675
1676 fn parse_minute(&self) -> Result<Minute, Error> {
1684 let number = self
1685 .parse_number_with_exactly_n_digits(2)
1686 .map_err(|e| e.context("invalid minute digits"))?;
1687 let minute = Minute::new(number).ok_or_else(|| {
1688 err!("minute in POSIX time zone must be in range 0..=59")
1689 })?;
1690 Ok(minute)
1691 }
1692
1693 fn parse_second(&self) -> Result<Second, Error> {
1701 let number = self
1702 .parse_number_with_exactly_n_digits(2)
1703 .map_err(|e| e.context("invalid second digits"))?;
1704 let second = Second::new(number).ok_or_else(|| {
1705 err!("second in POSIX time zone must be in range 0..=59")
1706 })?;
1707 Ok(second)
1708 }
1709
1710 fn parse_number_with_exactly_n_digits(
1719 &self,
1720 n: usize,
1721 ) -> Result<i64, Error> {
1722 assert!(n >= 1, "numbers must have at least 1 digit");
1723 let start = self.pos();
1724 for i in 0..n {
1725 if self.is_done() {
1726 return Err(err!("expected {n} digits, but found {i}"));
1727 }
1728 if !self.byte().is_ascii_digit() {
1729 return Err(err!("invalid digit '{}'", Byte(self.byte())));
1730 }
1731 self.bump();
1732 }
1733 let end = self.pos();
1734 parse::i64(&self.tz[start..end])
1735 }
1736
1737 fn parse_number_with_upto_n_digits(&self, n: usize) -> Result<i64, Error> {
1744 assert!(n >= 1, "numbers must have at least 1 digit");
1745 let start = self.pos();
1746 for _ in 0..n {
1747 if self.is_done() || !self.byte().is_ascii_digit() {
1748 break;
1749 }
1750 self.bump();
1751 }
1752 let end = self.pos();
1753 parse::i64(&self.tz[start..end])
1754 }
1755
1756 fn parse_optional_sign(&self) -> Result<Option<Sign>, Error> {
1764 if self.is_done() {
1765 return Ok(None);
1766 }
1767 Ok(match self.byte() {
1768 b'-' => {
1769 if !self.bump() {
1770 return Err(err!(
1771 "expected digit after '-' sign, but got end of input",
1772 ));
1773 }
1774 Some(Sign::N::<-1>())
1775 }
1776 b'+' => {
1777 if !self.bump() {
1778 return Err(err!(
1779 "expected digit after '+' sign, but got end of input",
1780 ));
1781 }
1782 Some(Sign::N::<1>())
1783 }
1784 _ => None,
1785 })
1786 }
1787}
1788
1789impl<'s> Parser<'s> {
1791 fn bump(&self) -> bool {
1795 if self.is_done() {
1796 return false;
1797 }
1798 self.pos.set(
1799 self.pos().checked_add(1).expect("pos cannot overflow usize"),
1800 );
1801 !self.is_done()
1802 }
1803
1804 fn is_done(&self) -> bool {
1806 self.pos() == self.tz.len()
1807 }
1808
1809 fn byte(&self) -> u8 {
1813 self.tz[self.pos()]
1814 }
1815
1816 fn maybe_byte(&self) -> Option<u8> {
1819 self.tz.get(self.pos()).copied()
1820 }
1821
1822 fn pos(&self) -> usize {
1826 self.pos.get()
1827 }
1828
1829 fn remaining(&self) -> &'s [u8] {
1833 &self.tz[self.pos()..]
1834 }
1835}
1836
1837#[derive(Debug)]
1842struct AbbreviationDisplay(Abbreviation);
1843
1844impl core::fmt::Display for AbbreviationDisplay {
1845 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1846 let s = self.0.as_str();
1847 if s.chars().any(|ch| ch == '+' || ch == '-') {
1848 write!(f, "<{s}>")
1849 } else {
1850 write!(f, "{s}")
1851 }
1852 }
1853}
1854
1855#[cfg(test)]
1858mod tests {
1859 use std::string::ToString;
1860
1861 use crate::civil::date;
1862
1863 use super::*;
1864
1865 fn reasonable_posix_time_zone(
1866 input: impl AsRef<[u8]>,
1867 ) -> ReasonablePosixTimeZone {
1868 let input = core::str::from_utf8(input.as_ref()).unwrap();
1869 let tz = IanaTz::parse_v3plus(input).unwrap().into_tz();
1870 assert_eq!(tz.to_string(), input);
1874 tz
1875 }
1876
1877 #[cfg(feature = "std")]
1883 #[test]
1884 fn debug_posix_tz() -> anyhow::Result<()> {
1885 const ENV: &str = "JIFF_DEBUG_POSIX_TZ";
1886 let Some(val) = std::env::var_os(ENV) else { return Ok(()) };
1887 let val = val
1888 .to_str()
1889 .ok_or_else(|| err!("{ENV} contains invalid UTF-8"))?;
1890 let tz = Parser::new(val).parse()?;
1891 std::eprintln!("{tz:#?}");
1892 Ok(())
1893 }
1894
1895 #[cfg(feature = "std")]
1901 #[test]
1902 fn debug_iana_tz() -> anyhow::Result<()> {
1903 const ENV: &str = "JIFF_DEBUG_IANA_TZ";
1904 let Some(val) = std::env::var_os(ENV) else { return Ok(()) };
1905 let val = val
1906 .to_str()
1907 .ok_or_else(|| err!("{ENV} contains invalid UTF-8"))?;
1908 let tz = Parser { ianav3plus: true, ..Parser::new(val) }.parse()?;
1909 std::eprintln!("{tz:#?}");
1910 Ok(())
1911 }
1912
1913 #[test]
1914 fn reasonable_to_dst_civil_datetime_utc_range() {
1915 let tz = reasonable_posix_time_zone("WART4WARST,J1/-3,J365/20");
1916 let dst_info = DstInfo {
1917 dst: tz.dst.as_ref().unwrap(),
1921 offset: crate::tz::offset(-3),
1922 start: date(2024, 1, 1).at(1, 0, 0, 0),
1923 end: date(2024, 12, 31).at(23, 0, 0, 0),
1924 };
1925 assert_eq!(tz.dst_info_utc(C(2024)), Some(dst_info));
1926
1927 let tz = reasonable_posix_time_zone("WART4WARST,J1/-4,J365/21");
1928 let dst_info = DstInfo {
1929 dst: tz.dst.as_ref().unwrap(),
1930 offset: crate::tz::offset(-3),
1931 start: date(2024, 1, 1).at(0, 0, 0, 0),
1932 end: date(2024, 12, 31).at(23, 59, 59, 999_999_999),
1933 };
1934 assert_eq!(tz.dst_info_utc(C(2024)), Some(dst_info));
1935
1936 let tz = reasonable_posix_time_zone("EST5EDT,M3.2.0,M11.1.0");
1937 let dst_info = DstInfo {
1938 dst: tz.dst.as_ref().unwrap(),
1939 offset: crate::tz::offset(-4),
1940 start: date(2024, 3, 10).at(7, 0, 0, 0),
1941 end: date(2024, 11, 3).at(6, 0, 0, 0),
1942 };
1943 assert_eq!(tz.dst_info_utc(C(2024)), Some(dst_info));
1944 }
1945
1946 #[test]
1947 fn reasonable() {
1948 assert!(PosixTimeZone::parse("EST5").unwrap().reasonable().is_ok());
1949 assert!(PosixTimeZone::parse("EST5EDT")
1950 .unwrap()
1951 .reasonable()
1952 .is_err());
1953 assert!(PosixTimeZone::parse("EST5EDT,J1,J365")
1954 .unwrap()
1955 .reasonable()
1956 .is_ok());
1957
1958 let tz = reasonable_posix_time_zone("EST24EDT,J1,J365");
1959 assert_eq!(
1960 tz,
1961 ReasonablePosixTimeZone {
1962 std_abbrev: "EST".into(),
1963 std_offset: PosixOffset {
1964 sign: None,
1965 hour: C(24).rinto(),
1966 minute: None,
1967 second: None,
1968 },
1969 dst: Some(ReasonablePosixDst {
1970 abbrev: "EDT".into(),
1971 offset: None,
1972 rule: Rule {
1973 start: PosixDateTimeSpec {
1974 date: PosixDateSpec::JulianOne(C(1).rinto()),
1975 time: None,
1976 },
1977 end: PosixDateTimeSpec {
1978 date: PosixDateSpec::JulianOne(C(365).rinto()),
1979 time: None,
1980 },
1981 },
1982 }),
1983 },
1984 );
1985
1986 let tz = reasonable_posix_time_zone("EST-24EDT,J1,J365");
1987 assert_eq!(
1988 tz,
1989 ReasonablePosixTimeZone {
1990 std_abbrev: "EST".into(),
1991 std_offset: PosixOffset {
1992 sign: Some(C(-1).rinto()),
1993 hour: C(24).rinto(),
1994 minute: None,
1995 second: None,
1996 },
1997 dst: Some(ReasonablePosixDst {
1998 abbrev: "EDT".into(),
1999 offset: None,
2000 rule: Rule {
2001 start: PosixDateTimeSpec {
2002 date: PosixDateSpec::JulianOne(C(1).rinto()),
2003 time: None,
2004 },
2005 end: PosixDateTimeSpec {
2006 date: PosixDateSpec::JulianOne(C(365).rinto()),
2007 time: None,
2008 },
2009 },
2010 }),
2011 },
2012 );
2013 }
2014
2015 #[test]
2016 fn posix_date_time_spec_to_datetime() {
2017 let to_datetime = |spec: &PosixDateTimeSpec, year: i16| {
2020 let year = Year::new(year).unwrap();
2021 spec.to_datetime(year, crate::tz::offset(0))
2022 };
2023
2024 let tz = reasonable_posix_time_zone("EST5EDT,J1,J365/5:12:34");
2025 assert_eq!(
2026 to_datetime(&tz.rule().start, 2023),
2027 date(2023, 1, 1).at(2, 0, 0, 0),
2028 );
2029 assert_eq!(
2030 to_datetime(&tz.rule().end, 2023),
2031 date(2023, 12, 31).at(5, 12, 34, 0),
2032 );
2033
2034 let tz = reasonable_posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2");
2035 assert_eq!(
2036 to_datetime(&tz.rule().start, 2024),
2037 date(2024, 3, 10).at(2, 0, 0, 0),
2038 );
2039 assert_eq!(
2040 to_datetime(&tz.rule().end, 2024),
2041 date(2024, 11, 3).at(2, 0, 0, 0),
2042 );
2043
2044 let tz = reasonable_posix_time_zone("EST+5EDT,M1.1.1,M12.5.2");
2045 assert_eq!(
2046 to_datetime(&tz.rule().start, 2024),
2047 date(2024, 1, 1).at(2, 0, 0, 0),
2048 );
2049 assert_eq!(
2050 to_datetime(&tz.rule().end, 2024),
2051 date(2024, 12, 31).at(2, 0, 0, 0),
2052 );
2053
2054 let tz = reasonable_posix_time_zone("EST5EDT,0/0,J365/25");
2055 assert_eq!(
2056 to_datetime(&tz.rule().start, 2024),
2057 date(2024, 1, 1).at(0, 0, 0, 0),
2058 );
2059 assert_eq!(
2060 to_datetime(&tz.rule().end, 2024),
2061 date(2024, 12, 31).at(23, 59, 59, 999_999_999),
2062 );
2063
2064 let tz = reasonable_posix_time_zone("XXX3EDT4,0/0,J365/23");
2065 assert_eq!(
2066 to_datetime(&tz.rule().start, 2024),
2067 date(2024, 1, 1).at(0, 0, 0, 0),
2068 );
2069 assert_eq!(
2070 to_datetime(&tz.rule().end, 2024),
2071 date(2024, 12, 31).at(23, 0, 0, 0),
2072 );
2073
2074 let tz = reasonable_posix_time_zone("XXX3EDT4,0/0,365");
2075 assert_eq!(
2076 to_datetime(&tz.rule().end, 2023),
2077 date(2023, 12, 31).at(23, 59, 59, 999_999_999),
2078 );
2079 assert_eq!(
2080 to_datetime(&tz.rule().end, 2024),
2081 date(2024, 12, 31).at(2, 0, 0, 0),
2082 );
2083
2084 let tz = reasonable_posix_time_zone(
2085 "XXX3EDT4,J1/-167:59:59,J365/167:59:59",
2086 );
2087 assert_eq!(
2088 to_datetime(&tz.rule().start, 2024),
2089 date(2024, 1, 1).at(0, 0, 0, 0),
2090 );
2091 assert_eq!(
2092 to_datetime(&tz.rule().end, 2024),
2093 date(2024, 12, 31).at(23, 59, 59, 999_999_999),
2094 );
2095 }
2096
2097 #[test]
2098 fn posix_date_time_spec_time() {
2099 let tz = reasonable_posix_time_zone("EST5EDT,J1,J365/5:12:34");
2100 assert_eq!(
2101 tz.rule().start.time(),
2102 PosixTimeSpec {
2103 sign: None,
2104 hour: C(2).rinto(),
2105 minute: None,
2106 second: None,
2107 },
2108 );
2109 assert_eq!(
2110 tz.rule().end.time(),
2111 PosixTimeSpec {
2112 sign: None,
2113 hour: C(5).rinto(),
2114 minute: Some(C(12).rinto()),
2115 second: Some(C(34).rinto()),
2116 },
2117 );
2118 }
2119
2120 #[test]
2121 fn posix_date_spec_to_date() {
2122 let tz = reasonable_posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2");
2123 let start = tz.rule().start.date.to_civil_date(C(2023));
2124 assert_eq!(start, Some(date(2023, 3, 12)));
2125 let end = tz.rule().end.date.to_civil_date(C(2023));
2126 assert_eq!(end, Some(date(2023, 11, 5)));
2127 let start = tz.rule().start.date.to_civil_date(C(2024));
2128 assert_eq!(start, Some(date(2024, 3, 10)));
2129 let end = tz.rule().end.date.to_civil_date(C(2024));
2130 assert_eq!(end, Some(date(2024, 11, 3)));
2131
2132 let tz = reasonable_posix_time_zone("EST+5EDT,J60,J365");
2133 let start = tz.rule().start.date.to_civil_date(C(2023));
2134 assert_eq!(start, Some(date(2023, 3, 1)));
2135 let end = tz.rule().end.date.to_civil_date(C(2023));
2136 assert_eq!(end, Some(date(2023, 12, 31)));
2137 let start = tz.rule().start.date.to_civil_date(C(2024));
2138 assert_eq!(start, Some(date(2024, 3, 1)));
2139 let end = tz.rule().end.date.to_civil_date(C(2024));
2140 assert_eq!(end, Some(date(2024, 12, 31)));
2141
2142 let tz = reasonable_posix_time_zone("EST+5EDT,59,365");
2143 let start = tz.rule().start.date.to_civil_date(C(2023));
2144 assert_eq!(start, Some(date(2023, 3, 1)));
2145 let end = tz.rule().end.date.to_civil_date(C(2023));
2146 assert_eq!(end, None);
2147 let start = tz.rule().start.date.to_civil_date(C(2024));
2148 assert_eq!(start, Some(date(2024, 2, 29)));
2149 let end = tz.rule().end.date.to_civil_date(C(2024));
2150 assert_eq!(end, Some(date(2024, 12, 31)));
2151
2152 let tz = reasonable_posix_time_zone("EST+5EDT,M1.1.1,M12.5.2");
2153 let start = tz.rule().start.date.to_civil_date(C(2024));
2154 assert_eq!(start, Some(date(2024, 1, 1)));
2155 let end = tz.rule().end.date.to_civil_date(C(2024));
2156 assert_eq!(end, Some(date(2024, 12, 31)));
2157 }
2158
2159 #[test]
2160 fn posix_time_spec_to_civil_time() {
2161 let tz = reasonable_posix_time_zone("EST5EDT,J1,J365/5:12:34");
2162 assert_eq!(
2163 tz.dst.as_ref().unwrap().rule.start.time().to_duration(),
2164 SignedDuration::from_hours(2),
2165 );
2166 assert_eq!(
2167 tz.dst.as_ref().unwrap().rule.end.time().to_duration(),
2168 SignedDuration::new(5 * 60 * 60 + 12 * 60 + 34, 0),
2169 );
2170
2171 let tz =
2172 reasonable_posix_time_zone("EST5EDT,J1/23:59:59,J365/24:00:00");
2173 assert_eq!(
2174 tz.dst.as_ref().unwrap().rule.start.time().to_duration(),
2175 SignedDuration::new(23 * 60 * 60 + 59 * 60 + 59, 0),
2176 );
2177 assert_eq!(
2178 tz.dst.as_ref().unwrap().rule.end.time().to_duration(),
2179 SignedDuration::from_hours(24),
2180 );
2181
2182 let tz = reasonable_posix_time_zone("EST5EDT,J1/-1,J365/167:00:00");
2183 assert_eq!(
2184 tz.dst.as_ref().unwrap().rule.start.time().to_duration(),
2185 SignedDuration::from_hours(-1),
2186 );
2187 assert_eq!(
2188 tz.dst.as_ref().unwrap().rule.end.time().to_duration(),
2189 SignedDuration::from_hours(167),
2190 );
2191 }
2192
2193 #[test]
2194 fn posix_time_spec_to_span() {
2195 let tz = reasonable_posix_time_zone("EST5EDT,J1,J365/5:12:34");
2196 assert_eq!(
2197 tz.dst.as_ref().unwrap().rule.start.time().to_duration(),
2198 SignedDuration::from_hours(2),
2199 );
2200 assert_eq!(
2201 tz.dst.as_ref().unwrap().rule.end.time().to_duration(),
2202 SignedDuration::from_secs((5 * 60 * 60) + (12 * 60) + 34),
2203 );
2204
2205 let tz =
2206 reasonable_posix_time_zone("EST5EDT,J1/23:59:59,J365/24:00:00");
2207 assert_eq!(
2208 tz.dst.as_ref().unwrap().rule.start.time().to_duration(),
2209 SignedDuration::from_secs((23 * 60 * 60) + (59 * 60) + 59),
2210 );
2211 assert_eq!(
2212 tz.dst.as_ref().unwrap().rule.end.time().to_duration(),
2213 SignedDuration::from_hours(24),
2214 );
2215
2216 let tz = reasonable_posix_time_zone("EST5EDT,J1/-1,J365/167:00:00");
2217 assert_eq!(
2218 tz.dst.as_ref().unwrap().rule.start.time().to_duration(),
2219 SignedDuration::from_hours(-1),
2220 );
2221 assert_eq!(
2222 tz.dst.as_ref().unwrap().rule.end.time().to_duration(),
2223 SignedDuration::from_hours(167),
2224 );
2225 }
2226
2227 #[cfg(feature = "tz-system")]
2228 #[test]
2229 fn parse_posix_tz() {
2230 let tz = PosixTz::parse("EST5EDT").unwrap();
2231 assert_eq!(
2232 tz,
2233 PosixTz::Rule(PosixTimeZone {
2234 std_abbrev: "EST".into(),
2235 std_offset: PosixOffset {
2236 sign: None,
2237 hour: C(5).rinto(),
2238 minute: None,
2239 second: None,
2240 },
2241 dst: Some(PosixDst {
2242 abbrev: "EDT".into(),
2243 offset: None,
2244 rule: None,
2245 }),
2246 },)
2247 );
2248
2249 let tz = PosixTz::parse(":EST5EDT").unwrap();
2250 assert_eq!(tz, PosixTz::Implementation("EST5EDT".into()));
2251
2252 assert!(PosixTz::parse(b":EST5\xFFEDT").is_err());
2255 }
2256
2257 #[test]
2258 fn parse_iana() {
2259 let p = IanaTz::parse_v3plus("CRAZY5SHORT,M12.5.0/50,0/2").unwrap();
2261 assert_eq!(
2262 p,
2263 IanaTz(ReasonablePosixTimeZone {
2264 std_abbrev: "CRAZY".into(),
2265 std_offset: PosixOffset {
2266 sign: None,
2267 hour: C(5).rinto(),
2268 minute: None,
2269 second: None,
2270 },
2271 dst: Some(ReasonablePosixDst {
2272 abbrev: "SHORT".into(),
2273 offset: None,
2274 rule: Rule {
2275 start: PosixDateTimeSpec {
2276 date: PosixDateSpec::WeekdayOfMonth(
2277 WeekdayOfMonth {
2278 month: C(12).rinto(),
2279 week: C(5).rinto(),
2280 weekday: Weekday::Sunday,
2281 },
2282 ),
2283 time: Some(PosixTimeSpec {
2284 sign: None,
2285 hour: C(50).rinto(),
2286 minute: None,
2287 second: None,
2288 },),
2289 },
2290 end: PosixDateTimeSpec {
2291 date: PosixDateSpec::JulianZero(C(0).rinto()),
2292 time: Some(PosixTimeSpec {
2293 sign: None,
2294 hour: C(2).rinto(),
2295 minute: None,
2296 second: None,
2297 },),
2298 },
2299 },
2300 }),
2301 }),
2302 );
2303
2304 let p = Parser::new("America/New_York");
2305 assert!(p.parse().is_err());
2306
2307 let p = Parser::new(":America/New_York");
2308 assert!(p.parse().is_err());
2309 }
2310
2311 #[test]
2312 fn parse() {
2313 let p = Parser::new("NZST-12NZDT,J60,J300");
2314 assert_eq!(
2315 p.parse().unwrap(),
2316 PosixTimeZone {
2317 std_abbrev: "NZST".into(),
2318 std_offset: PosixOffset {
2319 sign: Some(Sign::N::<-1>()),
2320 hour: C(12).rinto(),
2321 minute: None,
2322 second: None,
2323 },
2324 dst: Some(PosixDst {
2325 abbrev: "NZDT".into(),
2326 offset: None,
2327 rule: Some(Rule {
2328 start: PosixDateTimeSpec {
2329 date: PosixDateSpec::JulianOne(C(60).rinto()),
2330 time: None,
2331 },
2332 end: PosixDateTimeSpec {
2333 date: PosixDateSpec::JulianOne(C(300).rinto()),
2334 time: None,
2335 },
2336 }),
2337 }),
2338 },
2339 );
2340
2341 let p = Parser::new("NZST-12NZDT,J60,J300WAT");
2342 assert!(p.parse().is_err());
2343 }
2344
2345 #[test]
2346 fn parse_posix_time_zone() {
2347 let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3");
2348 assert_eq!(
2349 p.parse_posix_time_zone().unwrap(),
2350 PosixTimeZone {
2351 std_abbrev: "NZST".into(),
2352 std_offset: PosixOffset {
2353 sign: Some(Sign::N::<-1>()),
2354 hour: C(12).rinto(),
2355 minute: None,
2356 second: None,
2357 },
2358 dst: Some(PosixDst {
2359 abbrev: "NZDT".into(),
2360 offset: None,
2361 rule: Some(Rule {
2362 start: PosixDateTimeSpec {
2363 date: PosixDateSpec::WeekdayOfMonth(
2364 WeekdayOfMonth {
2365 month: C(9).rinto(),
2366 week: C(5).rinto(),
2367 weekday: Weekday::Sunday,
2368 }
2369 ),
2370 time: None,
2371 },
2372 end: PosixDateTimeSpec {
2373 date: PosixDateSpec::WeekdayOfMonth(
2374 WeekdayOfMonth {
2375 month: C(4).rinto(),
2376 week: C(1).rinto(),
2377 weekday: Weekday::Sunday,
2378 }
2379 ),
2380 time: Some(PosixTimeSpec {
2381 sign: None,
2382 hour: C(3).rinto(),
2383 minute: None,
2384 second: None,
2385 }),
2386 },
2387 })
2388 }),
2389 },
2390 );
2391
2392 let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3WAT");
2393 assert_eq!(
2394 p.parse_posix_time_zone().unwrap(),
2395 PosixTimeZone {
2396 std_abbrev: "NZST".into(),
2397 std_offset: PosixOffset {
2398 sign: Some(Sign::N::<-1>()),
2399 hour: C(12).rinto(),
2400 minute: None,
2401 second: None,
2402 },
2403 dst: Some(PosixDst {
2404 abbrev: "NZDT".into(),
2405 offset: None,
2406 rule: Some(Rule {
2407 start: PosixDateTimeSpec {
2408 date: PosixDateSpec::WeekdayOfMonth(
2409 WeekdayOfMonth {
2410 month: C(9).rinto(),
2411 week: C(5).rinto(),
2412 weekday: Weekday::Sunday,
2413 }
2414 ),
2415 time: None,
2416 },
2417 end: PosixDateTimeSpec {
2418 date: PosixDateSpec::WeekdayOfMonth(
2419 WeekdayOfMonth {
2420 month: C(4).rinto(),
2421 week: C(1).rinto(),
2422 weekday: Weekday::Sunday,
2423 }
2424 ),
2425 time: Some(PosixTimeSpec {
2426 sign: None,
2427 hour: C(3).rinto(),
2428 minute: None,
2429 second: None,
2430 }),
2431 },
2432 })
2433 }),
2434 },
2435 );
2436
2437 let p = Parser::new("NZST-12NZDT,J60,J300");
2438 assert_eq!(
2439 p.parse_posix_time_zone().unwrap(),
2440 PosixTimeZone {
2441 std_abbrev: "NZST".into(),
2442 std_offset: PosixOffset {
2443 sign: Some(Sign::N::<-1>()),
2444 hour: C(12).rinto(),
2445 minute: None,
2446 second: None,
2447 },
2448 dst: Some(PosixDst {
2449 abbrev: "NZDT".into(),
2450 offset: None,
2451 rule: Some(Rule {
2452 start: PosixDateTimeSpec {
2453 date: PosixDateSpec::JulianOne(C(60).rinto()),
2454 time: None,
2455 },
2456 end: PosixDateTimeSpec {
2457 date: PosixDateSpec::JulianOne(C(300).rinto()),
2458 time: None,
2459 },
2460 }),
2461 }),
2462 },
2463 );
2464
2465 let p = Parser::new("NZST-12NZDT,J60,J300WAT");
2466 assert_eq!(
2467 p.parse_posix_time_zone().unwrap(),
2468 PosixTimeZone {
2469 std_abbrev: "NZST".into(),
2470 std_offset: PosixOffset {
2471 sign: Some(Sign::N::<-1>()),
2472 hour: C(12).rinto(),
2473 minute: None,
2474 second: None,
2475 },
2476 dst: Some(PosixDst {
2477 abbrev: "NZDT".into(),
2478 offset: None,
2479 rule: Some(Rule {
2480 start: PosixDateTimeSpec {
2481 date: PosixDateSpec::JulianOne(C(60).rinto()),
2482 time: None,
2483 },
2484 end: PosixDateTimeSpec {
2485 date: PosixDateSpec::JulianOne(C(300).rinto()),
2486 time: None,
2487 },
2488 }),
2489 }),
2490 },
2491 );
2492 }
2493
2494 #[test]
2495 fn parse_posix_dst() {
2496 let p = Parser::new("NZDT,M9.5.0,M4.1.0/3");
2497 assert_eq!(
2498 p.parse_posix_dst().unwrap(),
2499 PosixDst {
2500 abbrev: "NZDT".into(),
2501 offset: None,
2502 rule: Some(Rule {
2503 start: PosixDateTimeSpec {
2504 date: PosixDateSpec::WeekdayOfMonth(WeekdayOfMonth {
2505 month: C(9).rinto(),
2506 week: C(5).rinto(),
2507 weekday: Weekday::Sunday,
2508 }),
2509 time: None,
2510 },
2511 end: PosixDateTimeSpec {
2512 date: PosixDateSpec::WeekdayOfMonth(WeekdayOfMonth {
2513 month: C(4).rinto(),
2514 week: C(1).rinto(),
2515 weekday: Weekday::Sunday,
2516 }),
2517 time: Some(PosixTimeSpec {
2518 sign: None,
2519 hour: C(3).rinto(),
2520 minute: None,
2521 second: None,
2522 }),
2523 },
2524 }),
2525 },
2526 );
2527
2528 let p = Parser::new("NZDT,J60,J300");
2529 assert_eq!(
2530 p.parse_posix_dst().unwrap(),
2531 PosixDst {
2532 abbrev: "NZDT".into(),
2533 offset: None,
2534 rule: Some(Rule {
2535 start: PosixDateTimeSpec {
2536 date: PosixDateSpec::JulianOne(C(60).rinto()),
2537 time: None,
2538 },
2539 end: PosixDateTimeSpec {
2540 date: PosixDateSpec::JulianOne(C(300).rinto()),
2541 time: None,
2542 },
2543 }),
2544 },
2545 );
2546
2547 let p = Parser::new("NZDT-7,J60,J300");
2548 assert_eq!(
2549 p.parse_posix_dst().unwrap(),
2550 PosixDst {
2551 abbrev: "NZDT".into(),
2552 offset: Some(PosixOffset {
2553 sign: Some(Sign::N::<-1>()),
2554 hour: C(7).rinto(),
2555 minute: None,
2556 second: None,
2557 }),
2558 rule: Some(Rule {
2559 start: PosixDateTimeSpec {
2560 date: PosixDateSpec::JulianOne(C(60).rinto()),
2561 time: None,
2562 },
2563 end: PosixDateTimeSpec {
2564 date: PosixDateSpec::JulianOne(C(300).rinto()),
2565 time: None,
2566 },
2567 }),
2568 },
2569 );
2570
2571 let p = Parser::new("NZDT+7,J60,J300");
2572 assert_eq!(
2573 p.parse_posix_dst().unwrap(),
2574 PosixDst {
2575 abbrev: "NZDT".into(),
2576 offset: Some(PosixOffset {
2577 sign: Some(Sign::N::<1>()),
2578 hour: C(7).rinto(),
2579 minute: None,
2580 second: None,
2581 }),
2582 rule: Some(Rule {
2583 start: PosixDateTimeSpec {
2584 date: PosixDateSpec::JulianOne(C(60).rinto()),
2585 time: None,
2586 },
2587 end: PosixDateTimeSpec {
2588 date: PosixDateSpec::JulianOne(C(300).rinto()),
2589 time: None,
2590 },
2591 }),
2592 },
2593 );
2594
2595 let p = Parser::new("NZDT7,J60,J300");
2596 assert_eq!(
2597 p.parse_posix_dst().unwrap(),
2598 PosixDst {
2599 abbrev: "NZDT".into(),
2600 offset: Some(PosixOffset {
2601 sign: None,
2602 hour: C(7).rinto(),
2603 minute: None,
2604 second: None,
2605 }),
2606 rule: Some(Rule {
2607 start: PosixDateTimeSpec {
2608 date: PosixDateSpec::JulianOne(C(60).rinto()),
2609 time: None,
2610 },
2611 end: PosixDateTimeSpec {
2612 date: PosixDateSpec::JulianOne(C(300).rinto()),
2613 time: None,
2614 },
2615 }),
2616 },
2617 );
2618
2619 let p = Parser::new("NZDT7,");
2620 assert!(p.parse_posix_dst().is_err());
2621
2622 let p = Parser::new("NZDT7!");
2623 assert!(p.parse_posix_dst().is_err());
2624 }
2625
2626 #[test]
2627 fn parse_abbreviation() {
2628 let p = Parser::new("ABC");
2629 assert_eq!(p.parse_abbreviation().unwrap(), "ABC");
2630
2631 let p = Parser::new("<ABC>");
2632 assert_eq!(p.parse_abbreviation().unwrap(), "ABC");
2633
2634 let p = Parser::new("<+09>");
2635 assert_eq!(p.parse_abbreviation().unwrap(), "+09");
2636
2637 let p = Parser::new("+09");
2638 assert!(p.parse_abbreviation().is_err());
2639 }
2640
2641 #[test]
2642 fn parse_unquoted_abbreviation() {
2643 let p = Parser::new("ABC");
2644 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC");
2645
2646 let p = Parser::new("ABCXYZ");
2647 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABCXYZ");
2648
2649 let p = Parser::new("ABC123");
2650 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC");
2651
2652 let tz = "a".repeat(30);
2653 let p = Parser::new(&tz);
2654 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), &*tz);
2655
2656 let p = Parser::new("a");
2657 assert!(p.parse_unquoted_abbreviation().is_err());
2658
2659 let p = Parser::new("ab");
2660 assert!(p.parse_unquoted_abbreviation().is_err());
2661
2662 let p = Parser::new("ab1");
2663 assert!(p.parse_unquoted_abbreviation().is_err());
2664
2665 let tz = "a".repeat(31);
2666 let p = Parser::new(&tz);
2667 assert!(p.parse_unquoted_abbreviation().is_err());
2668
2669 let p = Parser::new(b"ab\xFFcd");
2670 assert!(p.parse_unquoted_abbreviation().is_err());
2671 }
2672
2673 #[test]
2674 fn parse_quoted_abbreviation() {
2675 let p = Parser::new("ABC>");
2680 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC");
2681
2682 let p = Parser::new("ABCXYZ>");
2683 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABCXYZ");
2684
2685 let p = Parser::new("ABC>123");
2686 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC");
2687
2688 let p = Parser::new("ABC123>");
2689 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC123");
2690
2691 let p = Parser::new("ab1>");
2692 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ab1");
2693
2694 let p = Parser::new("+09>");
2695 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "+09");
2696
2697 let p = Parser::new("-09>");
2698 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "-09");
2699
2700 let tz = alloc::format!("{}>", "a".repeat(30));
2701 let p = Parser::new(&tz);
2702 assert_eq!(
2703 p.parse_quoted_abbreviation().unwrap(),
2704 tz.trim_end_matches(">")
2705 );
2706
2707 let p = Parser::new("a>");
2708 assert!(p.parse_quoted_abbreviation().is_err());
2709
2710 let p = Parser::new("ab>");
2711 assert!(p.parse_quoted_abbreviation().is_err());
2712
2713 let tz = alloc::format!("{}>", "a".repeat(31));
2714 let p = Parser::new(&tz);
2715 assert!(p.parse_quoted_abbreviation().is_err());
2716
2717 let p = Parser::new(b"ab\xFFcd>");
2718 assert!(p.parse_quoted_abbreviation().is_err());
2719
2720 let p = Parser::new("ABC");
2721 assert!(p.parse_quoted_abbreviation().is_err());
2722
2723 let p = Parser::new("ABC!>");
2724 assert!(p.parse_quoted_abbreviation().is_err());
2725 }
2726
2727 #[test]
2728 fn parse_posix_offset() {
2729 let p = Parser::new("5");
2730 assert_eq!(
2731 p.parse_posix_offset().unwrap(),
2732 PosixOffset {
2733 sign: None,
2734 hour: C(5).rinto(),
2735 minute: None,
2736 second: None,
2737 },
2738 );
2739
2740 let p = Parser::new("+5");
2741 assert_eq!(
2742 p.parse_posix_offset().unwrap(),
2743 PosixOffset {
2744 sign: Some(Sign::N::<1>()),
2745 hour: C(5).rinto(),
2746 minute: None,
2747 second: None,
2748 },
2749 );
2750
2751 let p = Parser::new("-5");
2752 assert_eq!(
2753 p.parse_posix_offset().unwrap(),
2754 PosixOffset {
2755 sign: Some(Sign::N::<-1>()),
2756 hour: C(5).rinto(),
2757 minute: None,
2758 second: None,
2759 },
2760 );
2761
2762 let p = Parser::new("-12:34:56");
2763 assert_eq!(
2764 p.parse_posix_offset().unwrap(),
2765 PosixOffset {
2766 sign: Some(Sign::N::<-1>()),
2767 hour: C(12).rinto(),
2768 minute: Some(C(34).rinto()),
2769 second: Some(C(56).rinto()),
2770 },
2771 );
2772
2773 let p = Parser::new("a");
2774 assert!(p.parse_posix_offset().is_err());
2775
2776 let p = Parser::new("-");
2777 assert!(p.parse_posix_offset().is_err());
2778
2779 let p = Parser::new("+");
2780 assert!(p.parse_posix_offset().is_err());
2781
2782 let p = Parser::new("-a");
2783 assert!(p.parse_posix_offset().is_err());
2784
2785 let p = Parser::new("+a");
2786 assert!(p.parse_posix_offset().is_err());
2787
2788 let p = Parser::new("-25");
2789 assert!(p.parse_posix_offset().is_err());
2790
2791 let p = Parser::new("+25");
2792 assert!(p.parse_posix_offset().is_err());
2793
2794 let p = Parser { ianav3plus: true, ..Parser::new("25") };
2802 assert!(p.parse_posix_offset().is_err());
2803 let p = Parser { ianav3plus: true, ..Parser::new("+25") };
2804 assert!(p.parse_posix_offset().is_err());
2805 let p = Parser { ianav3plus: true, ..Parser::new("-25") };
2806 assert!(p.parse_posix_offset().is_err());
2807 }
2808
2809 #[test]
2810 fn parse_rule() {
2811 let p = Parser::new("M9.5.0,M4.1.0/3");
2812 assert_eq!(
2813 p.parse_rule().unwrap(),
2814 Rule {
2815 start: PosixDateTimeSpec {
2816 date: PosixDateSpec::WeekdayOfMonth(WeekdayOfMonth {
2817 month: C(9).rinto(),
2818 week: C(5).rinto(),
2819 weekday: Weekday::Sunday,
2820 }),
2821 time: None,
2822 },
2823 end: PosixDateTimeSpec {
2824 date: PosixDateSpec::WeekdayOfMonth(WeekdayOfMonth {
2825 month: C(4).rinto(),
2826 week: C(1).rinto(),
2827 weekday: Weekday::Sunday,
2828 }),
2829 time: Some(PosixTimeSpec {
2830 sign: None,
2831 hour: C(3).rinto(),
2832 minute: None,
2833 second: None,
2834 }),
2835 },
2836 },
2837 );
2838
2839 let p = Parser::new("M9.5.0");
2840 assert!(p.parse_rule().is_err());
2841
2842 let p = Parser::new(",M9.5.0,M4.1.0/3");
2843 assert!(p.parse_rule().is_err());
2844
2845 let p = Parser::new("M9.5.0/");
2846 assert!(p.parse_rule().is_err());
2847
2848 let p = Parser::new("M9.5.0,M4.1.0/");
2849 assert!(p.parse_rule().is_err());
2850 }
2851
2852 #[test]
2853 fn parse_posix_datetime_spec() {
2854 let p = Parser::new("J1");
2855 assert_eq!(
2856 p.parse_posix_datetime_spec().unwrap(),
2857 PosixDateTimeSpec {
2858 date: PosixDateSpec::JulianOne(C(1).rinto()),
2859 time: None,
2860 },
2861 );
2862
2863 let p = Parser::new("J1/3");
2864 assert_eq!(
2865 p.parse_posix_datetime_spec().unwrap(),
2866 PosixDateTimeSpec {
2867 date: PosixDateSpec::JulianOne(C(1).rinto()),
2868 time: Some(PosixTimeSpec {
2869 sign: None,
2870 hour: C(3).rinto(),
2871 minute: None,
2872 second: None,
2873 }),
2874 },
2875 );
2876
2877 let p = Parser::new("M4.1.0/3");
2878 assert_eq!(
2879 p.parse_posix_datetime_spec().unwrap(),
2880 PosixDateTimeSpec {
2881 date: PosixDateSpec::WeekdayOfMonth(WeekdayOfMonth {
2882 month: C(4).rinto(),
2883 week: C(1).rinto(),
2884 weekday: Weekday::Sunday,
2885 }),
2886 time: Some(PosixTimeSpec {
2887 sign: None,
2888 hour: C(3).rinto(),
2889 minute: None,
2890 second: None,
2891 }),
2892 },
2893 );
2894
2895 let p = Parser::new("1/3:45:05");
2896 assert_eq!(
2897 p.parse_posix_datetime_spec().unwrap(),
2898 PosixDateTimeSpec {
2899 date: PosixDateSpec::JulianZero(C(1).rinto()),
2900 time: Some(PosixTimeSpec {
2901 sign: None,
2902 hour: C(3).rinto(),
2903 minute: Some(C(45).rinto()),
2904 second: Some(C(5).rinto()),
2905 }),
2906 },
2907 );
2908
2909 let p = Parser::new("a");
2910 assert!(p.parse_posix_datetime_spec().is_err());
2911
2912 let p = Parser::new("J1/");
2913 assert!(p.parse_posix_datetime_spec().is_err());
2914
2915 let p = Parser::new("1/");
2916 assert!(p.parse_posix_datetime_spec().is_err());
2917
2918 let p = Parser::new("M4.1.0/");
2919 assert!(p.parse_posix_datetime_spec().is_err());
2920 }
2921
2922 #[test]
2923 fn parse_posix_date_spec() {
2924 let p = Parser::new("J1");
2925 assert_eq!(
2926 p.parse_posix_date_spec().unwrap(),
2927 PosixDateSpec::JulianOne(C(1).rinto())
2928 );
2929 let p = Parser::new("J365");
2930 assert_eq!(
2931 p.parse_posix_date_spec().unwrap(),
2932 PosixDateSpec::JulianOne(C(365).rinto())
2933 );
2934
2935 let p = Parser::new("0");
2936 assert_eq!(
2937 p.parse_posix_date_spec().unwrap(),
2938 PosixDateSpec::JulianZero(C(0).rinto())
2939 );
2940 let p = Parser::new("1");
2941 assert_eq!(
2942 p.parse_posix_date_spec().unwrap(),
2943 PosixDateSpec::JulianZero(C(1).rinto())
2944 );
2945 let p = Parser::new("365");
2946 assert_eq!(
2947 p.parse_posix_date_spec().unwrap(),
2948 PosixDateSpec::JulianZero(C(365).rinto())
2949 );
2950
2951 let p = Parser::new("M9.5.0");
2952 assert_eq!(
2953 p.parse_posix_date_spec().unwrap(),
2954 PosixDateSpec::WeekdayOfMonth(WeekdayOfMonth {
2955 month: C(9).rinto(),
2956 week: C(5).rinto(),
2957 weekday: Weekday::Sunday,
2958 }),
2959 );
2960 let p = Parser::new("M9.5.6");
2961 assert_eq!(
2962 p.parse_posix_date_spec().unwrap(),
2963 PosixDateSpec::WeekdayOfMonth(WeekdayOfMonth {
2964 month: C(9).rinto(),
2965 week: C(5).rinto(),
2966 weekday: Weekday::Saturday,
2967 }),
2968 );
2969 let p = Parser::new("M09.5.6");
2970 assert_eq!(
2971 p.parse_posix_date_spec().unwrap(),
2972 PosixDateSpec::WeekdayOfMonth(WeekdayOfMonth {
2973 month: C(9).rinto(),
2974 week: C(5).rinto(),
2975 weekday: Weekday::Saturday,
2976 }),
2977 );
2978 let p = Parser::new("M12.1.1");
2979 assert_eq!(
2980 p.parse_posix_date_spec().unwrap(),
2981 PosixDateSpec::WeekdayOfMonth(WeekdayOfMonth {
2982 month: C(12).rinto(),
2983 week: C(1).rinto(),
2984 weekday: Weekday::Monday,
2985 }),
2986 );
2987
2988 let p = Parser::new("a");
2989 assert!(p.parse_posix_date_spec().is_err());
2990
2991 let p = Parser::new("j");
2992 assert!(p.parse_posix_date_spec().is_err());
2993
2994 let p = Parser::new("m");
2995 assert!(p.parse_posix_date_spec().is_err());
2996
2997 let p = Parser::new("n");
2998 assert!(p.parse_posix_date_spec().is_err());
2999
3000 let p = Parser::new("J366");
3001 assert!(p.parse_posix_date_spec().is_err());
3002
3003 let p = Parser::new("366");
3004 assert!(p.parse_posix_date_spec().is_err());
3005 }
3006
3007 #[test]
3008 fn parse_posix_julian_day_no_leap() {
3009 let p = Parser::new("1");
3010 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1);
3011
3012 let p = Parser::new("001");
3013 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1);
3014
3015 let p = Parser::new("365");
3016 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365);
3017
3018 let p = Parser::new("3655");
3019 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365);
3020
3021 let p = Parser::new("0");
3022 assert!(p.parse_posix_julian_day_no_leap().is_err());
3023
3024 let p = Parser::new("366");
3025 assert!(p.parse_posix_julian_day_no_leap().is_err());
3026 }
3027
3028 #[test]
3029 fn parse_posix_julian_day_with_leap() {
3030 let p = Parser::new("0");
3031 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 0);
3032
3033 let p = Parser::new("1");
3034 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1);
3035
3036 let p = Parser::new("001");
3037 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1);
3038
3039 let p = Parser::new("365");
3040 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365);
3041
3042 let p = Parser::new("3655");
3043 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365);
3044
3045 let p = Parser::new("366");
3046 assert!(p.parse_posix_julian_day_with_leap().is_err());
3047 }
3048
3049 #[test]
3050 fn parse_weekday_of_month() {
3051 let p = Parser::new("9.5.0");
3052 assert_eq!(
3053 p.parse_weekday_of_month().unwrap(),
3054 WeekdayOfMonth {
3055 month: C(9).rinto(),
3056 week: C(5).rinto(),
3057 weekday: Weekday::Sunday,
3058 },
3059 );
3060
3061 let p = Parser::new("9.1.6");
3062 assert_eq!(
3063 p.parse_weekday_of_month().unwrap(),
3064 WeekdayOfMonth {
3065 month: C(9).rinto(),
3066 week: C(1).rinto(),
3067 weekday: Weekday::Saturday,
3068 },
3069 );
3070
3071 let p = Parser::new("09.1.6");
3072 assert_eq!(
3073 p.parse_weekday_of_month().unwrap(),
3074 WeekdayOfMonth {
3075 month: C(9).rinto(),
3076 week: C(1).rinto(),
3077 weekday: Weekday::Saturday,
3078 },
3079 );
3080
3081 let p = Parser::new("9");
3082 assert!(p.parse_weekday_of_month().is_err());
3083
3084 let p = Parser::new("9.");
3085 assert!(p.parse_weekday_of_month().is_err());
3086
3087 let p = Parser::new("9.5");
3088 assert!(p.parse_weekday_of_month().is_err());
3089
3090 let p = Parser::new("9.5.");
3091 assert!(p.parse_weekday_of_month().is_err());
3092
3093 let p = Parser::new("0.5.0");
3094 assert!(p.parse_weekday_of_month().is_err());
3095
3096 let p = Parser::new("13.5.0");
3097 assert!(p.parse_weekday_of_month().is_err());
3098
3099 let p = Parser::new("9.0.0");
3100 assert!(p.parse_weekday_of_month().is_err());
3101
3102 let p = Parser::new("9.6.0");
3103 assert!(p.parse_weekday_of_month().is_err());
3104
3105 let p = Parser::new("9.5.7");
3106 assert!(p.parse_weekday_of_month().is_err());
3107 }
3108
3109 #[test]
3110 fn parse_posix_time_spec() {
3111 let p = Parser::new("5");
3112 assert_eq!(
3113 p.parse_posix_time_spec().unwrap(),
3114 PosixTimeSpec {
3115 sign: None,
3116 hour: C(5).rinto(),
3117 minute: None,
3118 second: None
3119 }
3120 );
3121
3122 let p = Parser::new("22");
3123 assert_eq!(
3124 p.parse_posix_time_spec().unwrap(),
3125 PosixTimeSpec {
3126 sign: None,
3127 hour: C(22).rinto(),
3128 minute: None,
3129 second: None
3130 }
3131 );
3132
3133 let p = Parser::new("02");
3134 assert_eq!(
3135 p.parse_posix_time_spec().unwrap(),
3136 PosixTimeSpec {
3137 sign: None,
3138 hour: C(2).rinto(),
3139 minute: None,
3140 second: None
3141 }
3142 );
3143
3144 let p = Parser::new("5:45");
3145 assert_eq!(
3146 p.parse_posix_time_spec().unwrap(),
3147 PosixTimeSpec {
3148 sign: None,
3149 hour: C(5).rinto(),
3150 minute: Some(C(45).rinto()),
3151 second: None
3152 }
3153 );
3154
3155 let p = Parser::new("5:45:12");
3156 assert_eq!(
3157 p.parse_posix_time_spec().unwrap(),
3158 PosixTimeSpec {
3159 sign: None,
3160 hour: C(5).rinto(),
3161 minute: Some(C(45).rinto()),
3162 second: Some(C(12).rinto()),
3163 }
3164 );
3165
3166 let p = Parser::new("5:45:129");
3167 assert_eq!(
3168 p.parse_posix_time_spec().unwrap(),
3169 PosixTimeSpec {
3170 sign: None,
3171 hour: C(5).rinto(),
3172 minute: Some(C(45).rinto()),
3173 second: Some(C(12).rinto()),
3174 }
3175 );
3176
3177 let p = Parser::new("5:45:12:");
3178 assert_eq!(
3179 p.parse_posix_time_spec().unwrap(),
3180 PosixTimeSpec {
3181 sign: None,
3182 hour: C(5).rinto(),
3183 minute: Some(C(45).rinto()),
3184 second: Some(C(12).rinto()),
3185 }
3186 );
3187
3188 let p = Parser { ianav3plus: true, ..Parser::new("+5:45:12") };
3189 assert_eq!(
3190 p.parse_posix_time_spec().unwrap(),
3191 PosixTimeSpec {
3192 sign: Some(C(1).rinto()),
3193 hour: C(5).rinto(),
3194 minute: Some(C(45).rinto()),
3195 second: Some(C(12).rinto()),
3196 }
3197 );
3198
3199 let p = Parser { ianav3plus: true, ..Parser::new("-5:45:12") };
3200 assert_eq!(
3201 p.parse_posix_time_spec().unwrap(),
3202 PosixTimeSpec {
3203 sign: Some(C(-1).rinto()),
3204 hour: C(5).rinto(),
3205 minute: Some(C(45).rinto()),
3206 second: Some(C(12).rinto()),
3207 }
3208 );
3209
3210 let p = Parser { ianav3plus: true, ..Parser::new("-167:45:12") };
3211 assert_eq!(
3212 p.parse_posix_time_spec().unwrap(),
3213 PosixTimeSpec {
3214 sign: Some(C(-1).rinto()),
3215 hour: C(167).rinto(),
3216 minute: Some(C(45).rinto()),
3217 second: Some(C(12).rinto()),
3218 }
3219 );
3220
3221 let p = Parser::new("25");
3222 assert!(p.parse_posix_time_spec().is_err());
3223
3224 let p = Parser::new("12:2");
3225 assert!(p.parse_posix_time_spec().is_err());
3226
3227 let p = Parser::new("12:");
3228 assert!(p.parse_posix_time_spec().is_err());
3229
3230 let p = Parser::new("12:23:5");
3231 assert!(p.parse_posix_time_spec().is_err());
3232
3233 let p = Parser::new("12:23:");
3234 assert!(p.parse_posix_time_spec().is_err());
3235
3236 let p = Parser { ianav3plus: true, ..Parser::new("168") };
3237 assert!(p.parse_posix_time_spec().is_err());
3238
3239 let p = Parser { ianav3plus: true, ..Parser::new("-168") };
3240 assert!(p.parse_posix_time_spec().is_err());
3241
3242 let p = Parser { ianav3plus: true, ..Parser::new("+168") };
3243 assert!(p.parse_posix_time_spec().is_err());
3244 }
3245
3246 #[test]
3247 fn parse_month() {
3248 let p = Parser::new("1");
3249 assert_eq!(p.parse_month().unwrap(), 1);
3250
3251 let p = Parser::new("01");
3256 assert_eq!(p.parse_month().unwrap(), 1);
3257
3258 let p = Parser::new("12");
3259 assert_eq!(p.parse_month().unwrap(), 12);
3260
3261 let p = Parser::new("0");
3262 assert!(p.parse_month().is_err());
3263
3264 let p = Parser::new("00");
3265 assert!(p.parse_month().is_err());
3266
3267 let p = Parser::new("001");
3268 assert!(p.parse_month().is_err());
3269
3270 let p = Parser::new("13");
3271 assert!(p.parse_month().is_err());
3272 }
3273
3274 #[test]
3275 fn parse_week() {
3276 let p = Parser::new("1");
3277 assert_eq!(p.parse_week().unwrap(), 1);
3278
3279 let p = Parser::new("5");
3280 assert_eq!(p.parse_week().unwrap(), 5);
3281
3282 let p = Parser::new("55");
3283 assert_eq!(p.parse_week().unwrap(), 5);
3284
3285 let p = Parser::new("0");
3286 assert!(p.parse_week().is_err());
3287
3288 let p = Parser::new("6");
3289 assert!(p.parse_week().is_err());
3290
3291 let p = Parser::new("00");
3292 assert!(p.parse_week().is_err());
3293
3294 let p = Parser::new("01");
3295 assert!(p.parse_week().is_err());
3296
3297 let p = Parser::new("05");
3298 assert!(p.parse_week().is_err());
3299 }
3300
3301 #[test]
3302 fn parse_weekday() {
3303 let p = Parser::new("0");
3304 assert_eq!(p.parse_weekday().unwrap(), Weekday::Sunday);
3305
3306 let p = Parser::new("1");
3307 assert_eq!(p.parse_weekday().unwrap(), Weekday::Monday);
3308
3309 let p = Parser::new("6");
3310 assert_eq!(p.parse_weekday().unwrap(), Weekday::Saturday);
3311
3312 let p = Parser::new("00");
3313 assert_eq!(p.parse_weekday().unwrap(), Weekday::Sunday);
3314
3315 let p = Parser::new("06");
3316 assert_eq!(p.parse_weekday().unwrap(), Weekday::Sunday);
3317
3318 let p = Parser::new("60");
3319 assert_eq!(p.parse_weekday().unwrap(), Weekday::Saturday);
3320
3321 let p = Parser::new("7");
3322 assert!(p.parse_weekday().is_err());
3323 }
3324
3325 #[test]
3326 fn parse_hour_posix() {
3327 let p = Parser::new("5");
3328 assert_eq!(p.parse_hour_posix().unwrap(), 5);
3329
3330 let p = Parser::new("0");
3331 assert_eq!(p.parse_hour_posix().unwrap(), 0);
3332
3333 let p = Parser::new("00");
3334 assert_eq!(p.parse_hour_posix().unwrap(), 0);
3335
3336 let p = Parser::new("24");
3337 assert_eq!(p.parse_hour_posix().unwrap(), 24);
3338
3339 let p = Parser::new("100");
3340 assert_eq!(p.parse_hour_posix().unwrap(), 10);
3341
3342 let p = Parser::new("25");
3343 assert!(p.parse_hour_posix().is_err());
3344
3345 let p = Parser::new("99");
3346 assert!(p.parse_hour_posix().is_err());
3347 }
3348
3349 #[test]
3350 fn parse_hour_ianav3plus() {
3351 let new = |input| Parser { ianav3plus: true, ..Parser::new(input) };
3352
3353 let p = new("5");
3354 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 5);
3355
3356 let p = new("0");
3357 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
3358
3359 let p = new("00");
3360 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
3361
3362 let p = new("000");
3363 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
3364
3365 let p = new("24");
3366 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 24);
3367
3368 let p = new("100");
3369 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100);
3370
3371 let p = new("1000");
3372 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100);
3373
3374 let p = new("167");
3375 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 167);
3376
3377 let p = new("168");
3378 assert!(p.parse_hour_ianav3plus().is_err());
3379
3380 let p = new("999");
3381 assert!(p.parse_hour_ianav3plus().is_err());
3382 }
3383
3384 #[test]
3385 fn parse_minute() {
3386 let p = Parser::new("00");
3387 assert_eq!(p.parse_minute().unwrap(), 0);
3388
3389 let p = Parser::new("24");
3390 assert_eq!(p.parse_minute().unwrap(), 24);
3391
3392 let p = Parser::new("59");
3393 assert_eq!(p.parse_minute().unwrap(), 59);
3394
3395 let p = Parser::new("599");
3396 assert_eq!(p.parse_minute().unwrap(), 59);
3397
3398 let p = Parser::new("0");
3399 assert!(p.parse_minute().is_err());
3400
3401 let p = Parser::new("1");
3402 assert!(p.parse_minute().is_err());
3403
3404 let p = Parser::new("9");
3405 assert!(p.parse_minute().is_err());
3406
3407 let p = Parser::new("60");
3408 assert!(p.parse_minute().is_err());
3409 }
3410
3411 #[test]
3412 fn parse_second() {
3413 let p = Parser::new("00");
3414 assert_eq!(p.parse_second().unwrap(), 0);
3415
3416 let p = Parser::new("24");
3417 assert_eq!(p.parse_second().unwrap(), 24);
3418
3419 let p = Parser::new("59");
3420 assert_eq!(p.parse_second().unwrap(), 59);
3421
3422 let p = Parser::new("599");
3423 assert_eq!(p.parse_second().unwrap(), 59);
3424
3425 let p = Parser::new("0");
3426 assert!(p.parse_second().is_err());
3427
3428 let p = Parser::new("1");
3429 assert!(p.parse_second().is_err());
3430
3431 let p = Parser::new("9");
3432 assert!(p.parse_second().is_err());
3433
3434 let p = Parser::new("60");
3435 assert!(p.parse_second().is_err());
3436 }
3437
3438 #[test]
3439 fn parse_number_with_exactly_n_digits() {
3440 let p = Parser::new("1");
3441 assert_eq!(p.parse_number_with_exactly_n_digits(1).unwrap(), 1);
3442
3443 let p = Parser::new("12");
3444 assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12);
3445
3446 let p = Parser::new("123");
3447 assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12);
3448
3449 let p = Parser::new("");
3450 assert!(p.parse_number_with_exactly_n_digits(1).is_err());
3451
3452 let p = Parser::new("1");
3453 assert!(p.parse_number_with_exactly_n_digits(2).is_err());
3454
3455 let p = Parser::new("12");
3456 assert!(p.parse_number_with_exactly_n_digits(3).is_err());
3457 }
3458
3459 #[test]
3460 fn parse_number_with_upto_n_digits() {
3461 let p = Parser::new("1");
3462 assert_eq!(p.parse_number_with_upto_n_digits(1).unwrap(), 1);
3463
3464 let p = Parser::new("1");
3465 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 1);
3466
3467 let p = Parser::new("12");
3468 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12);
3469
3470 let p = Parser::new("12");
3471 assert_eq!(p.parse_number_with_upto_n_digits(3).unwrap(), 12);
3472
3473 let p = Parser::new("123");
3474 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12);
3475
3476 let p = Parser::new("");
3477 assert!(p.parse_number_with_upto_n_digits(1).is_err());
3478
3479 let p = Parser::new("a");
3480 assert!(p.parse_number_with_upto_n_digits(1).is_err());
3481 }
3482}