1use crate::{
65 civil::{Date, DateTime, Time, TimeSecond, Weekday},
66 tz::{
67 Abbreviation, AmbiguousOffset, AmbiguousTimestamp, Offset, OffsetInfo,
68 TimeZoneId, Transition, REASONABLE_ABBREVIATION_MAX,
69 },
70 Timestamp,
71};
72
73#[derive(Debug, Eq, PartialEq)]
90pub enum TzEnv {
91 Rule(TimeZone),
93 Implementation(TimeZoneId),
98}
99
100impl TzEnv {
101 pub fn parse<B: AsRef<[u8]>>(bytes: B) -> Result<TzEnv, ParseError> {
103 let bytes = bytes.as_ref();
104 if bytes.get(0) == Some(&b':') {
105 let Ok(string) = core::str::from_utf8(&bytes[1..]) else {
106 return Err(ParseErrorKind::TzEnvColonInvalidUtf8.into());
107 };
108 let Some(smallstr) = TimeZoneId::new(string) else {
109 return Err(ParseErrorKind::TzEnvColonTooBig.into());
110 };
111 Ok(TzEnv::Implementation(smallstr))
112 } else {
113 TimeZone::parse(bytes).map(TzEnv::Rule)
114 }
115 }
116
117 #[cfg(feature = "std")]
119 pub fn parse_os_str<O: AsRef<std::ffi::OsStr>>(
120 osstr: O,
121 ) -> Result<TzEnv, ParseError> {
122 let bytes = crate::util::os_str_bytes(osstr.as_ref())
123 .ok_or(ParseError::from(ParseErrorKind::TzEnvInvalidUtf8))?;
124 TzEnv::parse(bytes)
125 }
126}
127
128#[derive(Clone, Debug, Eq, PartialEq)]
188#[repr(align(8))]
198pub struct TimeZone {
199 pub std_abbrev: Abbreviation,
201 pub std_offset: Offset,
203 pub dst: Option<Dst>,
205}
206
207#[derive(Clone, Debug, Eq, PartialEq)]
209pub struct Dst {
210 pub abbrev: Abbreviation,
213 pub offset: Offset,
216 pub rule: Rule,
218}
219
220#[derive(Clone, Copy, Debug, Eq, PartialEq)]
225pub struct Rule {
226 pub start: DayTime,
228 pub end: DayTime,
230}
231
232#[derive(Clone, Copy, Debug, Eq, PartialEq)]
235pub struct DayTime {
236 pub date: Day,
238 pub time: TransitionCivilTime,
240}
241
242#[derive(Clone, Copy, Debug, Eq, PartialEq)]
244pub enum Day {
245 JulianOne(i16),
249 JulianZero(i16),
253 WeekdayOfMonth {
255 month: i8,
259 week: i8,
268 weekday: Weekday,
270 },
271}
272
273#[derive(Clone, Copy, Debug, Eq, PartialEq)]
278pub struct TransitionCivilTime {
279 pub second: i32,
283}
284
285impl TimeZone {
286 pub fn parse<B: AsRef<[u8]>>(bytes: B) -> Result<TimeZone, ParseError> {
297 let bytes = bytes.as_ref();
298 let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
304 parser.parse()
305 }
306
307 pub fn parse_prefix<'b, B: AsRef<[u8]> + ?Sized>(
316 bytes: &'b B,
317 ) -> Result<(TimeZone, usize), ParseError> {
318 let bytes = bytes.as_ref();
319 let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
320 parser.parse_prefix()
321 }
322}
323
324impl TimeZone {
325 pub fn to_offset(&self, timestamp: Timestamp) -> Offset {
333 if self.dst.is_none() {
334 return self.std_offset;
335 }
336
337 let dt = timestamp.to_datetime(Offset::UTC);
338 self.dst_info_utc(dt.date().year())
339 .filter(|dst_info| dst_info.in_dst(dt))
340 .map(|dst_info| dst_info.offset())
341 .unwrap_or_else(|| self.std_offset)
342 }
343
344 pub fn to_offset_info(&self, timestamp: Timestamp) -> OffsetInfo {
351 if self.dst.is_none() {
352 return OffsetInfo {
353 offset: self.std_offset,
354 abbreviation: self.std_abbrev.clone(),
355 dst: super::Dst::No,
356 };
357 }
358
359 let dt = timestamp.to_datetime(Offset::UTC);
360 self.dst_info_utc(dt.date().year())
361 .filter(|dst_info| dst_info.in_dst(dt))
362 .map(|dst_info| OffsetInfo {
363 offset: dst_info.offset(),
364 abbreviation: dst_info.dst.abbrev.clone(),
365 dst: super::Dst::Yes,
366 })
367 .unwrap_or_else(|| OffsetInfo {
368 offset: self.std_offset,
369 abbreviation: self.std_abbrev.clone(),
370 dst: super::Dst::No,
371 })
372 }
373
374 pub fn to_ambiguous_timestamp(&self, dt: DateTime) -> AmbiguousTimestamp {
386 let year = dt.date().year();
387 let std_offset = self.std_offset;
388 let Some(dst_info) = self.dst_info_wall(year) else {
389 return AmbiguousOffset::Unambiguous { offset: std_offset }
390 .into_ambiguous_timestamp(dt);
391 };
392 let dst_offset = dst_info.offset();
393 let diff = std_offset.until(dst_offset);
394 let ambiguous_offset = if diff == 0 {
409 debug_assert_eq!(std_offset, dst_offset);
410 AmbiguousOffset::Unambiguous { offset: std_offset }
411 } else if diff.is_negative() {
412 if dst_info.in_dst(dt) {
416 AmbiguousOffset::Unambiguous { offset: dst_offset }
417 } else {
418 let fold_start = dst_info.start.saturating_add_seconds(diff);
419 let gap_end =
420 dst_info.end.saturating_add_seconds(diff.saturating_neg());
421 if fold_start <= dt && dt < dst_info.start {
422 AmbiguousOffset::Fold {
423 before: std_offset,
424 after: dst_offset,
425 }
426 } else if dst_info.end <= dt && dt < gap_end {
427 AmbiguousOffset::Gap {
428 before: dst_offset,
429 after: std_offset,
430 }
431 } else {
432 AmbiguousOffset::Unambiguous { offset: std_offset }
433 }
434 }
435 } else {
436 if !dst_info.in_dst(dt) {
440 AmbiguousOffset::Unambiguous { offset: std_offset }
441 } else {
442 let gap_end = dst_info.start.saturating_add_seconds(diff);
447 let fold_start =
448 dst_info.end.saturating_add_seconds(diff.saturating_neg());
449 if dst_info.start <= dt && dt < gap_end {
450 AmbiguousOffset::Gap {
451 before: std_offset,
452 after: dst_offset,
453 }
454 } else if fold_start <= dt && dt < dst_info.end {
455 AmbiguousOffset::Fold {
456 before: dst_offset,
457 after: std_offset,
458 }
459 } else {
460 AmbiguousOffset::Unambiguous { offset: dst_offset }
461 }
462 }
463 };
464 ambiguous_offset.into_ambiguous_timestamp(dt)
465 }
466
467 pub fn previous_transition(
470 &self,
471 timestamp: Timestamp,
472 ) -> Option<Transition> {
473 let dt = timestamp.to_datetime(Offset::UTC);
474 let dst_info = self.dst_info_utc(dt.date().year())?;
475 let (earlier, later) = dst_info.ordered();
476 let (prev, dst_info) = if dt > later {
477 (later, dst_info)
478 } else if dt > earlier {
479 (earlier, dst_info)
480 } else {
481 let prev_year = if dt.date().year() == Date::MIN.year() {
482 return None;
483 } else {
484 dt.date().year() - 1
485 };
486 let dst_info = self.dst_info_utc(prev_year)?;
487 let (_, later) = dst_info.ordered();
488 (later, dst_info)
489 };
490
491 let timestamp = prev.to_timestamp(Offset::UTC).ok()?;
492 let dt = timestamp.to_datetime(Offset::UTC);
493 let (offset, abbreviation, dst) = if dst_info.in_dst(dt) {
494 (dst_info.offset(), dst_info.dst.abbrev.clone(), super::Dst::Yes)
495 } else {
496 (self.std_offset, self.std_abbrev.clone(), super::Dst::No)
497 };
498 let info = OffsetInfo { offset, abbreviation, dst };
499 Some(Transition { timestamp, info })
500 }
501
502 pub fn next_transition(&self, timestamp: Timestamp) -> Option<Transition> {
505 let dt = timestamp.to_datetime(Offset::UTC);
506 let dst_info = self.dst_info_utc(dt.date().year())?;
507 let (earlier, later) = dst_info.ordered();
508 let (next, dst_info) = if dt < earlier {
509 (earlier, dst_info)
510 } else if dt < later {
511 (later, dst_info)
512 } else {
513 let next_year = if dt.date().year() == Date::MAX.year() {
514 return None;
515 } else {
516 dt.date().year() + 1
517 };
518 let dst_info = self.dst_info_utc(next_year)?;
519 let (earlier, _) = dst_info.ordered();
520 (earlier, dst_info)
521 };
522
523 let timestamp = next.to_timestamp(Offset::UTC).ok()?;
524 let dt = timestamp.to_datetime(Offset::UTC);
525 let (offset, abbreviation, dst) = if dst_info.in_dst(dt) {
526 (dst_info.offset(), dst_info.dst.abbrev.clone(), super::Dst::Yes)
527 } else {
528 (self.std_offset, self.std_abbrev.clone(), super::Dst::No)
529 };
530 let info = OffsetInfo { offset, abbreviation, dst };
531 Some(Transition { timestamp, info })
532 }
533
534 fn dst_info_utc(&self, year: i16) -> Option<DstInfo<'_>> {
539 let dst = self.dst.as_ref()?;
540 let start = dst.rule.start.to_datetime(year, self.std_offset);
543 let mut end = dst.rule.end.to_datetime(year, dst.offset);
546 if start.date().month() == 1
597 && start.date().day() == 1
598 && start.time() == Time::MIN
599 && year
601 != end.saturating_add_seconds(self.std_offset.seconds()).date().year()
602 {
603 end = DateTime::from_parts(
604 Date::new(year, 12, 31)
605 .expect("12/31 is valid for all valid years"),
606 Time::MAX,
607 );
608 }
609 Some(DstInfo { dst, start, end })
610 }
611
612 fn dst_info_wall(&self, year: i16) -> Option<DstInfo<'_>> {
618 let dst = self.dst.as_ref()?;
619 let start = dst.rule.start.to_datetime(year, Offset::UTC);
623 let end = dst.rule.end.to_datetime(year, Offset::UTC);
624 Some(DstInfo { dst, start, end })
625 }
626
627 #[cfg(test)]
630 fn rule(&self) -> &Rule {
631 &self.dst.as_ref().unwrap().rule
632 }
633}
634
635impl DayTime {
636 fn to_datetime(&self, year: i16, offset: Offset) -> DateTime {
648 let mkmin =
649 || DateTime::from_parts(Date::new(year, 1, 1).unwrap(), Time::MIN);
650 let mkmax = || {
651 DateTime::from_parts(Date::new(year, 12, 31).unwrap(), Time::MAX)
652 };
653 let Some(date) = self.date.to_date(year) else { return mkmax() };
654 let offset = self.time.second - offset.seconds();
658 let days = offset.div_euclid(86400);
661 let second = offset.rem_euclid(86400);
662
663 let Ok(date) = date.checked_add(days) else {
664 return if offset < 0 { mkmin() } else { mkmax() };
665 };
666 if date.year() < year {
667 mkmin()
668 } else if date.year() > year {
669 mkmax()
670 } else {
671 let time = TimeSecond::new(second).unwrap().to_time();
673 DateTime::from_parts(date, time)
674 }
675 }
676}
677
678impl Day {
679 fn to_date(&self, year: i16) -> Option<Date> {
686 match *self {
687 Day::JulianOne(day) => {
688 Some(
692 Date::from_day_of_year_no_leap(year, day)
693 .expect("Julian `J day` should be in bounds"),
694 )
695 }
696 Day::JulianZero(day) => {
697 Date::from_day_of_year(year, day + 1).ok()
707 }
708 Day::WeekdayOfMonth { month, week, weekday } => {
709 let first = Date::new(year, month, 1)
710 .expect("all valid year/month combinations support day 1");
711 let week = if week == 5 { -1 } else { week };
712 debug_assert!(week == -1 || (1..=4).contains(&week));
713 Some(
728 first
729 .nth_weekday_of_month(week, weekday)
730 .expect("nth weekday always exists"),
731 )
732 }
733 }
734 }
735}
736
737impl TransitionCivilTime {
738 pub const DEFAULT: TransitionCivilTime =
743 TransitionCivilTime { second: 2 * 60 * 60 };
744}
745
746#[derive(Debug, Eq, PartialEq)]
749struct DstInfo<'a> {
750 dst: &'a Dst,
752 start: DateTime,
761 end: DateTime,
770}
771
772impl<'a> DstInfo<'a> {
773 fn in_dst(&self, utc_dt: DateTime) -> bool {
776 if self.start <= self.end {
777 self.start <= utc_dt && utc_dt < self.end
778 } else {
779 !(self.end <= utc_dt && utc_dt < self.start)
780 }
781 }
782
783 fn ordered(&self) -> (DateTime, DateTime) {
785 if self.start <= self.end {
786 (self.start, self.end)
787 } else {
788 (self.end, self.start)
789 }
790 }
791
792 fn offset(&self) -> Offset {
794 self.dst.offset
795 }
796}
797
798#[derive(Debug)]
800struct Parser<'s> {
801 tz: &'s [u8],
803 pos: core::cell::Cell<usize>,
805 ianav3plus: bool,
819}
820
821impl<'s> Parser<'s> {
822 fn new<B: ?Sized + AsRef<[u8]>>(tz: &'s B) -> Parser<'s> {
825 Parser {
826 tz: tz.as_ref(),
827 pos: core::cell::Cell::new(0),
828 ianav3plus: false,
829 }
830 }
831
832 fn parse(&self) -> Result<TimeZone, ParseError> {
836 let (time_zone, len) = self.parse_prefix()?;
837 if !self.tz[len..].is_empty() {
838 return Err(ParseErrorKind::FoundRemaining.into());
839 }
840 Ok(time_zone)
841 }
842
843 fn parse_prefix(&self) -> Result<(TimeZone, usize), ParseError> {
846 let time_zone = self.parse_posix_time_zone()?;
847 Ok((time_zone, self.pos()))
848 }
849
850 #[inline(never)] fn parse_posix_time_zone(&self) -> Result<TimeZone, ParseError> {
856 if self.is_done() {
857 return Err(ParseErrorKind::Empty.into());
858 }
859 let std_abbrev = self
860 .parse_abbreviation()
861 .map_err(ParseErrorKind::AbbreviationStd)?;
862 let std_offset =
863 self.parse_posix_offset().map_err(ParseErrorKind::OffsetStd)?;
864 let mut dst = None;
865 if !self.is_done()
866 && (self.byte().is_ascii_alphabetic() || self.byte() == b'<')
867 {
868 dst = Some(self.parse_posix_dst(std_offset)?);
869 }
870 Ok(TimeZone { std_abbrev, std_offset, dst })
871 }
872
873 fn parse_posix_dst(&self, std_offset: Offset) -> Result<Dst, ParseError> {
882 let abbrev = self
883 .parse_abbreviation()
884 .map_err(ParseErrorKind::AbbreviationDst)?;
885 if self.is_done() {
886 return Err(ParseErrorKind::FoundDstNoRule.into());
887 }
888 let mut offset = std_offset.checked_add(3600).unwrap();
896 if self.byte() != b',' {
897 offset = self
898 .parse_posix_offset()
899 .map_err(ParseErrorKind::OffsetDst)?;
900 if self.is_done() {
901 return Err(ParseErrorKind::FoundDstNoRuleWithOffset.into());
902 }
903 }
904 if self.byte() != b',' {
905 return Err(ParseErrorKind::ExpectedCommaAfterDst.into());
906 }
907 if !self.bump() {
908 return Err(ParseErrorKind::FoundEndAfterComma.into());
909 }
910 let rule = self.parse_rule().map_err(ParseErrorKind::Rule)?;
911 Ok(Dst { abbrev, offset, rule })
912 }
913
914 fn parse_abbreviation(&self) -> Result<Abbreviation, AbbreviationError> {
927 if self.byte() == b'<' {
928 if !self.bump() {
929 return Err(AbbreviationError::Quoted(
930 QuotedAbbreviationError::UnexpectedEndAfterOpening,
931 ));
932 }
933 self.parse_quoted_abbreviation().map_err(AbbreviationError::Quoted)
934 } else {
935 self.parse_unquoted_abbreviation()
936 .map_err(AbbreviationError::Unquoted)
937 }
938 }
939
940 fn parse_unquoted_abbreviation(
952 &self,
953 ) -> Result<Abbreviation, UnquotedAbbreviationError> {
954 let start = self.pos();
955 for _ in 0.. {
956 if !self.byte().is_ascii_alphabetic() {
957 break;
958 }
959 if self.pos() - start >= REASONABLE_ABBREVIATION_MAX {
960 return Err(UnquotedAbbreviationError::TooLong);
961 }
962 if !self.bump() {
963 break;
964 }
965 }
966 let end = self.pos();
967 let abbrev =
968 core::str::from_utf8(&self.tz[start..end]).map_err(|_| {
969 UnquotedAbbreviationError::InvalidUtf8
976 })?;
977 if abbrev.len() < 3 {
978 return Err(UnquotedAbbreviationError::TooShort);
979 }
980 Abbreviation::new(abbrev).ok_or(UnquotedAbbreviationError::TooLong)
981 }
982
983 fn parse_quoted_abbreviation(
995 &self,
996 ) -> Result<Abbreviation, QuotedAbbreviationError> {
997 let start = self.pos();
998 for _ in 0.. {
999 if !self.byte().is_ascii_alphanumeric()
1000 && self.byte() != b'+'
1001 && self.byte() != b'-'
1002 {
1003 break;
1004 }
1005 if self.pos() - start >= REASONABLE_ABBREVIATION_MAX {
1006 return Err(QuotedAbbreviationError::TooLong);
1007 }
1008 if !self.bump() {
1009 break;
1010 }
1011 }
1012 let end = self.pos();
1013 let abbrev =
1014 core::str::from_utf8(&self.tz[start..end]).map_err(|_| {
1015 QuotedAbbreviationError::InvalidUtf8
1022 })?;
1023 if self.is_done() {
1024 return Err(QuotedAbbreviationError::UnexpectedEnd);
1025 }
1026 if self.byte() != b'>' {
1027 return Err(QuotedAbbreviationError::UnexpectedLastByte);
1028 }
1029 self.bump();
1030 if abbrev.len() < 3 {
1031 return Err(QuotedAbbreviationError::TooShort);
1032 }
1033 Abbreviation::new(abbrev).ok_or(QuotedAbbreviationError::TooLong)
1034 }
1035
1036 fn parse_posix_offset(&self) -> Result<Offset, OffsetError> {
1045 let sign = self.parse_optional_sign()?.unwrap_or(1);
1046 let hour = self.parse_hour_posix()?;
1047 let (mut minute, mut second) = (0, 0);
1048 if self.maybe_byte() == Some(b':') {
1049 if !self.bump() {
1050 return Err(OffsetError::IncompleteMinutes);
1051 }
1052 minute = self.parse_minute()?;
1053 if self.maybe_byte() == Some(b':') {
1054 if !self.bump() {
1055 return Err(OffsetError::IncompleteSeconds);
1056 }
1057 second = self.parse_second()?;
1058 }
1059 }
1060 let mut offset = Offset::from_hours(hour).expect("hours are valid");
1061 offset += i32::from(minute) * 60;
1062 offset += i32::from(second);
1063 if sign.is_positive() {
1066 offset = -offset;
1067 }
1068 assert!(
1072 -89999 <= offset.seconds() && offset.seconds() <= 89999,
1073 "POSIX offset seconds {} is out of range",
1074 offset.seconds(),
1075 );
1076 Ok(offset)
1077 }
1078
1079 fn parse_rule(&self) -> Result<Rule, RuleError> {
1089 let start =
1090 self.parse_posix_datetime().map_err(RuleError::DateTimeStart)?;
1091 if self.maybe_byte() != Some(b',') || !self.bump() {
1092 return Err(RuleError::ExpectedEnd);
1093 }
1094 let end =
1095 self.parse_posix_datetime().map_err(RuleError::DateTimeEnd)?;
1096 Ok(Rule { start, end })
1097 }
1098
1099 fn parse_posix_datetime(&self) -> Result<DayTime, DateTimeError> {
1108 let mut daytime = DayTime {
1109 date: self.parse_posix_date()?,
1110 time: TransitionCivilTime::DEFAULT,
1111 };
1112 if self.maybe_byte() != Some(b'/') {
1113 return Ok(daytime);
1114 }
1115 if !self.bump() {
1116 return Err(DateTimeError::ExpectedTime);
1117 }
1118 daytime.time = self.parse_posix_time()?;
1119 Ok(daytime)
1120 }
1121
1122 fn parse_posix_date(&self) -> Result<Day, DateError> {
1136 match self.byte() {
1137 b'J' => {
1138 if !self.bump() {
1139 return Err(DateError::ExpectedJulianNoLeap);
1140 }
1141 Ok(Day::JulianOne(self.parse_posix_julian_day_no_leap()?))
1142 }
1143 b'0'..=b'9' => {
1144 Ok(Day::JulianZero(self.parse_posix_julian_day_with_leap()?))
1145 }
1146 b'M' => {
1147 if !self.bump() {
1148 return Err(DateError::ExpectedMonthWeekWeekday);
1149 }
1150 let (month, week, weekday) = self.parse_weekday_of_month()?;
1151 Ok(Day::WeekdayOfMonth { month, week, weekday })
1152 }
1153 _ => Err(DateError::UnexpectedByte),
1154 }
1155 }
1156
1157 fn parse_posix_julian_day_no_leap(
1164 &self,
1165 ) -> Result<i16, JulianNoLeapError> {
1166 let number = self
1167 .parse_number_with_upto_n_digits(3)
1168 .map_err(JulianNoLeapError::Parse)?;
1169 let number =
1170 i16::try_from(number).map_err(|_| JulianNoLeapError::Range)?;
1171 if !(1 <= number && number <= 365) {
1172 return Err(JulianNoLeapError::Range);
1173 }
1174 Ok(number)
1175 }
1176
1177 fn parse_posix_julian_day_with_leap(
1184 &self,
1185 ) -> Result<i16, JulianLeapError> {
1186 let number = self
1187 .parse_number_with_upto_n_digits(3)
1188 .map_err(JulianLeapError::Parse)?;
1189 let number =
1190 i16::try_from(number).map_err(|_| JulianLeapError::Range)?;
1191 if !(0 <= number && number <= 365) {
1192 return Err(JulianLeapError::Range);
1193 }
1194 Ok(number)
1195 }
1196
1197 fn parse_weekday_of_month(
1207 &self,
1208 ) -> Result<(i8, i8, Weekday), WeekdayOfMonthError> {
1209 let month = self.parse_month()?;
1210 if self.maybe_byte() != Some(b'.') {
1211 return Err(WeekdayOfMonthError::ExpectedDotAfterMonth);
1212 }
1213 if !self.bump() {
1214 return Err(WeekdayOfMonthError::ExpectedWeekAfterMonth);
1215 }
1216 let week = self.parse_week()?;
1217 if self.maybe_byte() != Some(b'.') {
1218 return Err(WeekdayOfMonthError::ExpectedDotAfterWeek);
1219 }
1220 if !self.bump() {
1221 return Err(WeekdayOfMonthError::ExpectedDayOfWeekAfterWeek);
1222 }
1223 let weekday = self.parse_weekday()?;
1224 Ok((month, week, weekday))
1225 }
1226
1227 fn parse_posix_time(&self) -> Result<TransitionCivilTime, TimeError> {
1234 let (sign, hour) = if self.ianav3plus {
1235 let sign = self.parse_optional_sign()?.unwrap_or(1);
1236 let hour = self.parse_hour_ianav3plus()?;
1237 (sign, hour)
1238 } else {
1239 (1, i16::from(self.parse_hour_posix()?))
1240 };
1241 let (mut minute, mut second) = (0, 0);
1242 if self.maybe_byte() == Some(b':') {
1243 if !self.bump() {
1244 return Err(TimeError::IncompleteMinutes);
1245 }
1246 minute = self.parse_minute()?;
1247 if self.maybe_byte() == Some(b':') {
1248 if !self.bump() {
1249 return Err(TimeError::IncompleteSeconds);
1250 }
1251 second = self.parse_second()?;
1252 }
1253 }
1254 let mut time = TransitionCivilTime { second: i32::from(hour) * 3600 };
1255 time.second += i32::from(minute) * 60;
1256 time.second += i32::from(second);
1257 time.second *= i32::from(sign);
1258 assert!(
1262 -604799 <= time.second && time.second <= 604799,
1263 "POSIX time seconds {} is out of range",
1264 time.second
1265 );
1266 Ok(time)
1267 }
1268
1269 fn parse_month(&self) -> Result<i8, MonthError> {
1275 let number = self
1276 .parse_number_with_upto_n_digits(2)
1277 .map_err(MonthError::Parse)?;
1278 let number = i8::try_from(number).map_err(|_| MonthError::Range)?;
1279 if !(1 <= number && number <= 12) {
1280 return Err(MonthError::Range);
1281 }
1282 Ok(number)
1283 }
1284
1285 fn parse_week(&self) -> Result<i8, WeekOfMonthError> {
1290 let number = self
1291 .parse_number_with_exactly_n_digits(1)
1292 .map_err(WeekOfMonthError::Parse)?;
1293 let number =
1294 i8::try_from(number).map_err(|_| WeekOfMonthError::Range)?;
1295 if !(1 <= number && number <= 5) {
1296 return Err(WeekOfMonthError::Range);
1297 }
1298 Ok(number)
1299 }
1300
1301 fn parse_weekday(&self) -> Result<Weekday, WeekdayError> {
1309 let number = self
1310 .parse_number_with_exactly_n_digits(1)
1311 .map_err(WeekdayError::Parse)?;
1312 let number = i8::try_from(number).map_err(|_| WeekdayError::Range)?;
1313
1314 Weekday::from_sunday_zero_offset(number)
1315 .map_err(|_| WeekdayError::Range)
1316 }
1317
1318 fn parse_hour_ianav3plus(&self) -> Result<i16, HourIanaError> {
1330 assert!(self.ianav3plus);
1333 let number = self
1334 .parse_number_with_upto_n_digits(3)
1335 .map_err(HourIanaError::Parse)?;
1336 let number =
1337 i16::try_from(number).map_err(|_| HourIanaError::Range)?;
1338 if !(0 <= number && number <= 167) {
1339 return Err(HourIanaError::Range);
1343 }
1344 Ok(number)
1345 }
1346
1347 fn parse_hour_posix(&self) -> Result<i8, HourPosixError> {
1357 let number = self
1358 .parse_number_with_upto_n_digits(2)
1359 .map_err(HourPosixError::Parse)?;
1360 let number =
1361 i8::try_from(number).map_err(|_| HourPosixError::Range)?;
1362 if !(0 <= number && number <= 24) {
1363 return Err(HourPosixError::Range);
1364 }
1365 Ok(number)
1366 }
1367
1368 fn parse_minute(&self) -> Result<i8, MinuteError> {
1376 let number = self
1377 .parse_number_with_exactly_n_digits(2)
1378 .map_err(MinuteError::Parse)?;
1379 let number = i8::try_from(number).map_err(|_| MinuteError::Range)?;
1380 if !(0 <= number && number <= 59) {
1381 return Err(MinuteError::Range);
1382 }
1383 Ok(number)
1384 }
1385
1386 fn parse_second(&self) -> Result<i8, SecondError> {
1394 let number = self
1395 .parse_number_with_exactly_n_digits(2)
1396 .map_err(SecondError::Parse)?;
1397 let number = i8::try_from(number).map_err(|_| SecondError::Range)?;
1398 if !(0 <= number && number <= 59) {
1399 return Err(SecondError::Range);
1400 }
1401 Ok(number)
1402 }
1403
1404 fn parse_number_with_exactly_n_digits(
1413 &self,
1414 n: usize,
1415 ) -> Result<i32, NumberError> {
1416 assert!(n >= 1, "numbers must have at least 1 digit");
1417 let mut number: i32 = 0;
1418 for _ in 0..n {
1419 if self.is_done() {
1420 return Err(NumberError::ExpectedLength);
1421 }
1422 let byte = self.byte();
1423 let digit = match byte.checked_sub(b'0') {
1424 None => {
1425 return Err(NumberError::InvalidDigit);
1426 }
1427 Some(digit) if digit > 9 => {
1428 return Err(NumberError::InvalidDigit);
1429 }
1430 Some(digit) => {
1431 debug_assert!((0..=9).contains(&digit));
1432 i32::from(digit)
1433 }
1434 };
1435 number = number
1436 .checked_mul(10)
1437 .and_then(|n| n.checked_add(digit))
1438 .ok_or(NumberError::TooBig)?;
1439 self.bump();
1440 }
1441 Ok(number)
1442 }
1443
1444 fn parse_number_with_upto_n_digits(
1451 &self,
1452 n: usize,
1453 ) -> Result<i32, NumberError> {
1454 assert!(n >= 1, "numbers must have at least 1 digit");
1455 let mut number: i32 = 0;
1456 for i in 0..n {
1457 if self.is_done() || !self.byte().is_ascii_digit() {
1458 if i == 0 {
1459 return Err(NumberError::Empty);
1460 }
1461 break;
1462 }
1463 let digit = i32::from(self.byte() - b'0');
1464 number = number
1465 .checked_mul(10)
1466 .and_then(|n| n.checked_add(digit))
1467 .ok_or(NumberError::TooBig)?;
1468 self.bump();
1469 }
1470 Ok(number)
1471 }
1472
1473 fn parse_optional_sign(&self) -> Result<Option<i8>, OptionalSignError> {
1481 if self.is_done() {
1482 return Ok(None);
1483 }
1484 Ok(match self.byte() {
1485 b'-' => {
1486 if !self.bump() {
1487 return Err(OptionalSignError::ExpectedDigitAfterMinus);
1488 }
1489 Some(-1)
1490 }
1491 b'+' => {
1492 if !self.bump() {
1493 return Err(OptionalSignError::ExpectedDigitAfterPlus);
1494 }
1495 Some(1)
1496 }
1497 _ => None,
1498 })
1499 }
1500}
1501
1502impl<'s> Parser<'s> {
1504 fn bump(&self) -> bool {
1508 if self.is_done() {
1509 return false;
1510 }
1511 self.pos.set(
1512 self.pos().checked_add(1).expect("pos cannot overflow usize"),
1513 );
1514 !self.is_done()
1515 }
1516
1517 fn is_done(&self) -> bool {
1519 self.pos() == self.tz.len()
1520 }
1521
1522 fn byte(&self) -> u8 {
1527 self.tz[self.pos()]
1528 }
1529
1530 fn maybe_byte(&self) -> Option<u8> {
1533 self.tz.get(self.pos()).copied()
1534 }
1535
1536 fn pos(&self) -> usize {
1540 self.pos.get()
1541 }
1542}
1543
1544#[derive(Clone, Debug, Eq, PartialEq)]
1546#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1547pub struct ParseError {
1548 kind: ParseErrorKind,
1549}
1550
1551#[derive(Clone, Debug, Eq, PartialEq)]
1552#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1553enum ParseErrorKind {
1554 AbbreviationDst(AbbreviationError),
1555 AbbreviationStd(AbbreviationError),
1556 Empty,
1557 ExpectedCommaAfterDst,
1558 FoundDstNoRule,
1559 FoundDstNoRuleWithOffset,
1560 FoundEndAfterComma,
1561 FoundRemaining,
1562 OffsetDst(OffsetError),
1563 OffsetStd(OffsetError),
1564 Rule(RuleError),
1565 TzEnvColonTooBig,
1566 TzEnvColonInvalidUtf8,
1567 #[allow(dead_code)] TzEnvInvalidUtf8,
1569}
1570
1571impl core::fmt::Display for ParseError {
1572 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1573 use self::ParseErrorKind::*;
1574 match self.kind {
1575 AbbreviationDst(ref err) => {
1576 f.write_str("failed to parse DST time zone abbreviation: ")?;
1577 core::fmt::Display::fmt(err, f)
1578 }
1579 AbbreviationStd(ref err) => {
1580 f.write_str(
1581 "failed to parse standard time zone abbreviation: ",
1582 )?;
1583 core::fmt::Display::fmt(err, f)
1584 }
1585 Empty => f.write_str(
1586 "an empty string is not a valid POSIX time zone \
1587 transition rule",
1588 ),
1589 ExpectedCommaAfterDst => f.write_str(
1590 "expected `,` after parsing DST offset \
1591 in POSIX time zone string",
1592 ),
1593 FoundDstNoRule => f.write_str(
1594 "found DST abbreviation in POSIX time zone string, \
1595 but no transition rule \
1596 (this is technically allowed by POSIX, but has \
1597 unspecified behavior)",
1598 ),
1599 FoundDstNoRuleWithOffset => f.write_str(
1600 "found DST abbreviation and offset in POSIX time zone string, \
1601 but no transition rule \
1602 (this is technically allowed by POSIX, but has \
1603 unspecified behavior)",
1604 ),
1605 FoundEndAfterComma => f.write_str(
1606 "after parsing DST offset in POSIX time zone string, \
1607 found end of string after a trailing `,`",
1608 ),
1609 FoundRemaining => f.write_str(
1610 "expected entire POSIX TZ string to be a valid \
1611 time zone transition rule, but found data after \
1612 parsing a valid time zone transition rule",
1613 ),
1614 OffsetDst(ref err) => {
1615 f.write_str("failed to parse DST offset: ")?;
1616 core::fmt::Display::fmt(err, f)
1617 }
1618 OffsetStd(ref err) => {
1619 f.write_str("failed to parse standard offset: ")?;
1620 core::fmt::Display::fmt(err, f)
1621 }
1622 Rule(ref err) => core::fmt::Display::fmt(err, f),
1623 TzEnvColonTooBig => {
1624 f.write_str(
1625 "IANA time zone identifier is too big for core-only \
1626 environments \
1627 (must be less than or equal to ",
1628 )?;
1629 core::fmt::Display::fmt(&TimeZoneId::array_capacity_max(), f)?;
1630 f.write_str(" bytes)")
1631 }
1632 TzEnvColonInvalidUtf8 => f.write_str(
1633 "IANA time zone identifier is invalid UTF-8",
1634 ),
1635 TzEnvInvalidUtf8 => f.write_str(
1636 "POSIX transition rule or \
1637 IANA time zone identifier is invalid UTF-8",
1638 ),
1639 }
1640 }
1641}
1642
1643impl From<ParseErrorKind> for ParseError {
1644 fn from(kind: ParseErrorKind) -> ParseError {
1645 ParseError { kind }
1646 }
1647}
1648
1649#[derive(Clone, Debug, Eq, PartialEq)]
1650#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1651enum OffsetError {
1652 HourPosix(HourPosixError),
1653 IncompleteMinutes,
1654 IncompleteSeconds,
1655 Minute(MinuteError),
1656 OptionalSign(OptionalSignError),
1657 Second(SecondError),
1658}
1659
1660impl core::fmt::Display for OffsetError {
1661 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1662 use self::OffsetError::*;
1663 match *self {
1664 HourPosix(ref err) => core::fmt::Display::fmt(err, f),
1665 IncompleteMinutes => f.write_str(
1666 "incomplete time in \
1667 POSIX time zone string (missing minutes)",
1668 ),
1669 IncompleteSeconds => f.write_str(
1670 "incomplete time in \
1671 POSIX time zone string (missing seconds)",
1672 ),
1673 Minute(ref err) => core::fmt::Display::fmt(err, f),
1674 Second(ref err) => core::fmt::Display::fmt(err, f),
1675 OptionalSign(ref err) => {
1676 f.write_str(
1677 "failed to parse sign for time offset \
1678 POSIX time zone string",
1679 )?;
1680 core::fmt::Display::fmt(err, f)
1681 }
1682 }
1683 }
1684}
1685
1686impl From<HourPosixError> for OffsetError {
1687 fn from(err: HourPosixError) -> OffsetError {
1688 OffsetError::HourPosix(err)
1689 }
1690}
1691
1692impl From<MinuteError> for OffsetError {
1693 fn from(err: MinuteError) -> OffsetError {
1694 OffsetError::Minute(err)
1695 }
1696}
1697
1698impl From<OptionalSignError> for OffsetError {
1699 fn from(err: OptionalSignError) -> OffsetError {
1700 OffsetError::OptionalSign(err)
1701 }
1702}
1703
1704impl From<SecondError> for OffsetError {
1705 fn from(err: SecondError) -> OffsetError {
1706 OffsetError::Second(err)
1707 }
1708}
1709
1710#[derive(Clone, Debug, Eq, PartialEq)]
1711#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1712enum RuleError {
1713 DateTimeEnd(DateTimeError),
1714 DateTimeStart(DateTimeError),
1715 ExpectedEnd,
1716}
1717
1718impl core::fmt::Display for RuleError {
1719 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1720 use self::RuleError::*;
1721 match *self {
1722 DateTimeEnd(ref err) => {
1723 f.write_str("failed to parse end of DST transition rule: ")?;
1724 core::fmt::Display::fmt(err, f)
1725 }
1726 DateTimeStart(ref err) => {
1727 f.write_str("failed to parse start of DST transition rule: ")?;
1728 core::fmt::Display::fmt(err, f)
1729 }
1730 ExpectedEnd => f.write_str(
1731 "expected end of DST rule after parsing the start \
1732 of the DST rule",
1733 ),
1734 }
1735 }
1736}
1737
1738#[derive(Clone, Debug, Eq, PartialEq)]
1739#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1740enum DateTimeError {
1741 Date(DateError),
1742 ExpectedTime,
1743 Time(TimeError),
1744}
1745
1746impl core::fmt::Display for DateTimeError {
1747 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1748 use self::DateTimeError::*;
1749 match *self {
1750 Date(ref err) => core::fmt::Display::fmt(err, f),
1751 ExpectedTime => f.write_str(
1752 "expected time specification after `/` following a date
1753 specification in a POSIX time zone DST transition rule",
1754 ),
1755 Time(ref err) => core::fmt::Display::fmt(err, f),
1756 }
1757 }
1758}
1759
1760impl From<DateError> for DateTimeError {
1761 fn from(err: DateError) -> DateTimeError {
1762 DateTimeError::Date(err)
1763 }
1764}
1765
1766impl From<TimeError> for DateTimeError {
1767 fn from(err: TimeError) -> DateTimeError {
1768 DateTimeError::Time(err)
1769 }
1770}
1771
1772#[derive(Clone, Debug, Eq, PartialEq)]
1773#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1774enum DateError {
1775 ExpectedJulianNoLeap,
1776 ExpectedMonthWeekWeekday,
1777 JulianLeap(JulianLeapError),
1778 JulianNoLeap(JulianNoLeapError),
1779 UnexpectedByte,
1780 WeekdayOfMonth(WeekdayOfMonthError),
1781}
1782
1783impl core::fmt::Display for DateError {
1784 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1785 use self::DateError::*;
1786 match *self {
1787 ExpectedJulianNoLeap => f.write_str(
1788 "expected one-based Julian day after `J` in date \
1789 specification of a POSIX time zone DST \
1790 transition rule, but found the end of input",
1791 ),
1792 ExpectedMonthWeekWeekday => f.write_str(
1793 "expected month-week-weekday after `M` in date \
1794 specification of a POSIX time zone DST \
1795 transition rule, but found the end of input",
1796 ),
1797 JulianLeap(ref err) => core::fmt::Display::fmt(err, f),
1798 JulianNoLeap(ref err) => core::fmt::Display::fmt(err, f),
1799 UnexpectedByte => f.write_str(
1800 "expected `J`, a digit or `M` at the beginning of a date \
1801 specification of a POSIX time zone DST transition rule",
1802 ),
1803 WeekdayOfMonth(ref err) => core::fmt::Display::fmt(err, f),
1804 }
1805 }
1806}
1807
1808impl From<JulianLeapError> for DateError {
1809 fn from(err: JulianLeapError) -> DateError {
1810 DateError::JulianLeap(err)
1811 }
1812}
1813
1814impl From<JulianNoLeapError> for DateError {
1815 fn from(err: JulianNoLeapError) -> DateError {
1816 DateError::JulianNoLeap(err)
1817 }
1818}
1819
1820impl From<WeekdayOfMonthError> for DateError {
1821 fn from(err: WeekdayOfMonthError) -> DateError {
1822 DateError::WeekdayOfMonth(err)
1823 }
1824}
1825
1826#[derive(Clone, Debug, Eq, PartialEq)]
1827#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1828enum JulianNoLeapError {
1829 Parse(NumberError),
1830 Range,
1831}
1832
1833impl core::fmt::Display for JulianNoLeapError {
1834 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1835 use self::JulianNoLeapError::*;
1836 match *self {
1837 Parse(ref err) => {
1838 f.write_str("invalid one-based Julian day digits: ")?;
1839 core::fmt::Display::fmt(err, f)
1840 }
1841 Range => f.write_str(
1842 "parsed one-based Julian day, but it's not in supported \
1843 range of `1..=365`",
1844 ),
1845 }
1846 }
1847}
1848
1849#[derive(Clone, Debug, Eq, PartialEq)]
1850#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1851enum JulianLeapError {
1852 Parse(NumberError),
1853 Range,
1854}
1855
1856impl core::fmt::Display for JulianLeapError {
1857 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1858 use self::JulianLeapError::*;
1859 match *self {
1860 Parse(ref err) => {
1861 f.write_str("invalid zero-based Julian day digits: ")?;
1862 core::fmt::Display::fmt(err, f)
1863 }
1864 Range => f.write_str(
1865 "parsed zero-based Julian day, but it's not in supported \
1866 range of `0..=365`",
1867 ),
1868 }
1869 }
1870}
1871
1872#[derive(Clone, Debug, Eq, PartialEq)]
1873#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1874enum AbbreviationError {
1875 Quoted(QuotedAbbreviationError),
1876 Unquoted(UnquotedAbbreviationError),
1877}
1878
1879impl core::fmt::Display for AbbreviationError {
1880 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1881 use self::AbbreviationError::*;
1882 match *self {
1883 Quoted(ref err) => core::fmt::Display::fmt(err, f),
1884 Unquoted(ref err) => core::fmt::Display::fmt(err, f),
1885 }
1886 }
1887}
1888
1889#[derive(Clone, Debug, Eq, PartialEq)]
1890#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1891enum UnquotedAbbreviationError {
1892 InvalidUtf8,
1893 TooLong,
1894 TooShort,
1895}
1896
1897impl core::fmt::Display for UnquotedAbbreviationError {
1898 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1899 use self::UnquotedAbbreviationError::*;
1900 match *self {
1901 InvalidUtf8 => f.write_str(
1902 "unquoted time zone abbreviation must be valid UTF-8",
1903 ),
1904 TooLong => write!(
1905 f,
1906 "expected unquoted time zone abbreviation with at most \
1907 {} bytes, but found an abbreviation that is longer",
1908 REASONABLE_ABBREVIATION_MAX,
1909 ),
1910 TooShort => f.write_str(
1911 "expected unquoted time zone abbreviation to have length of \
1912 3 or more bytes, but an abbreviation that is shorter",
1913 ),
1914 }
1915 }
1916}
1917
1918#[derive(Clone, Debug, Eq, PartialEq)]
1919#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1920enum QuotedAbbreviationError {
1921 InvalidUtf8,
1922 TooLong,
1923 TooShort,
1924 UnexpectedEnd,
1925 UnexpectedEndAfterOpening,
1926 UnexpectedLastByte,
1927}
1928
1929impl core::fmt::Display for QuotedAbbreviationError {
1930 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1931 use self::QuotedAbbreviationError::*;
1932 match *self {
1933 InvalidUtf8 => f.write_str(
1934 "quoted time zone abbreviation must be valid UTF-8",
1935 ),
1936 TooLong => write!(
1937 f,
1938 "expected quoted time zone abbreviation with at most \
1939 {} bytes, but found an abbreviation that is longer",
1940 REASONABLE_ABBREVIATION_MAX,
1941 ),
1942 TooShort => f.write_str(
1943 "expected quoted time zone abbreviation to have length of \
1944 3 or more bytes, but an abbreviation that is shorter",
1945 ),
1946 UnexpectedEnd => f.write_str(
1947 "found non-empty quoted time zone abbreviation, but \
1948 found end of input before an end-of-quoted abbreviation \
1949 `>` character",
1950 ),
1951 UnexpectedEndAfterOpening => f.write_str(
1952 "found opening `<` quote for time zone abbreviation in \
1953 POSIX time zone transition rule, and expected a name \
1954 following it, but found the end of input instead",
1955 ),
1956 UnexpectedLastByte => f.write_str(
1957 "found non-empty quoted time zone abbreviation, but \
1958 found did not find end-of-quoted abbreviation `>` \
1959 character",
1960 ),
1961 }
1962 }
1963}
1964
1965#[derive(Clone, Debug, Eq, PartialEq)]
1966#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1967enum WeekdayOfMonthError {
1968 ExpectedDayOfWeekAfterWeek,
1969 ExpectedDotAfterMonth,
1970 ExpectedDotAfterWeek,
1971 ExpectedWeekAfterMonth,
1972 Month(MonthError),
1973 WeekOfMonth(WeekOfMonthError),
1974 Weekday(WeekdayError),
1975}
1976
1977impl core::fmt::Display for WeekdayOfMonthError {
1978 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1979 use self::WeekdayOfMonthError::*;
1980 match *self {
1981 ExpectedDayOfWeekAfterWeek => f.write_str(
1982 "expected day-of-week after week in POSIX time zone rule",
1983 ),
1984 ExpectedDotAfterMonth => {
1985 f.write_str("expected `.` after month in POSIX time zone rule")
1986 }
1987 ExpectedWeekAfterMonth => f.write_str(
1988 "expected week after month in POSIX time zone rule",
1989 ),
1990 ExpectedDotAfterWeek => {
1991 f.write_str("expected `.` after week in POSIX time zone rule")
1992 }
1993 Month(ref err) => core::fmt::Display::fmt(err, f),
1994 WeekOfMonth(ref err) => core::fmt::Display::fmt(err, f),
1995 Weekday(ref err) => core::fmt::Display::fmt(err, f),
1996 }
1997 }
1998}
1999
2000impl From<MonthError> for WeekdayOfMonthError {
2001 fn from(err: MonthError) -> WeekdayOfMonthError {
2002 WeekdayOfMonthError::Month(err)
2003 }
2004}
2005
2006impl From<WeekOfMonthError> for WeekdayOfMonthError {
2007 fn from(err: WeekOfMonthError) -> WeekdayOfMonthError {
2008 WeekdayOfMonthError::WeekOfMonth(err)
2009 }
2010}
2011
2012impl From<WeekdayError> for WeekdayOfMonthError {
2013 fn from(err: WeekdayError) -> WeekdayOfMonthError {
2014 WeekdayOfMonthError::Weekday(err)
2015 }
2016}
2017
2018#[derive(Clone, Debug, Eq, PartialEq)]
2019#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2020enum TimeError {
2021 HourIana(HourIanaError),
2022 HourPosix(HourPosixError),
2023 IncompleteMinutes,
2024 IncompleteSeconds,
2025 Minute(MinuteError),
2026 OptionalSign(OptionalSignError),
2027 Second(SecondError),
2028}
2029
2030impl core::fmt::Display for TimeError {
2031 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2032 use self::TimeError::*;
2033 match *self {
2034 HourIana(ref err) => core::fmt::Display::fmt(err, f),
2035 HourPosix(ref err) => core::fmt::Display::fmt(err, f),
2036 IncompleteMinutes => f.write_str(
2037 "incomplete time zone transition time in \
2038 POSIX time zone string (missing minutes)",
2039 ),
2040 IncompleteSeconds => f.write_str(
2041 "incomplete time zone transition time in \
2042 POSIX time zone string (missing seconds)",
2043 ),
2044 Minute(ref err) => core::fmt::Display::fmt(err, f),
2045 Second(ref err) => core::fmt::Display::fmt(err, f),
2046 OptionalSign(ref err) => {
2047 f.write_str(
2048 "failed to parse sign for time zone transition time",
2049 )?;
2050 core::fmt::Display::fmt(err, f)
2051 }
2052 }
2053 }
2054}
2055
2056impl From<HourIanaError> for TimeError {
2057 fn from(err: HourIanaError) -> TimeError {
2058 TimeError::HourIana(err)
2059 }
2060}
2061
2062impl From<HourPosixError> for TimeError {
2063 fn from(err: HourPosixError) -> TimeError {
2064 TimeError::HourPosix(err)
2065 }
2066}
2067
2068impl From<MinuteError> for TimeError {
2069 fn from(err: MinuteError) -> TimeError {
2070 TimeError::Minute(err)
2071 }
2072}
2073
2074impl From<OptionalSignError> for TimeError {
2075 fn from(err: OptionalSignError) -> TimeError {
2076 TimeError::OptionalSign(err)
2077 }
2078}
2079
2080impl From<SecondError> for TimeError {
2081 fn from(err: SecondError) -> TimeError {
2082 TimeError::Second(err)
2083 }
2084}
2085
2086#[derive(Clone, Debug, Eq, PartialEq)]
2087#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2088enum MonthError {
2089 Parse(NumberError),
2090 Range,
2091}
2092
2093impl core::fmt::Display for MonthError {
2094 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2095 use self::MonthError::*;
2096 match *self {
2097 Parse(ref err) => {
2098 f.write_str("invalid month digits: ")?;
2099 core::fmt::Display::fmt(err, f)
2100 }
2101 Range => f.write_str(
2102 "parsed month, but it's not in supported \
2103 range of `1..=12`",
2104 ),
2105 }
2106 }
2107}
2108
2109#[derive(Clone, Debug, Eq, PartialEq)]
2110#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2111enum WeekOfMonthError {
2112 Parse(NumberError),
2113 Range,
2114}
2115
2116impl core::fmt::Display for WeekOfMonthError {
2117 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2118 use self::WeekOfMonthError::*;
2119 match *self {
2120 Parse(ref err) => {
2121 f.write_str("invalid week-of-month digits: ")?;
2122 core::fmt::Display::fmt(err, f)
2123 }
2124 Range => f.write_str(
2125 "parsed week-of-month, but it's not in supported \
2126 range of `1..=5`",
2127 ),
2128 }
2129 }
2130}
2131
2132#[derive(Clone, Debug, Eq, PartialEq)]
2133#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2134enum WeekdayError {
2135 Parse(NumberError),
2136 Range,
2137}
2138
2139impl core::fmt::Display for WeekdayError {
2140 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2141 use self::WeekdayError::*;
2142 match *self {
2143 Parse(ref err) => {
2144 f.write_str("invalid weekday digits: ")?;
2145 core::fmt::Display::fmt(err, f)
2146 }
2147 Range => f.write_str(
2148 "parsed weekday, but it's not in supported \
2149 range of `0..=6` (with `0` corresponding to Sunday)",
2150 ),
2151 }
2152 }
2153}
2154
2155#[derive(Clone, Debug, Eq, PartialEq)]
2156#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2157enum HourIanaError {
2158 Parse(NumberError),
2159 Range,
2160}
2161
2162impl core::fmt::Display for HourIanaError {
2163 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2164 use self::HourIanaError::*;
2165 match *self {
2166 Parse(ref err) => {
2167 f.write_str("invalid hour digits: ")?;
2168 core::fmt::Display::fmt(err, f)
2169 }
2170 Range => f.write_str(
2171 "parsed hours, but it's not in supported \
2172 range of `-167..=167`",
2173 ),
2174 }
2175 }
2176}
2177
2178#[derive(Clone, Debug, Eq, PartialEq)]
2179#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2180enum HourPosixError {
2181 Parse(NumberError),
2182 Range,
2183}
2184
2185impl core::fmt::Display for HourPosixError {
2186 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2187 use self::HourPosixError::*;
2188 match *self {
2189 Parse(ref err) => {
2190 f.write_str("invalid hour digits: ")?;
2191 core::fmt::Display::fmt(err, f)
2192 }
2193 Range => f.write_str(
2194 "parsed hours, but it's not in supported \
2195 range of `0..=24`",
2196 ),
2197 }
2198 }
2199}
2200
2201#[derive(Clone, Debug, Eq, PartialEq)]
2202#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2203enum MinuteError {
2204 Parse(NumberError),
2205 Range,
2206}
2207
2208impl core::fmt::Display for MinuteError {
2209 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2210 use self::MinuteError::*;
2211 match *self {
2212 Parse(ref err) => {
2213 f.write_str("invalid minute digits: ")?;
2214 core::fmt::Display::fmt(err, f)
2215 }
2216 Range => f.write_str(
2217 "parsed minutes, but it's not in supported \
2218 range of `0..=59`",
2219 ),
2220 }
2221 }
2222}
2223
2224#[derive(Clone, Debug, Eq, PartialEq)]
2225#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2226enum SecondError {
2227 Parse(NumberError),
2228 Range,
2229}
2230
2231impl core::fmt::Display for SecondError {
2232 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2233 use self::SecondError::*;
2234 match *self {
2235 Parse(ref err) => {
2236 f.write_str("invalid second digits: ")?;
2237 core::fmt::Display::fmt(err, f)
2238 }
2239 Range => f.write_str(
2240 "parsed seconds, but it's not in supported \
2241 range of `0..=59`",
2242 ),
2243 }
2244 }
2245}
2246
2247#[derive(Clone, Debug, Eq, PartialEq)]
2248#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2249enum NumberError {
2250 Empty,
2251 ExpectedLength,
2252 InvalidDigit,
2253 TooBig,
2254}
2255
2256impl core::fmt::Display for NumberError {
2257 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2258 use self::NumberError::*;
2259 match *self {
2260 Empty => f.write_str("invalid number, no digits found"),
2261 ExpectedLength => f.write_str(
2262 "expected a fixed number of digits, \
2263 but found incorrect number",
2264 ),
2265 InvalidDigit => f.write_str("expected digit in range `0..=9`"),
2266 TooBig => f.write_str(
2267 "parsed number too big to fit into a 32-bit signed integer",
2268 ),
2269 }
2270 }
2271}
2272
2273#[derive(Clone, Debug, Eq, PartialEq)]
2274#[cfg_attr(feature = "defmt", derive(defmt::Format))]
2275enum OptionalSignError {
2276 ExpectedDigitAfterMinus,
2277 ExpectedDigitAfterPlus,
2278}
2279
2280impl core::fmt::Display for OptionalSignError {
2281 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2282 use self::OptionalSignError::*;
2283 match *self {
2284 ExpectedDigitAfterMinus => f.write_str(
2285 "expected digit after `-` sign, \
2286 but got end of input",
2287 ),
2288 ExpectedDigitAfterPlus => f.write_str(
2289 "expected digit after `+` sign, \
2290 but got end of input",
2291 ),
2292 }
2293 }
2294}
2295
2296#[cfg(test)]
2297mod tests {
2298 use crate::civil::date;
2299
2300 use super::*;
2301
2302 fn posix_time_zone(input: impl AsRef<[u8]>) -> TimeZone {
2303 let input = input.as_ref();
2304 let tz = TimeZone::parse(input).unwrap();
2305 tz
2306 }
2307
2308 fn parser(s: &str) -> Parser<'_> {
2309 Parser::new(s)
2310 }
2311
2312 fn astr(s: &'static str) -> Abbreviation {
2313 Abbreviation::array(s)
2314 }
2315
2316 fn off(seconds: i32) -> Offset {
2317 Offset::from_seconds(seconds).unwrap()
2318 }
2319
2320 #[test]
2321 fn parse() {
2322 let p = parser("NZST-12NZDT,J60,J300");
2323 assert_eq!(
2324 p.parse().unwrap(),
2325 TimeZone {
2326 std_abbrev: astr("NZST"),
2327 std_offset: off(12 * 60 * 60),
2328 dst: Some(Dst {
2329 abbrev: astr("NZDT"),
2330 offset: off(13 * 60 * 60),
2331 rule: Rule {
2332 start: DayTime {
2333 date: Day::JulianOne(60),
2334 time: TransitionCivilTime { second: 2 * 60 * 60 },
2335 },
2336 end: DayTime {
2337 date: Day::JulianOne(300),
2338 time: TransitionCivilTime { second: 2 * 60 * 60 },
2339 },
2340 },
2341 }),
2342 },
2343 );
2344
2345 let p = Parser::new("NZST-12NZDT,J60,J300WAT");
2346 assert!(p.parse().is_err());
2347 }
2348
2349 #[test]
2350 fn parse_posix_time_zone() {
2351 let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3");
2352 assert_eq!(
2353 p.parse_posix_time_zone().unwrap(),
2354 TimeZone {
2355 std_abbrev: astr("NZST"),
2356 std_offset: off(12 * 60 * 60),
2357 dst: Some(Dst {
2358 abbrev: astr("NZDT"),
2359 offset: off(13 * 60 * 60),
2360 rule: Rule {
2361 start: DayTime {
2362 date: Day::WeekdayOfMonth {
2363 month: 9,
2364 week: 5,
2365 weekday: Weekday::Sunday,
2366 },
2367 time: TransitionCivilTime { second: 2 * 60 * 60 },
2368 },
2369 end: DayTime {
2370 date: Day::WeekdayOfMonth {
2371 month: 4,
2372 week: 1,
2373 weekday: Weekday::Sunday,
2374 },
2375 time: TransitionCivilTime { second: 3 * 60 * 60 },
2376 },
2377 },
2378 }),
2379 },
2380 );
2381
2382 let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3WAT");
2383 assert_eq!(
2384 p.parse_posix_time_zone().unwrap(),
2385 TimeZone {
2386 std_abbrev: astr("NZST"),
2387 std_offset: off(12 * 60 * 60),
2388 dst: Some(Dst {
2389 abbrev: astr("NZDT"),
2390 offset: off(13 * 60 * 60),
2391 rule: Rule {
2392 start: DayTime {
2393 date: Day::WeekdayOfMonth {
2394 month: 9,
2395 week: 5,
2396 weekday: Weekday::Sunday,
2397 },
2398 time: TransitionCivilTime { second: 2 * 60 * 60 },
2399 },
2400 end: DayTime {
2401 date: Day::WeekdayOfMonth {
2402 month: 4,
2403 week: 1,
2404 weekday: Weekday::Sunday,
2405 },
2406 time: TransitionCivilTime { second: 3 * 60 * 60 },
2407 },
2408 },
2409 }),
2410 },
2411 );
2412
2413 let p = Parser::new("NZST-12NZDT,J60,J300");
2414 assert_eq!(
2415 p.parse_posix_time_zone().unwrap(),
2416 TimeZone {
2417 std_abbrev: astr("NZST"),
2418 std_offset: off(12 * 60 * 60),
2419 dst: Some(Dst {
2420 abbrev: astr("NZDT"),
2421 offset: off(13 * 60 * 60),
2422 rule: Rule {
2423 start: DayTime {
2424 date: Day::JulianOne(60),
2425 time: TransitionCivilTime { second: 2 * 60 * 60 },
2426 },
2427 end: DayTime {
2428 date: Day::JulianOne(300),
2429 time: TransitionCivilTime { second: 2 * 60 * 60 },
2430 },
2431 },
2432 }),
2433 },
2434 );
2435
2436 let p = Parser::new("NZST-12NZDT,J60,J300WAT");
2437 assert_eq!(
2438 p.parse_posix_time_zone().unwrap(),
2439 TimeZone {
2440 std_abbrev: astr("NZST"),
2441 std_offset: off(12 * 60 * 60),
2442 dst: Some(Dst {
2443 abbrev: astr("NZDT"),
2444 offset: off(13 * 60 * 60),
2445 rule: Rule {
2446 start: DayTime {
2447 date: Day::JulianOne(60),
2448 time: TransitionCivilTime { second: 2 * 60 * 60 },
2449 },
2450 end: DayTime {
2451 date: Day::JulianOne(300),
2452 time: TransitionCivilTime { second: 2 * 60 * 60 },
2453 },
2454 },
2455 }),
2456 },
2457 );
2458 }
2459
2460 #[test]
2461 fn parse_posix_dst() {
2462 let p = Parser::new("NZDT,M9.5.0,M4.1.0/3");
2463 assert_eq!(
2464 p.parse_posix_dst(off(12 * 60 * 60)).unwrap(),
2465 Dst {
2466 abbrev: astr("NZDT"),
2467 offset: off(13 * 60 * 60),
2468 rule: Rule {
2469 start: DayTime {
2470 date: Day::WeekdayOfMonth {
2471 month: 9,
2472 week: 5,
2473 weekday: Weekday::Sunday,
2474 },
2475 time: TransitionCivilTime { second: 2 * 60 * 60 },
2476 },
2477 end: DayTime {
2478 date: Day::WeekdayOfMonth {
2479 month: 4,
2480 week: 1,
2481 weekday: Weekday::Sunday,
2482 },
2483 time: TransitionCivilTime { second: 3 * 60 * 60 },
2484 },
2485 },
2486 },
2487 );
2488
2489 let p = Parser::new("NZDT,J60,J300");
2490 assert_eq!(
2491 p.parse_posix_dst(off(12 * 60 * 60)).unwrap(),
2492 Dst {
2493 abbrev: astr("NZDT"),
2494 offset: off(13 * 60 * 60),
2495 rule: Rule {
2496 start: DayTime {
2497 date: Day::JulianOne(60),
2498 time: TransitionCivilTime { second: 2 * 60 * 60 },
2499 },
2500 end: DayTime {
2501 date: Day::JulianOne(300),
2502 time: TransitionCivilTime { second: 2 * 60 * 60 },
2503 },
2504 },
2505 },
2506 );
2507
2508 let p = Parser::new("NZDT-7,J60,J300");
2509 assert_eq!(
2510 p.parse_posix_dst(off(12 * 60 * 60)).unwrap(),
2511 Dst {
2512 abbrev: astr("NZDT"),
2513 offset: off(7 * 60 * 60),
2514 rule: Rule {
2515 start: DayTime {
2516 date: Day::JulianOne(60),
2517 time: TransitionCivilTime { second: 2 * 60 * 60 },
2518 },
2519 end: DayTime {
2520 date: Day::JulianOne(300),
2521 time: TransitionCivilTime { second: 2 * 60 * 60 },
2522 },
2523 },
2524 },
2525 );
2526
2527 let p = Parser::new("NZDT+7,J60,J300");
2528 assert_eq!(
2529 p.parse_posix_dst(off(12 * 60 * 60)).unwrap(),
2530 Dst {
2531 abbrev: astr("NZDT"),
2532 offset: off(-7 * 60 * 60),
2533 rule: Rule {
2534 start: DayTime {
2535 date: Day::JulianOne(60),
2536 time: TransitionCivilTime { second: 2 * 60 * 60 },
2537 },
2538 end: DayTime {
2539 date: Day::JulianOne(300),
2540 time: TransitionCivilTime { second: 2 * 60 * 60 },
2541 },
2542 },
2543 },
2544 );
2545
2546 let p = Parser::new("NZDT7,J60,J300");
2547 assert_eq!(
2548 p.parse_posix_dst(off(12 * 60 * 60)).unwrap(),
2549 Dst {
2550 abbrev: astr("NZDT"),
2551 offset: off(-7 * 60 * 60),
2552 rule: Rule {
2553 start: DayTime {
2554 date: Day::JulianOne(60),
2555 time: TransitionCivilTime { second: 2 * 60 * 60 },
2556 },
2557 end: DayTime {
2558 date: Day::JulianOne(300),
2559 time: TransitionCivilTime { second: 2 * 60 * 60 },
2560 },
2561 },
2562 },
2563 );
2564
2565 let p = Parser::new("NZDT7,");
2566 assert!(p.parse_posix_dst(off(12 * 60 * 60)).is_err());
2567
2568 let p = Parser::new("NZDT7!");
2569 assert!(p.parse_posix_dst(off(12 * 60 * 60)).is_err());
2570 }
2571
2572 #[test]
2573 fn parse_abbreviation() {
2574 let p = Parser::new("ABC");
2575 assert_eq!(p.parse_abbreviation().unwrap(), "ABC");
2576
2577 let p = Parser::new("<ABC>");
2578 assert_eq!(p.parse_abbreviation().unwrap(), "ABC");
2579
2580 let p = Parser::new("<+09>");
2581 assert_eq!(p.parse_abbreviation().unwrap(), "+09");
2582
2583 let p = Parser::new("+09");
2584 assert!(p.parse_abbreviation().is_err());
2585 }
2586
2587 #[test]
2588 fn parse_unquoted_abbreviation() {
2589 let p = Parser::new("ABC");
2590 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC");
2591
2592 let p = Parser::new("ABCXYZ");
2593 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABCXYZ");
2594
2595 let p = Parser::new("ABC123");
2596 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC");
2597
2598 let tz = "a".repeat(6);
2599 let p = Parser::new(&tz);
2600 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), &*tz);
2601
2602 let p = Parser::new("a");
2603 assert!(p.parse_unquoted_abbreviation().is_err());
2604
2605 let p = Parser::new("ab");
2606 assert!(p.parse_unquoted_abbreviation().is_err());
2607
2608 let p = Parser::new("ab1");
2609 assert!(p.parse_unquoted_abbreviation().is_err());
2610
2611 let tz = "a".repeat(31);
2612 #[cfg(feature = "alloc")]
2613 {
2614 let p = Parser::new(&tz);
2615 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), &*tz);
2616 }
2617 #[cfg(not(feature = "alloc"))]
2618 {
2619 let p = Parser::new(&tz);
2620 assert!(p.parse_unquoted_abbreviation().is_err());
2621 }
2622
2623 let p = Parser::new(b"ab\xFFcd");
2624 assert!(p.parse_unquoted_abbreviation().is_err());
2625 }
2626
2627 #[test]
2628 fn parse_quoted_abbreviation() {
2629 let p = Parser::new("ABC>");
2634 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC");
2635
2636 let p = Parser::new("ABCXYZ>");
2637 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABCXYZ");
2638
2639 let p = Parser::new("ABC>123");
2640 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC");
2641
2642 let p = Parser::new("ABC123>");
2643 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC123");
2644
2645 let p = Parser::new("ab1>");
2646 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ab1");
2647
2648 let p = Parser::new("+09>");
2649 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "+09");
2650
2651 let p = Parser::new("-09>");
2652 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "-09");
2653
2654 let tz = alloc::format!("{}>", "a".repeat(6));
2655 let p = Parser::new(&tz);
2656 assert_eq!(
2657 p.parse_quoted_abbreviation().unwrap(),
2658 tz.trim_end_matches(">")
2659 );
2660
2661 let p = Parser::new("a>");
2662 assert!(p.parse_quoted_abbreviation().is_err());
2663
2664 let p = Parser::new("ab>");
2665 assert!(p.parse_quoted_abbreviation().is_err());
2666
2667 let tz = alloc::format!("{}>", "a".repeat(31));
2668 #[cfg(feature = "alloc")]
2669 {
2670 let p = Parser::new(&tz);
2671 assert_eq!(
2672 p.parse_quoted_abbreviation().unwrap(),
2673 tz.trim_end_matches(">")
2674 );
2675 }
2676 #[cfg(not(feature = "alloc"))]
2677 {
2678 let p = Parser::new(&tz);
2679 assert!(p.parse_quoted_abbreviation().is_err());
2680 }
2681
2682 let p = Parser::new(b"ab\xFFcd>");
2683 assert!(p.parse_quoted_abbreviation().is_err());
2684
2685 let p = Parser::new("ABC");
2686 assert!(p.parse_quoted_abbreviation().is_err());
2687
2688 let p = Parser::new("ABC!>");
2689 assert!(p.parse_quoted_abbreviation().is_err());
2690 }
2691
2692 #[test]
2693 fn parse_posix_offset() {
2694 let p = Parser::new("5");
2695 assert_eq!(p.parse_posix_offset().unwrap().seconds(), -5 * 60 * 60);
2696
2697 let p = Parser::new("+5");
2698 assert_eq!(p.parse_posix_offset().unwrap().seconds(), -5 * 60 * 60);
2699
2700 let p = Parser::new("-5");
2701 assert_eq!(p.parse_posix_offset().unwrap().seconds(), 5 * 60 * 60);
2702
2703 let p = Parser::new("-12:34:56");
2704 assert_eq!(
2705 p.parse_posix_offset().unwrap().seconds(),
2706 12 * 60 * 60 + 34 * 60 + 56,
2707 );
2708
2709 let p = Parser::new("a");
2710 assert!(p.parse_posix_offset().is_err());
2711
2712 let p = Parser::new("-");
2713 assert!(p.parse_posix_offset().is_err());
2714
2715 let p = Parser::new("+");
2716 assert!(p.parse_posix_offset().is_err());
2717
2718 let p = Parser::new("-a");
2719 assert!(p.parse_posix_offset().is_err());
2720
2721 let p = Parser::new("+a");
2722 assert!(p.parse_posix_offset().is_err());
2723
2724 let p = Parser::new("-25");
2725 assert!(p.parse_posix_offset().is_err());
2726
2727 let p = Parser::new("+25");
2728 assert!(p.parse_posix_offset().is_err());
2729
2730 let p = Parser { ianav3plus: true, ..Parser::new("25") };
2738 assert!(p.parse_posix_offset().is_err());
2739 let p = Parser { ianav3plus: true, ..Parser::new("+25") };
2740 assert!(p.parse_posix_offset().is_err());
2741 let p = Parser { ianav3plus: true, ..Parser::new("-25") };
2742 assert!(p.parse_posix_offset().is_err());
2743 }
2744
2745 #[test]
2746 fn parse_rule() {
2747 let p = Parser::new("M9.5.0,M4.1.0/3");
2748 assert_eq!(
2749 p.parse_rule().unwrap(),
2750 Rule {
2751 start: DayTime {
2752 date: Day::WeekdayOfMonth {
2753 month: 9,
2754 week: 5,
2755 weekday: Weekday::Sunday,
2756 },
2757 time: TransitionCivilTime { second: 2 * 60 * 60 },
2758 },
2759 end: DayTime {
2760 date: Day::WeekdayOfMonth {
2761 month: 4,
2762 week: 1,
2763 weekday: Weekday::Sunday,
2764 },
2765 time: TransitionCivilTime { second: 3 * 60 * 60 },
2766 },
2767 },
2768 );
2769
2770 let p = Parser::new("M9.5.0");
2771 assert!(p.parse_rule().is_err());
2772
2773 let p = Parser::new(",M9.5.0,M4.1.0/3");
2774 assert!(p.parse_rule().is_err());
2775
2776 let p = Parser::new("M9.5.0/");
2777 assert!(p.parse_rule().is_err());
2778
2779 let p = Parser::new("M9.5.0,M4.1.0/");
2780 assert!(p.parse_rule().is_err());
2781 }
2782
2783 #[test]
2784 fn parse_posix_datetime() {
2785 let p = Parser::new("J1");
2786 assert_eq!(
2787 p.parse_posix_datetime().unwrap(),
2788 DayTime {
2789 date: Day::JulianOne(1),
2790 time: TransitionCivilTime { second: 2 * 60 * 60 }
2791 },
2792 );
2793
2794 let p = Parser::new("J1/3");
2795 assert_eq!(
2796 p.parse_posix_datetime().unwrap(),
2797 DayTime {
2798 date: Day::JulianOne(1),
2799 time: TransitionCivilTime { second: 3 * 60 * 60 }
2800 },
2801 );
2802
2803 let p = Parser::new("M4.1.0/3");
2804 assert_eq!(
2805 p.parse_posix_datetime().unwrap(),
2806 DayTime {
2807 date: Day::WeekdayOfMonth {
2808 month: 4,
2809 week: 1,
2810 weekday: Weekday::Sunday
2811 },
2812 time: TransitionCivilTime { second: 3 * 60 * 60 },
2813 },
2814 );
2815
2816 let p = Parser::new("1/3:45:05");
2817 assert_eq!(
2818 p.parse_posix_datetime().unwrap(),
2819 DayTime {
2820 date: Day::JulianZero(1),
2821 time: TransitionCivilTime {
2822 second: 3 * 60 * 60 + 45 * 60 + 5
2823 },
2824 },
2825 );
2826
2827 let p = Parser::new("a");
2828 assert!(p.parse_posix_datetime().is_err());
2829
2830 let p = Parser::new("J1/");
2831 assert!(p.parse_posix_datetime().is_err());
2832
2833 let p = Parser::new("1/");
2834 assert!(p.parse_posix_datetime().is_err());
2835
2836 let p = Parser::new("M4.1.0/");
2837 assert!(p.parse_posix_datetime().is_err());
2838 }
2839
2840 #[test]
2841 fn parse_posix_date() {
2842 let p = Parser::new("J1");
2843 assert_eq!(p.parse_posix_date().unwrap(), Day::JulianOne(1));
2844 let p = Parser::new("J365");
2845 assert_eq!(p.parse_posix_date().unwrap(), Day::JulianOne(365));
2846
2847 let p = Parser::new("0");
2848 assert_eq!(p.parse_posix_date().unwrap(), Day::JulianZero(0));
2849 let p = Parser::new("1");
2850 assert_eq!(p.parse_posix_date().unwrap(), Day::JulianZero(1));
2851 let p = Parser::new("365");
2852 assert_eq!(p.parse_posix_date().unwrap(), Day::JulianZero(365));
2853
2854 let p = Parser::new("M9.5.0");
2855 assert_eq!(
2856 p.parse_posix_date().unwrap(),
2857 Day::WeekdayOfMonth {
2858 month: 9,
2859 week: 5,
2860 weekday: Weekday::Sunday
2861 },
2862 );
2863 let p = Parser::new("M9.5.6");
2864 assert_eq!(
2865 p.parse_posix_date().unwrap(),
2866 Day::WeekdayOfMonth {
2867 month: 9,
2868 week: 5,
2869 weekday: Weekday::Saturday
2870 },
2871 );
2872 let p = Parser::new("M09.5.6");
2873 assert_eq!(
2874 p.parse_posix_date().unwrap(),
2875 Day::WeekdayOfMonth {
2876 month: 9,
2877 week: 5,
2878 weekday: Weekday::Saturday
2879 },
2880 );
2881 let p = Parser::new("M12.1.1");
2882 assert_eq!(
2883 p.parse_posix_date().unwrap(),
2884 Day::WeekdayOfMonth {
2885 month: 12,
2886 week: 1,
2887 weekday: Weekday::Monday
2888 },
2889 );
2890
2891 let p = Parser::new("a");
2892 assert!(p.parse_posix_date().is_err());
2893
2894 let p = Parser::new("j");
2895 assert!(p.parse_posix_date().is_err());
2896
2897 let p = Parser::new("m");
2898 assert!(p.parse_posix_date().is_err());
2899
2900 let p = Parser::new("n");
2901 assert!(p.parse_posix_date().is_err());
2902
2903 let p = Parser::new("J366");
2904 assert!(p.parse_posix_date().is_err());
2905
2906 let p = Parser::new("366");
2907 assert!(p.parse_posix_date().is_err());
2908 }
2909
2910 #[test]
2911 fn parse_posix_julian_day_no_leap() {
2912 let p = Parser::new("1");
2913 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1);
2914
2915 let p = Parser::new("001");
2916 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1);
2917
2918 let p = Parser::new("365");
2919 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365);
2920
2921 let p = Parser::new("3655");
2922 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365);
2923
2924 let p = Parser::new("0");
2925 assert!(p.parse_posix_julian_day_no_leap().is_err());
2926
2927 let p = Parser::new("366");
2928 assert!(p.parse_posix_julian_day_no_leap().is_err());
2929 }
2930
2931 #[test]
2932 fn parse_posix_julian_day_with_leap() {
2933 let p = Parser::new("0");
2934 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 0);
2935
2936 let p = Parser::new("1");
2937 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1);
2938
2939 let p = Parser::new("001");
2940 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1);
2941
2942 let p = Parser::new("365");
2943 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365);
2944
2945 let p = Parser::new("3655");
2946 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365);
2947
2948 let p = Parser::new("366");
2949 assert!(p.parse_posix_julian_day_with_leap().is_err());
2950 }
2951
2952 #[test]
2953 fn parse_weekday_of_month() {
2954 let p = Parser::new("9.5.0");
2955 assert_eq!(
2956 p.parse_weekday_of_month().unwrap(),
2957 (9, 5, Weekday::Sunday)
2958 );
2959
2960 let p = Parser::new("9.1.6");
2961 assert_eq!(
2962 p.parse_weekday_of_month().unwrap(),
2963 (9, 1, Weekday::Saturday)
2964 );
2965
2966 let p = Parser::new("09.1.6");
2967 assert_eq!(
2968 p.parse_weekday_of_month().unwrap(),
2969 (9, 1, Weekday::Saturday)
2970 );
2971
2972 let p = Parser::new("9");
2973 assert!(p.parse_weekday_of_month().is_err());
2974
2975 let p = Parser::new("9.");
2976 assert!(p.parse_weekday_of_month().is_err());
2977
2978 let p = Parser::new("9.5");
2979 assert!(p.parse_weekday_of_month().is_err());
2980
2981 let p = Parser::new("9.5.");
2982 assert!(p.parse_weekday_of_month().is_err());
2983
2984 let p = Parser::new("0.5.0");
2985 assert!(p.parse_weekday_of_month().is_err());
2986
2987 let p = Parser::new("13.5.0");
2988 assert!(p.parse_weekday_of_month().is_err());
2989
2990 let p = Parser::new("9.0.0");
2991 assert!(p.parse_weekday_of_month().is_err());
2992
2993 let p = Parser::new("9.6.0");
2994 assert!(p.parse_weekday_of_month().is_err());
2995
2996 let p = Parser::new("9.5.7");
2997 assert!(p.parse_weekday_of_month().is_err());
2998 }
2999
3000 #[test]
3001 fn parse_posix_time() {
3002 let p = Parser::new("5");
3003 assert_eq!(p.parse_posix_time().unwrap().second, 5 * 60 * 60);
3004
3005 let p = Parser::new("22");
3006 assert_eq!(p.parse_posix_time().unwrap().second, 22 * 60 * 60);
3007
3008 let p = Parser::new("02");
3009 assert_eq!(p.parse_posix_time().unwrap().second, 2 * 60 * 60);
3010
3011 let p = Parser::new("5:45");
3012 assert_eq!(
3013 p.parse_posix_time().unwrap().second,
3014 5 * 60 * 60 + 45 * 60
3015 );
3016
3017 let p = Parser::new("5:45:12");
3018 assert_eq!(
3019 p.parse_posix_time().unwrap().second,
3020 5 * 60 * 60 + 45 * 60 + 12
3021 );
3022
3023 let p = Parser::new("5:45:129");
3024 assert_eq!(
3025 p.parse_posix_time().unwrap().second,
3026 5 * 60 * 60 + 45 * 60 + 12
3027 );
3028
3029 let p = Parser::new("5:45:12:");
3030 assert_eq!(
3031 p.parse_posix_time().unwrap().second,
3032 5 * 60 * 60 + 45 * 60 + 12
3033 );
3034
3035 let p = Parser { ianav3plus: true, ..Parser::new("+5:45:12") };
3036 assert_eq!(
3037 p.parse_posix_time().unwrap().second,
3038 5 * 60 * 60 + 45 * 60 + 12
3039 );
3040
3041 let p = Parser { ianav3plus: true, ..Parser::new("-5:45:12") };
3042 assert_eq!(
3043 p.parse_posix_time().unwrap().second,
3044 -(5 * 60 * 60 + 45 * 60 + 12)
3045 );
3046
3047 let p = Parser { ianav3plus: true, ..Parser::new("-167:45:12") };
3048 assert_eq!(
3049 p.parse_posix_time().unwrap().second,
3050 -(167 * 60 * 60 + 45 * 60 + 12),
3051 );
3052
3053 let p = Parser::new("25");
3054 assert!(p.parse_posix_time().is_err());
3055
3056 let p = Parser::new("12:2");
3057 assert!(p.parse_posix_time().is_err());
3058
3059 let p = Parser::new("12:");
3060 assert!(p.parse_posix_time().is_err());
3061
3062 let p = Parser::new("12:23:5");
3063 assert!(p.parse_posix_time().is_err());
3064
3065 let p = Parser::new("12:23:");
3066 assert!(p.parse_posix_time().is_err());
3067
3068 let p = Parser { ianav3plus: true, ..Parser::new("168") };
3069 assert!(p.parse_posix_time().is_err());
3070
3071 let p = Parser { ianav3plus: true, ..Parser::new("-168") };
3072 assert!(p.parse_posix_time().is_err());
3073
3074 let p = Parser { ianav3plus: true, ..Parser::new("+168") };
3075 assert!(p.parse_posix_time().is_err());
3076 }
3077
3078 #[test]
3079 fn parse_month() {
3080 let p = Parser::new("1");
3081 assert_eq!(p.parse_month().unwrap(), 1);
3082
3083 let p = Parser::new("01");
3088 assert_eq!(p.parse_month().unwrap(), 1);
3089
3090 let p = Parser::new("12");
3091 assert_eq!(p.parse_month().unwrap(), 12);
3092
3093 let p = Parser::new("0");
3094 assert!(p.parse_month().is_err());
3095
3096 let p = Parser::new("00");
3097 assert!(p.parse_month().is_err());
3098
3099 let p = Parser::new("001");
3100 assert!(p.parse_month().is_err());
3101
3102 let p = Parser::new("13");
3103 assert!(p.parse_month().is_err());
3104 }
3105
3106 #[test]
3107 fn parse_week() {
3108 let p = Parser::new("1");
3109 assert_eq!(p.parse_week().unwrap(), 1);
3110
3111 let p = Parser::new("5");
3112 assert_eq!(p.parse_week().unwrap(), 5);
3113
3114 let p = Parser::new("55");
3115 assert_eq!(p.parse_week().unwrap(), 5);
3116
3117 let p = Parser::new("0");
3118 assert!(p.parse_week().is_err());
3119
3120 let p = Parser::new("6");
3121 assert!(p.parse_week().is_err());
3122
3123 let p = Parser::new("00");
3124 assert!(p.parse_week().is_err());
3125
3126 let p = Parser::new("01");
3127 assert!(p.parse_week().is_err());
3128
3129 let p = Parser::new("05");
3130 assert!(p.parse_week().is_err());
3131 }
3132
3133 #[test]
3134 fn parse_weekday() {
3135 let p = Parser::new("0");
3136 assert_eq!(p.parse_weekday().unwrap(), Weekday::Sunday);
3137
3138 let p = Parser::new("1");
3139 assert_eq!(p.parse_weekday().unwrap(), Weekday::Monday);
3140
3141 let p = Parser::new("6");
3142 assert_eq!(p.parse_weekday().unwrap(), Weekday::Saturday);
3143
3144 let p = Parser::new("00");
3145 assert_eq!(p.parse_weekday().unwrap(), Weekday::Sunday);
3146
3147 let p = Parser::new("06");
3148 assert_eq!(p.parse_weekday().unwrap(), Weekday::Sunday);
3149
3150 let p = Parser::new("60");
3151 assert_eq!(p.parse_weekday().unwrap(), Weekday::Saturday);
3152
3153 let p = Parser::new("7");
3154 assert!(p.parse_weekday().is_err());
3155 }
3156
3157 #[test]
3158 fn parse_hour_posix() {
3159 let p = Parser::new("5");
3160 assert_eq!(p.parse_hour_posix().unwrap(), 5);
3161
3162 let p = Parser::new("0");
3163 assert_eq!(p.parse_hour_posix().unwrap(), 0);
3164
3165 let p = Parser::new("00");
3166 assert_eq!(p.parse_hour_posix().unwrap(), 0);
3167
3168 let p = Parser::new("24");
3169 assert_eq!(p.parse_hour_posix().unwrap(), 24);
3170
3171 let p = Parser::new("100");
3172 assert_eq!(p.parse_hour_posix().unwrap(), 10);
3173
3174 let p = Parser::new("25");
3175 assert!(p.parse_hour_posix().is_err());
3176
3177 let p = Parser::new("99");
3178 assert!(p.parse_hour_posix().is_err());
3179 }
3180
3181 #[test]
3182 fn parse_hour_ianav3plus() {
3183 let new = |input| Parser { ianav3plus: true, ..Parser::new(input) };
3184
3185 let p = new("5");
3186 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 5);
3187
3188 let p = new("0");
3189 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
3190
3191 let p = new("00");
3192 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
3193
3194 let p = new("000");
3195 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
3196
3197 let p = new("24");
3198 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 24);
3199
3200 let p = new("100");
3201 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100);
3202
3203 let p = new("1000");
3204 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100);
3205
3206 let p = new("167");
3207 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 167);
3208
3209 let p = new("168");
3210 assert!(p.parse_hour_ianav3plus().is_err());
3211
3212 let p = new("999");
3213 assert!(p.parse_hour_ianav3plus().is_err());
3214 }
3215
3216 #[test]
3217 fn parse_minute() {
3218 let p = Parser::new("00");
3219 assert_eq!(p.parse_minute().unwrap(), 0);
3220
3221 let p = Parser::new("24");
3222 assert_eq!(p.parse_minute().unwrap(), 24);
3223
3224 let p = Parser::new("59");
3225 assert_eq!(p.parse_minute().unwrap(), 59);
3226
3227 let p = Parser::new("599");
3228 assert_eq!(p.parse_minute().unwrap(), 59);
3229
3230 let p = Parser::new("0");
3231 assert!(p.parse_minute().is_err());
3232
3233 let p = Parser::new("1");
3234 assert!(p.parse_minute().is_err());
3235
3236 let p = Parser::new("9");
3237 assert!(p.parse_minute().is_err());
3238
3239 let p = Parser::new("60");
3240 assert!(p.parse_minute().is_err());
3241 }
3242
3243 #[test]
3244 fn parse_second() {
3245 let p = Parser::new("00");
3246 assert_eq!(p.parse_second().unwrap(), 0);
3247
3248 let p = Parser::new("24");
3249 assert_eq!(p.parse_second().unwrap(), 24);
3250
3251 let p = Parser::new("59");
3252 assert_eq!(p.parse_second().unwrap(), 59);
3253
3254 let p = Parser::new("599");
3255 assert_eq!(p.parse_second().unwrap(), 59);
3256
3257 let p = Parser::new("0");
3258 assert!(p.parse_second().is_err());
3259
3260 let p = Parser::new("1");
3261 assert!(p.parse_second().is_err());
3262
3263 let p = Parser::new("9");
3264 assert!(p.parse_second().is_err());
3265
3266 let p = Parser::new("60");
3267 assert!(p.parse_second().is_err());
3268 }
3269
3270 #[test]
3271 fn parse_number_with_exactly_n_digits() {
3272 let p = Parser::new("1");
3273 assert_eq!(p.parse_number_with_exactly_n_digits(1).unwrap(), 1);
3274
3275 let p = Parser::new("12");
3276 assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12);
3277
3278 let p = Parser::new("123");
3279 assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12);
3280
3281 let p = Parser::new("");
3282 assert!(p.parse_number_with_exactly_n_digits(1).is_err());
3283
3284 let p = Parser::new("1");
3285 assert!(p.parse_number_with_exactly_n_digits(2).is_err());
3286
3287 let p = Parser::new("12");
3288 assert!(p.parse_number_with_exactly_n_digits(3).is_err());
3289 }
3290
3291 #[test]
3292 fn parse_number_with_upto_n_digits() {
3293 let p = Parser::new("1");
3294 assert_eq!(p.parse_number_with_upto_n_digits(1).unwrap(), 1);
3295
3296 let p = Parser::new("1");
3297 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 1);
3298
3299 let p = Parser::new("12");
3300 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12);
3301
3302 let p = Parser::new("12");
3303 assert_eq!(p.parse_number_with_upto_n_digits(3).unwrap(), 12);
3304
3305 let p = Parser::new("123");
3306 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12);
3307
3308 let p = Parser::new("");
3309 assert!(p.parse_number_with_upto_n_digits(1).is_err());
3310
3311 let p = Parser::new("a");
3312 assert!(p.parse_number_with_upto_n_digits(1).is_err());
3313 }
3314
3315 #[test]
3316 fn to_dst_civil_datetime_utc_range() {
3317 let tz = posix_time_zone("WART4WARST,J1/-3,J365/20");
3318 let dst_info = DstInfo {
3319 dst: tz.dst.as_ref().unwrap(),
3323 start: date(2024, 1, 1).at(1, 0, 0, 0),
3324 end: date(2024, 12, 31).at(23, 0, 0, 0),
3325 };
3326 assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
3327
3328 let tz = posix_time_zone("WART4WARST,J1/-4,J365/21");
3329 let dst_info = DstInfo {
3330 dst: tz.dst.as_ref().unwrap(),
3331 start: date(2024, 1, 1).at(0, 0, 0, 0),
3332 end: date(2024, 12, 31).at(23, 59, 59, 999_999_999),
3333 };
3334 assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
3335
3336 let tz = posix_time_zone("EST5EDT,M3.2.0,M11.1.0");
3337 let dst_info = DstInfo {
3338 dst: tz.dst.as_ref().unwrap(),
3339 start: date(2024, 3, 10).at(7, 0, 0, 0),
3340 end: date(2024, 11, 3).at(6, 0, 0, 0),
3341 };
3342 assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
3343 }
3344
3345 #[test]
3347 fn regression_permanent_dst() {
3348 let tz = posix_time_zone("XXX-2<+01>-1,0/0,J365/23");
3349 let dst_info = DstInfo {
3350 dst: tz.dst.as_ref().unwrap(),
3351 start: date(2087, 1, 1).at(0, 0, 0, 0),
3352 end: date(2087, 12, 31).at(23, 59, 59, 999_999_999),
3353 };
3354 assert_eq!(tz.dst_info_utc(2087), Some(dst_info));
3355 }
3356
3357 #[test]
3358 fn reasonable() {
3359 assert!(TimeZone::parse(b"EST5").is_ok());
3360 assert!(TimeZone::parse(b"EST5EDT").is_err());
3361 assert!(TimeZone::parse(b"EST5EDT,J1,J365").is_ok());
3362
3363 let tz = posix_time_zone("EST24EDT,J1,J365");
3364 assert_eq!(
3365 tz,
3366 TimeZone {
3367 std_abbrev: astr("EST"),
3368 std_offset: off(-24 * 60 * 60),
3369 dst: Some(Dst {
3370 abbrev: astr("EDT"),
3371 offset: off(-23 * 60 * 60),
3372 rule: Rule {
3373 start: DayTime {
3374 date: Day::JulianOne(1),
3375 time: TransitionCivilTime::DEFAULT,
3376 },
3377 end: DayTime {
3378 date: Day::JulianOne(365),
3379 time: TransitionCivilTime::DEFAULT,
3380 },
3381 },
3382 }),
3383 },
3384 );
3385
3386 let tz = posix_time_zone("EST-24EDT,J1,J365");
3387 assert_eq!(
3388 tz,
3389 TimeZone {
3390 std_abbrev: astr("EST"),
3391 std_offset: off(24 * 60 * 60),
3392 dst: Some(Dst {
3393 abbrev: astr("EDT"),
3394 offset: off(25 * 60 * 60),
3395 rule: Rule {
3396 start: DayTime {
3397 date: Day::JulianOne(1),
3398 time: TransitionCivilTime::DEFAULT,
3399 },
3400 end: DayTime {
3401 date: Day::JulianOne(365),
3402 time: TransitionCivilTime::DEFAULT,
3403 },
3404 },
3405 }),
3406 },
3407 );
3408 }
3409
3410 #[test]
3411 fn posix_date_time_spec_to_datetime() {
3412 let to_datetime = |daytime: &DayTime, year: i16| {
3415 daytime.to_datetime(year, Offset::UTC)
3416 };
3417
3418 let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
3419 assert_eq!(
3420 to_datetime(&tz.rule().start, 2023),
3421 date(2023, 1, 1).at(2, 0, 0, 0),
3422 );
3423 assert_eq!(
3424 to_datetime(&tz.rule().end, 2023),
3425 date(2023, 12, 31).at(5, 12, 34, 0),
3426 );
3427
3428 let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2");
3429 assert_eq!(
3430 to_datetime(&tz.rule().start, 2024),
3431 date(2024, 3, 10).at(2, 0, 0, 0),
3432 );
3433 assert_eq!(
3434 to_datetime(&tz.rule().end, 2024),
3435 date(2024, 11, 3).at(2, 0, 0, 0),
3436 );
3437
3438 let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2");
3439 assert_eq!(
3440 to_datetime(&tz.rule().start, 2024),
3441 date(2024, 1, 1).at(2, 0, 0, 0),
3442 );
3443 assert_eq!(
3444 to_datetime(&tz.rule().end, 2024),
3445 date(2024, 12, 31).at(2, 0, 0, 0),
3446 );
3447
3448 let tz = posix_time_zone("EST5EDT,0/0,J365/25");
3449 assert_eq!(
3450 to_datetime(&tz.rule().start, 2024),
3451 date(2024, 1, 1).at(0, 0, 0, 0),
3452 );
3453 assert_eq!(
3454 to_datetime(&tz.rule().end, 2024),
3455 date(2024, 12, 31).at(23, 59, 59, 999_999_999),
3456 );
3457
3458 let tz = posix_time_zone("XXX3EDT4,0/0,J365/23");
3459 assert_eq!(
3460 to_datetime(&tz.rule().start, 2024),
3461 date(2024, 1, 1).at(0, 0, 0, 0),
3462 );
3463 assert_eq!(
3464 to_datetime(&tz.rule().end, 2024),
3465 date(2024, 12, 31).at(23, 0, 0, 0),
3466 );
3467
3468 let tz = posix_time_zone("XXX3EDT4,0/0,365");
3469 assert_eq!(
3470 to_datetime(&tz.rule().end, 2023),
3471 date(2023, 12, 31).at(23, 59, 59, 999_999_999),
3472 );
3473 assert_eq!(
3474 to_datetime(&tz.rule().end, 2024),
3475 date(2024, 12, 31).at(2, 0, 0, 0),
3476 );
3477
3478 let tz = posix_time_zone("XXX3EDT4,J1/-167:59:59,J365/167:59:59");
3479 assert_eq!(
3480 to_datetime(&tz.rule().start, 2024),
3481 date(2024, 1, 1).at(0, 0, 0, 0),
3482 );
3483 assert_eq!(
3484 to_datetime(&tz.rule().end, 2024),
3485 date(2024, 12, 31).at(23, 59, 59, 999_999_999),
3486 );
3487 }
3488
3489 #[test]
3490 fn posix_date_time_spec_time() {
3491 let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
3492 assert_eq!(tz.rule().start.time, TransitionCivilTime::DEFAULT);
3493 assert_eq!(
3494 tz.rule().end.time,
3495 TransitionCivilTime { second: 5 * 60 * 60 + 12 * 60 + 34 },
3496 );
3497 }
3498
3499 #[test]
3500 fn posix_date_spec_to_date() {
3501 let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2");
3502 let start = tz.rule().start.date.to_date(2023);
3503 assert_eq!(start, Some(date(2023, 3, 12)));
3504 let end = tz.rule().end.date.to_date(2023);
3505 assert_eq!(end, Some(date(2023, 11, 5)));
3506 let start = tz.rule().start.date.to_date(2024);
3507 assert_eq!(start, Some(date(2024, 3, 10)));
3508 let end = tz.rule().end.date.to_date(2024);
3509 assert_eq!(end, Some(date(2024, 11, 3)));
3510
3511 let tz = posix_time_zone("EST+5EDT,J60,J365");
3512 let start = tz.rule().start.date.to_date(2023);
3513 assert_eq!(start, Some(date(2023, 3, 1)));
3514 let end = tz.rule().end.date.to_date(2023);
3515 assert_eq!(end, Some(date(2023, 12, 31)));
3516 let start = tz.rule().start.date.to_date(2024);
3517 assert_eq!(start, Some(date(2024, 3, 1)));
3518 let end = tz.rule().end.date.to_date(2024);
3519 assert_eq!(end, Some(date(2024, 12, 31)));
3520
3521 let tz = posix_time_zone("EST+5EDT,59,365");
3522 let start = tz.rule().start.date.to_date(2023);
3523 assert_eq!(start, Some(date(2023, 3, 1)));
3524 let end = tz.rule().end.date.to_date(2023);
3525 assert_eq!(end, None);
3526 let start = tz.rule().start.date.to_date(2024);
3527 assert_eq!(start, Some(date(2024, 2, 29)));
3528 let end = tz.rule().end.date.to_date(2024);
3529 assert_eq!(end, Some(date(2024, 12, 31)));
3530
3531 let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2");
3532 let start = tz.rule().start.date.to_date(2024);
3533 assert_eq!(start, Some(date(2024, 1, 1)));
3534 let end = tz.rule().end.date.to_date(2024);
3535 assert_eq!(end, Some(date(2024, 12, 31)));
3536 }
3537
3538 #[test]
3539 fn posix_time_spec_to_civil_time() {
3540 let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
3541 assert_eq!(
3542 tz.dst.as_ref().unwrap().rule.start.time.second,
3543 2 * 60 * 60,
3544 );
3545 assert_eq!(
3546 tz.dst.as_ref().unwrap().rule.end.time.second,
3547 5 * 60 * 60 + 12 * 60 + 34,
3548 );
3549
3550 let tz = posix_time_zone("EST5EDT,J1/23:59:59,J365/24:00:00");
3551 assert_eq!(
3552 tz.dst.as_ref().unwrap().rule.start.time.second,
3553 23 * 60 * 60 + 59 * 60 + 59,
3554 );
3555 assert_eq!(
3556 tz.dst.as_ref().unwrap().rule.end.time.second,
3557 24 * 60 * 60,
3558 );
3559
3560 let tz = posix_time_zone("EST5EDT,J1/-1,J365/167:00:00");
3561 assert_eq!(
3562 tz.dst.as_ref().unwrap().rule.start.time.second,
3563 -1 * 60 * 60,
3564 );
3565 assert_eq!(
3566 tz.dst.as_ref().unwrap().rule.end.time.second,
3567 167 * 60 * 60,
3568 );
3569 }
3570
3571 #[test]
3572 fn parse_iana() {
3573 let p = TimeZone::parse(b"CRAZY5SHORT,M12.5.0/50,0/2").unwrap();
3575 assert_eq!(
3576 p,
3577 TimeZone {
3578 std_abbrev: astr("CRAZY"),
3579 std_offset: off(-5 * 60 * 60),
3580 dst: Some(Dst {
3581 abbrev: astr("SHORT"),
3582 offset: off(-4 * 60 * 60),
3583 rule: Rule {
3584 start: DayTime {
3585 date: Day::WeekdayOfMonth {
3586 month: 12,
3587 week: 5,
3588 weekday: Weekday::Sunday,
3589 },
3590 time: TransitionCivilTime { second: 50 * 60 * 60 },
3591 },
3592 end: DayTime {
3593 date: Day::JulianZero(0),
3594 time: TransitionCivilTime { second: 2 * 60 * 60 },
3595 },
3596 },
3597 }),
3598 },
3599 );
3600
3601 assert!(TimeZone::parse(b"America/New_York").is_err());
3602 assert!(TimeZone::parse(b":America/New_York").is_err());
3603 }
3604
3605 #[test]
3607 fn parse_empty_is_err() {
3608 assert!(TimeZone::parse(b"").is_err());
3609 }
3610
3611 #[test]
3613 fn parse_weird_is_err() {
3614 let s =
3615 b"AAAAAAAAAAAAAAACAAAAAAAAAAAAQA8AACAAAAAAAAAAAAAAAAACAAAAAAAAAAA";
3616 assert!(TimeZone::parse(s).is_err());
3617
3618 let s =
3619 b"<AAAAAAAAAAAAAAACAAAAAAAAAAAAQA>8<AACAAAAAAAAAAAAAAAAACAAAAAAAAAAA>";
3620 assert!(TimeZone::parse(s).is_err());
3621
3622 let s = b"PPPPPPPPPPPPPPPPPPPPnoofPPPAAA6DaPPPPPPPPPPPPPPPPPPPPPnoofPPPPP,n";
3623 assert!(TimeZone::parse(s).is_err());
3624
3625 let s = b"oooooooooovooooooooooooooooool9<ooooo2o-o-oooooookoorooooooooroo8";
3626 assert!(TimeZone::parse(s).is_err());
3627 }
3628
3629 #[test]
3630 fn parse_long() {
3631 let ts = Timestamp::new(1782939639, 0).unwrap();
3632
3633 let p = posix_time_zone("EST5EDT,M3.2.0,M11.1.0");
3634 assert_eq!(p.next_transition(ts).unwrap().abbreviation(), "EST");
3635 assert_eq!(p.previous_transition(ts).unwrap().abbreviation(), "EDT");
3636
3637 let p = posix_time_zone("ABCDEF5abcdef,M3.2.0,M11.1.0");
3638 assert_eq!(p.next_transition(ts).unwrap().abbreviation(), "ABCDEF");
3639 assert_eq!(
3640 p.previous_transition(ts).unwrap().abbreviation(),
3641 "abcdef"
3642 );
3643
3644 #[cfg(not(feature = "alloc"))]
3645 {
3646 let core_only_over_max = "ABCDEFG5abcdefg,M3.2.0,M11.1.0";
3647 assert!(TimeZone::parse(core_only_over_max).is_err());
3648 }
3649
3650 #[cfg(feature = "alloc")]
3651 {
3652 let alloc_max = "\
3653 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3654 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3655 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3656 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3657 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3658 ABCDE\
3659 5\
3660 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3661 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3662 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3663 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3664 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3665 abcde\
3666 ,M3.2.0,M11.1.0\
3667 ";
3668 let p = posix_time_zone(alloc_max);
3669 assert_eq!(
3670 p.next_transition(ts).unwrap().abbreviation(),
3671 "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3672 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3673 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3674 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3675 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3676 ABCDE\
3677 ",
3678 );
3679 assert_eq!(
3680 p.previous_transition(ts).unwrap().abbreviation(),
3681 "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3682 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3683 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3684 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3685 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3686 abcde\
3687 ",
3688 );
3689
3690 let alloc_over_max = "\
3691 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3692 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3693 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3694 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3695 ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\
3696 ABCDEF\
3697 5\
3698 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3699 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3700 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3701 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3702 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx\
3703 abcdef\
3704 ,M3.2.0,M11.1.0\
3705 ";
3706 assert!(TimeZone::parse(alloc_over_max).is_err());
3707 }
3708 }
3709
3710 #[test]
3711 fn extremes() {
3712 assert!(TimeZone::parse(b"XXX25YYY,M3.2.0,M11.1.0").is_err());
3713 assert!(TimeZone::parse(b"XXX-25YYY,M3.2.0,M11.1.0").is_err());
3714 assert!(
3715 TimeZone::parse(b"XXX24:59:59YYY25:59:59,M3.2.0,M11.1.0").is_err()
3716 );
3717 assert!(TimeZone::parse(b"XXX-24:59:59YYY-25:59:59,M3.2.0,M11.1.0")
3719 .is_err());
3720
3721 let p = TimeZone::parse(b"XXX24:59:59YYY,M3.2.0,M11.1.0").unwrap();
3722 let ts = Timestamp::from_second(1783116302 + 6 * 30 * 86400).unwrap();
3724 assert_eq!(p.to_offset(ts), Offset::from_seconds(-89_999).unwrap());
3725 let ts = Timestamp::from_second(1783116302).unwrap();
3727 assert_eq!(p.to_offset(ts), Offset::from_seconds(-86_399).unwrap());
3728
3729 let p = TimeZone::parse(b"XXX-24:59:59YYY,M3.2.0,M11.1.0").unwrap();
3730 let ts = Timestamp::from_second(1783116302 + 6 * 30 * 86400).unwrap();
3732 assert_eq!(p.to_offset(ts), Offset::from_seconds(89_999).unwrap());
3733 let ts = Timestamp::from_second(1783116302).unwrap();
3735 assert_eq!(p.to_offset(ts), Offset::MAX);
3737 }
3738
3739 #[test]
3740 fn parse_posix_tz() {
3741 assert!(TzEnv::parse("EST5EDT").is_err());
3747
3748 let tz = TzEnv::parse(":EST5EDT").unwrap();
3749 assert_eq!(
3750 tz,
3751 TzEnv::Implementation(TimeZoneId::new("EST5EDT").unwrap())
3752 );
3753
3754 assert!(TzEnv::parse(b":EST5\xFFEDT").is_err());
3757 }
3758}