1use core::fmt::Debug;
2
3use super::{
4 util::{
5 array_str::Abbreviation,
6 itime::{
7 IAmbiguousOffset, IDate, IDateTime, IOffset, ITime, ITimeSecond,
8 ITimestamp, IWeekday,
9 },
10 },
11 PosixDay, PosixDayTime, PosixDst, PosixOffset, PosixRule, PosixTime,
12 PosixTimeZone,
13};
14
15impl PosixTimeZone<Abbreviation> {
16 pub fn parse(
19 bytes: &[u8],
20 ) -> Result<PosixTimeZone<Abbreviation>, PosixTimeZoneError> {
21 let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
27 parser.parse()
28 }
29
30 pub fn parse_prefix<'b>(
34 bytes: &'b [u8],
35 ) -> Result<(PosixTimeZone<Abbreviation>, &'b [u8]), PosixTimeZoneError>
36 {
37 let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
38 parser.parse_prefix()
39 }
40 }
42
43impl<ABBREV: AsRef<str> + Debug> PosixTimeZone<ABBREV> {
44 pub(crate) fn to_offset(&self, timestamp: ITimestamp) -> IOffset {
52 let std_offset = self.std_offset.to_ioffset();
53 if self.dst.is_none() {
54 return std_offset;
55 }
56
57 let dt = timestamp.to_datetime(IOffset::UTC);
58 self.dst_info_utc(dt.date.year)
59 .filter(|dst_info| dst_info.in_dst(dt))
60 .map(|dst_info| dst_info.offset().to_ioffset())
61 .unwrap_or_else(|| std_offset)
62 }
63
64 pub(crate) fn to_offset_info(
71 &self,
72 timestamp: ITimestamp,
73 ) -> (IOffset, &'_ str, bool) {
74 let std_offset = self.std_offset.to_ioffset();
75 if self.dst.is_none() {
76 return (std_offset, self.std_abbrev.as_ref(), false);
77 }
78
79 let dt = timestamp.to_datetime(IOffset::UTC);
80 self.dst_info_utc(dt.date.year)
81 .filter(|dst_info| dst_info.in_dst(dt))
82 .map(|dst_info| {
83 (
84 dst_info.offset().to_ioffset(),
85 dst_info.dst.abbrev.as_ref(),
86 true,
87 )
88 })
89 .unwrap_or_else(|| (std_offset, self.std_abbrev.as_ref(), false))
90 }
91
92 pub(crate) fn to_ambiguous_kind(&self, dt: IDateTime) -> IAmbiguousOffset {
104 let year = dt.date.year;
105 let std_offset = self.std_offset.to_ioffset();
106 let Some(dst_info) = self.dst_info_wall(year) else {
107 return IAmbiguousOffset::Unambiguous { offset: std_offset };
108 };
109 let dst_offset = dst_info.offset().to_ioffset();
110 let diff = dst_offset.second - std_offset.second;
111 if diff == 0 {
126 debug_assert_eq!(std_offset, dst_offset);
127 IAmbiguousOffset::Unambiguous { offset: std_offset }
128 } else if diff.is_negative() {
129 if dst_info.in_dst(dt) {
133 IAmbiguousOffset::Unambiguous { offset: dst_offset }
134 } else {
135 let fold_start = dst_info.start.saturating_add_seconds(diff);
136 let gap_end =
137 dst_info.end.saturating_add_seconds(diff.saturating_neg());
138 if fold_start <= dt && dt < dst_info.start {
139 IAmbiguousOffset::Fold {
140 before: std_offset,
141 after: dst_offset,
142 }
143 } else if dst_info.end <= dt && dt < gap_end {
144 IAmbiguousOffset::Gap {
145 before: dst_offset,
146 after: std_offset,
147 }
148 } else {
149 IAmbiguousOffset::Unambiguous { offset: std_offset }
150 }
151 }
152 } else {
153 if !dst_info.in_dst(dt) {
157 IAmbiguousOffset::Unambiguous { offset: std_offset }
158 } else {
159 let gap_end = dst_info.start.saturating_add_seconds(diff);
164 let fold_start =
165 dst_info.end.saturating_add_seconds(diff.saturating_neg());
166 if dst_info.start <= dt && dt < gap_end {
167 IAmbiguousOffset::Gap {
168 before: std_offset,
169 after: dst_offset,
170 }
171 } else if fold_start <= dt && dt < dst_info.end {
172 IAmbiguousOffset::Fold {
173 before: dst_offset,
174 after: std_offset,
175 }
176 } else {
177 IAmbiguousOffset::Unambiguous { offset: dst_offset }
178 }
179 }
180 }
181 }
182
183 pub(crate) fn previous_transition(
186 &self,
187 timestamp: ITimestamp,
188 ) -> Option<(ITimestamp, IOffset, &'_ str, bool)> {
189 let dt = timestamp.to_datetime(IOffset::UTC);
190 let dst_info = self.dst_info_utc(dt.date.year)?;
191 let (earlier, later) = dst_info.ordered();
192 let (prev, dst_info) = if dt > later {
193 (later, dst_info)
194 } else if dt > earlier {
195 (earlier, dst_info)
196 } else {
197 let prev_year = dt.date.prev_year().ok()?;
198 let dst_info = self.dst_info_utc(prev_year)?;
199 let (_, later) = dst_info.ordered();
200 (later, dst_info)
201 };
202
203 let timestamp = prev.to_timestamp_checked(IOffset::UTC)?;
204 let dt = timestamp.to_datetime(IOffset::UTC);
205 let (offset, abbrev, dst) = if dst_info.in_dst(dt) {
206 (dst_info.offset(), dst_info.dst.abbrev.as_ref(), true)
207 } else {
208 (&self.std_offset, self.std_abbrev.as_ref(), false)
209 };
210 Some((timestamp, offset.to_ioffset(), abbrev, dst))
211 }
212
213 pub(crate) fn next_transition(
216 &self,
217 timestamp: ITimestamp,
218 ) -> Option<(ITimestamp, IOffset, &'_ str, bool)> {
219 let dt = timestamp.to_datetime(IOffset::UTC);
220 let dst_info = self.dst_info_utc(dt.date.year)?;
221 let (earlier, later) = dst_info.ordered();
222 let (next, dst_info) = if dt < earlier {
223 (earlier, dst_info)
224 } else if dt < later {
225 (later, dst_info)
226 } else {
227 let next_year = dt.date.next_year().ok()?;
228 let dst_info = self.dst_info_utc(next_year)?;
229 let (earlier, _) = dst_info.ordered();
230 (earlier, dst_info)
231 };
232
233 let timestamp = next.to_timestamp_checked(IOffset::UTC)?;
234 let dt = timestamp.to_datetime(IOffset::UTC);
235 let (offset, abbrev, dst) = if dst_info.in_dst(dt) {
236 (dst_info.offset(), dst_info.dst.abbrev.as_ref(), true)
237 } else {
238 (&self.std_offset, self.std_abbrev.as_ref(), false)
239 };
240 Some((timestamp, offset.to_ioffset(), abbrev, dst))
241 }
242
243 fn dst_info_utc(&self, year: i16) -> Option<DstInfo<'_, ABBREV>> {
248 let dst = self.dst.as_ref()?;
249 let start =
252 dst.rule.start.to_datetime(year, self.std_offset.to_ioffset());
253 let mut end = dst.rule.end.to_datetime(year, dst.offset.to_ioffset());
256 if start.date.month == 1
307 && start.date.day == 1
308 && start.time == ITime::MIN
309 && year
311 != end.saturating_add_seconds(self.std_offset.second).date.year
312 {
313 end = IDateTime {
314 date: IDate { year, month: 12, day: 31 },
315 time: ITime::MAX,
316 };
317 }
318 Some(DstInfo { dst, start, end })
319 }
320
321 fn dst_info_wall(&self, year: i16) -> Option<DstInfo<'_, ABBREV>> {
327 let dst = self.dst.as_ref()?;
328 let start = dst.rule.start.to_datetime(year, IOffset::UTC);
332 let end = dst.rule.end.to_datetime(year, IOffset::UTC);
333 Some(DstInfo { dst, start, end })
334 }
335
336 #[cfg(test)]
339 fn rule(&self) -> &PosixRule {
340 &self.dst.as_ref().unwrap().rule
341 }
342}
343
344impl<ABBREV: AsRef<str>> core::fmt::Display for PosixTimeZone<ABBREV> {
345 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
346 core::fmt::Display::fmt(
347 &AbbreviationDisplay(self.std_abbrev.as_ref()),
348 f,
349 )?;
350 core::fmt::Display::fmt(&self.std_offset, f)?;
351 if let Some(ref dst) = self.dst {
352 dst.display(&self.std_offset, f)?;
353 }
354 Ok(())
355 }
356}
357
358impl<ABBREV: AsRef<str>> PosixDst<ABBREV> {
359 fn display(
360 &self,
361 std_offset: &PosixOffset,
362 f: &mut core::fmt::Formatter,
363 ) -> core::fmt::Result {
364 core::fmt::Display::fmt(
365 &AbbreviationDisplay(self.abbrev.as_ref()),
366 f,
367 )?;
368 let default = PosixOffset { second: std_offset.second + 3600 };
372 if self.offset != default {
373 core::fmt::Display::fmt(&self.offset, f)?;
374 }
375 f.write_str(",")?;
376 core::fmt::Display::fmt(&self.rule, f)?;
377 Ok(())
378 }
379}
380
381impl core::fmt::Display for PosixRule {
382 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
383 core::fmt::Display::fmt(&self.start, f)?;
384 f.write_str(",")?;
385 core::fmt::Display::fmt(&self.end, f)?;
386 Ok(())
387 }
388}
389
390impl PosixDayTime {
391 pub(crate) fn to_datetime(&self, year: i16, offset: IOffset) -> IDateTime {
403 let mkmin = || IDateTime {
404 date: IDate { year, month: 1, day: 1 },
405 time: ITime::MIN,
406 };
407 let mkmax = || IDateTime {
408 date: IDate { year, month: 12, day: 31 },
409 time: ITime::MAX,
410 };
411 let Some(date) = self.date.to_date(year) else { return mkmax() };
412 let offset = self.time.second - offset.second;
416 let days = offset.div_euclid(86400);
419 let second = offset.rem_euclid(86400);
420
421 let Ok(date) = date.checked_add_days(days) else {
422 return if offset < 0 { mkmin() } else { mkmax() };
423 };
424 if date.year < year {
425 mkmin()
426 } else if date.year > year {
427 mkmax()
428 } else {
429 let time = ITimeSecond { second }.to_time();
430 IDateTime { date, time }
431 }
432 }
433}
434
435impl core::fmt::Display for PosixDayTime {
436 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
437 core::fmt::Display::fmt(&self.date, f)?;
438 if self.time != PosixTime::DEFAULT {
441 f.write_str("/")?;
442 core::fmt::Display::fmt(&self.time, f)?;
443 }
444 Ok(())
445 }
446}
447
448impl PosixDay {
449 fn to_date(&self, year: i16) -> Option<IDate> {
456 match *self {
457 PosixDay::JulianOne(day) => {
458 Some(
462 IDate::from_day_of_year_no_leap(year, day)
463 .expect("Julian `J day` should be in bounds"),
464 )
465 }
466 PosixDay::JulianZero(day) => {
467 IDate::from_day_of_year(year, day + 1).ok()
477 }
478 PosixDay::WeekdayOfMonth { month, week, weekday } => {
479 let weekday = IWeekday::from_sunday_zero_offset(weekday);
480 let first = IDate { year, month, day: 1 };
481 let week = if week == 5 { -1 } else { week };
482 debug_assert!(week == -1 || (1..=4).contains(&week));
483 Some(
498 first
499 .nth_weekday_of_month(week, weekday)
500 .expect("nth weekday always exists"),
501 )
502 }
503 }
504 }
505}
506
507impl core::fmt::Display for PosixDay {
508 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
509 match *self {
510 PosixDay::JulianOne(n) => {
511 f.write_str("J")?;
512 core::fmt::Display::fmt(&n, f)
513 }
514 PosixDay::JulianZero(n) => core::fmt::Display::fmt(&n, f),
515 PosixDay::WeekdayOfMonth { month, week, weekday } => {
516 f.write_str("M")?;
517 core::fmt::Display::fmt(&month, f)?;
518 f.write_str(".")?;
519 core::fmt::Display::fmt(&week, f)?;
520 f.write_str(".")?;
521 core::fmt::Display::fmt(&weekday, f)?;
522 Ok(())
523 }
524 }
525 }
526}
527
528impl PosixTime {
529 const DEFAULT: PosixTime = PosixTime { second: 2 * 60 * 60 };
530}
531
532impl core::fmt::Display for PosixTime {
533 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
534 if self.second.is_negative() {
535 f.write_str("-")?;
536 }
539 let second = self.second.unsigned_abs();
540 let h = second / 3600;
541 let m = (second / 60) % 60;
542 let s = second % 60;
543 core::fmt::Display::fmt(&h, f)?;
544 if m != 0 || s != 0 {
545 write!(f, ":{m:02}")?;
546 if s != 0 {
547 write!(f, ":{s:02}")?;
548 }
549 }
550 Ok(())
551 }
552}
553
554impl PosixOffset {
555 fn to_ioffset(&self) -> IOffset {
556 IOffset { second: self.second }
557 }
558}
559
560impl core::fmt::Display for PosixOffset {
561 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
562 if self.second > 0 {
566 f.write_str("-")?;
567 }
568 let second = self.second.unsigned_abs();
569 let h = second / 3600;
570 let m = (second / 60) % 60;
571 let s = second % 60;
572 core::fmt::Display::fmt(&h, f)?;
573 if m != 0 || s != 0 {
574 write!(f, ":{m:02}")?;
575 if s != 0 {
576 write!(f, ":{s:02}")?;
577 }
578 }
579 Ok(())
580 }
581}
582
583#[derive(Debug)]
588struct AbbreviationDisplay<S>(S);
589
590impl<S: AsRef<str>> core::fmt::Display for AbbreviationDisplay<S> {
591 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
592 let s = self.0.as_ref();
593 if s.chars().any(|ch| ch == '+' || ch == '-') {
594 f.write_str("<")?;
595 core::fmt::Display::fmt(&s, f)?;
596 f.write_str(">")
597 } else {
598 core::fmt::Display::fmt(&s, f)
599 }
600 }
601}
602
603#[derive(Debug, Eq, PartialEq)]
606struct DstInfo<'a, ABBREV> {
607 dst: &'a PosixDst<ABBREV>,
609 start: IDateTime,
618 end: IDateTime,
627}
628
629impl<'a, ABBREV> DstInfo<'a, ABBREV> {
630 fn in_dst(&self, utc_dt: IDateTime) -> bool {
633 if self.start <= self.end {
634 self.start <= utc_dt && utc_dt < self.end
635 } else {
636 !(self.end <= utc_dt && utc_dt < self.start)
637 }
638 }
639
640 fn ordered(&self) -> (IDateTime, IDateTime) {
642 if self.start <= self.end {
643 (self.start, self.end)
644 } else {
645 (self.end, self.start)
646 }
647 }
648
649 fn offset(&self) -> &PosixOffset {
651 &self.dst.offset
652 }
653}
654
655#[derive(Debug)]
657struct Parser<'s> {
658 tz: &'s [u8],
660 pos: core::cell::Cell<usize>,
662 ianav3plus: bool,
676}
677
678impl<'s> Parser<'s> {
679 fn new<B: ?Sized + AsRef<[u8]>>(tz: &'s B) -> Parser<'s> {
682 Parser {
683 tz: tz.as_ref(),
684 pos: core::cell::Cell::new(0),
685 ianav3plus: false,
686 }
687 }
688
689 fn parse(
693 &self,
694 ) -> Result<PosixTimeZone<Abbreviation>, PosixTimeZoneError> {
695 let (time_zone, remaining) = self.parse_prefix()?;
696 if !remaining.is_empty() {
697 return Err(ErrorKind::FoundRemaining.into());
698 }
699 Ok(time_zone)
700 }
701
702 fn parse_prefix(
705 &self,
706 ) -> Result<(PosixTimeZone<Abbreviation>, &'s [u8]), PosixTimeZoneError>
707 {
708 let time_zone = self.parse_posix_time_zone()?;
709 Ok((time_zone, self.remaining()))
710 }
711
712 fn parse_posix_time_zone(
717 &self,
718 ) -> Result<PosixTimeZone<Abbreviation>, PosixTimeZoneError> {
719 if self.is_done() {
720 return Err(ErrorKind::Empty.into());
721 }
722 let std_abbrev =
723 self.parse_abbreviation().map_err(ErrorKind::AbbreviationStd)?;
724 let std_offset =
725 self.parse_posix_offset().map_err(ErrorKind::OffsetStd)?;
726 let mut dst = None;
727 if !self.is_done()
728 && (self.byte().is_ascii_alphabetic() || self.byte() == b'<')
729 {
730 dst = Some(self.parse_posix_dst(&std_offset)?);
731 }
732 Ok(PosixTimeZone { std_abbrev, std_offset, dst })
733 }
734
735 fn parse_posix_dst(
744 &self,
745 std_offset: &PosixOffset,
746 ) -> Result<PosixDst<Abbreviation>, PosixTimeZoneError> {
747 let abbrev =
748 self.parse_abbreviation().map_err(ErrorKind::AbbreviationDst)?;
749 if self.is_done() {
750 return Err(ErrorKind::FoundDstNoRule.into());
751 }
752 let mut offset = PosixOffset { second: std_offset.second + 3600 };
756 if self.byte() != b',' {
757 offset =
758 self.parse_posix_offset().map_err(ErrorKind::OffsetDst)?;
759 if self.is_done() {
760 return Err(ErrorKind::FoundDstNoRuleWithOffset.into());
761 }
762 }
763 if self.byte() != b',' {
764 return Err(ErrorKind::ExpectedCommaAfterDst.into());
765 }
766 if !self.bump() {
767 return Err(ErrorKind::FoundEndAfterComma.into());
768 }
769 let rule = self.parse_rule().map_err(ErrorKind::Rule)?;
770 Ok(PosixDst { abbrev, offset, rule })
771 }
772
773 fn parse_abbreviation(&self) -> Result<Abbreviation, AbbreviationError> {
786 if self.byte() == b'<' {
787 if !self.bump() {
788 return Err(AbbreviationError::Quoted(
789 QuotedAbbreviationError::UnexpectedEndAfterOpening,
790 ));
791 }
792 self.parse_quoted_abbreviation().map_err(AbbreviationError::Quoted)
793 } else {
794 self.parse_unquoted_abbreviation()
795 .map_err(AbbreviationError::Unquoted)
796 }
797 }
798
799 fn parse_unquoted_abbreviation(
811 &self,
812 ) -> Result<Abbreviation, UnquotedAbbreviationError> {
813 let start = self.pos();
814 for i in 0.. {
815 if !self.byte().is_ascii_alphabetic() {
816 break;
817 }
818 if i >= Abbreviation::capacity() {
819 return Err(UnquotedAbbreviationError::TooLong);
820 }
821 if !self.bump() {
822 break;
823 }
824 }
825 let end = self.pos();
826 let abbrev =
827 core::str::from_utf8(&self.tz[start..end]).map_err(|_| {
828 UnquotedAbbreviationError::InvalidUtf8
835 })?;
836 if abbrev.len() < 3 {
837 return Err(UnquotedAbbreviationError::TooShort);
838 }
839 Ok(Abbreviation::new(abbrev).unwrap())
842 }
843
844 fn parse_quoted_abbreviation(
856 &self,
857 ) -> Result<Abbreviation, QuotedAbbreviationError> {
858 let start = self.pos();
859 for i in 0.. {
860 if !self.byte().is_ascii_alphanumeric()
861 && self.byte() != b'+'
862 && self.byte() != b'-'
863 {
864 break;
865 }
866 if i >= Abbreviation::capacity() {
867 return Err(QuotedAbbreviationError::TooLong);
868 }
869 if !self.bump() {
870 break;
871 }
872 }
873 let end = self.pos();
874 let abbrev =
875 core::str::from_utf8(&self.tz[start..end]).map_err(|_| {
876 QuotedAbbreviationError::InvalidUtf8
883 })?;
884 if self.is_done() {
885 return Err(QuotedAbbreviationError::UnexpectedEnd);
886 }
887 if self.byte() != b'>' {
888 return Err(QuotedAbbreviationError::UnexpectedLastByte);
889 }
890 self.bump();
891 if abbrev.len() < 3 {
892 return Err(QuotedAbbreviationError::TooShort);
893 }
894 Ok(Abbreviation::new(abbrev).unwrap())
897 }
898
899 fn parse_posix_offset(&self) -> Result<PosixOffset, PosixOffsetError> {
908 let sign = self.parse_optional_sign()?.unwrap_or(1);
909 let hour = self.parse_hour_posix()?;
910 let (mut minute, mut second) = (0, 0);
911 if self.maybe_byte() == Some(b':') {
912 if !self.bump() {
913 return Err(PosixOffsetError::IncompleteMinutes);
914 }
915 minute = self.parse_minute()?;
916 if self.maybe_byte() == Some(b':') {
917 if !self.bump() {
918 return Err(PosixOffsetError::IncompleteSeconds);
919 }
920 second = self.parse_second()?;
921 }
922 }
923 let mut offset = PosixOffset { second: i32::from(hour) * 3600 };
924 offset.second += i32::from(minute) * 60;
925 offset.second += i32::from(second);
926 offset.second *= i32::from(-sign);
929 assert!(
933 -89999 <= offset.second && offset.second <= 89999,
934 "POSIX offset seconds {} is out of range",
935 offset.second
936 );
937 Ok(offset)
938 }
939
940 fn parse_rule(&self) -> Result<PosixRule, PosixRuleError> {
950 let start = self
951 .parse_posix_datetime()
952 .map_err(PosixRuleError::DateTimeStart)?;
953 if self.maybe_byte() != Some(b',') || !self.bump() {
954 return Err(PosixRuleError::ExpectedEnd);
955 }
956 let end = self
957 .parse_posix_datetime()
958 .map_err(PosixRuleError::DateTimeEnd)?;
959 Ok(PosixRule { start, end })
960 }
961
962 fn parse_posix_datetime(
971 &self,
972 ) -> Result<PosixDayTime, PosixDateTimeError> {
973 let mut daytime = PosixDayTime {
974 date: self.parse_posix_date()?,
975 time: PosixTime::DEFAULT,
976 };
977 if self.maybe_byte() != Some(b'/') {
978 return Ok(daytime);
979 }
980 if !self.bump() {
981 return Err(PosixDateTimeError::ExpectedTime);
982 }
983 daytime.time = self.parse_posix_time()?;
984 Ok(daytime)
985 }
986
987 fn parse_posix_date(&self) -> Result<PosixDay, PosixDateError> {
1001 match self.byte() {
1002 b'J' => {
1003 if !self.bump() {
1004 return Err(PosixDateError::ExpectedJulianNoLeap);
1005 }
1006 Ok(PosixDay::JulianOne(self.parse_posix_julian_day_no_leap()?))
1007 }
1008 b'0'..=b'9' => Ok(PosixDay::JulianZero(
1009 self.parse_posix_julian_day_with_leap()?,
1010 )),
1011 b'M' => {
1012 if !self.bump() {
1013 return Err(PosixDateError::ExpectedMonthWeekWeekday);
1014 }
1015 let (month, week, weekday) = self.parse_weekday_of_month()?;
1016 Ok(PosixDay::WeekdayOfMonth { month, week, weekday })
1017 }
1018 _ => Err(PosixDateError::UnexpectedByte),
1019 }
1020 }
1021
1022 fn parse_posix_julian_day_no_leap(
1029 &self,
1030 ) -> Result<i16, PosixJulianNoLeapError> {
1031 let number = self
1032 .parse_number_with_upto_n_digits(3)
1033 .map_err(PosixJulianNoLeapError::Parse)?;
1034 let number = i16::try_from(number)
1035 .map_err(|_| PosixJulianNoLeapError::Range)?;
1036 if !(1 <= number && number <= 365) {
1037 return Err(PosixJulianNoLeapError::Range);
1038 }
1039 Ok(number)
1040 }
1041
1042 fn parse_posix_julian_day_with_leap(
1049 &self,
1050 ) -> Result<i16, PosixJulianLeapError> {
1051 let number = self
1052 .parse_number_with_upto_n_digits(3)
1053 .map_err(PosixJulianLeapError::Parse)?;
1054 let number =
1055 i16::try_from(number).map_err(|_| PosixJulianLeapError::Range)?;
1056 if !(0 <= number && number <= 365) {
1057 return Err(PosixJulianLeapError::Range);
1058 }
1059 Ok(number)
1060 }
1061
1062 fn parse_weekday_of_month(
1072 &self,
1073 ) -> Result<(i8, i8, i8), WeekdayOfMonthError> {
1074 let month = self.parse_month()?;
1075 if self.maybe_byte() != Some(b'.') {
1076 return Err(WeekdayOfMonthError::ExpectedDotAfterMonth);
1077 }
1078 if !self.bump() {
1079 return Err(WeekdayOfMonthError::ExpectedWeekAfterMonth);
1080 }
1081 let week = self.parse_week()?;
1082 if self.maybe_byte() != Some(b'.') {
1083 return Err(WeekdayOfMonthError::ExpectedDotAfterWeek);
1084 }
1085 if !self.bump() {
1086 return Err(WeekdayOfMonthError::ExpectedDayOfWeekAfterWeek);
1087 }
1088 let weekday = self.parse_weekday()?;
1089 Ok((month, week, weekday))
1090 }
1091
1092 fn parse_posix_time(&self) -> Result<PosixTime, PosixTimeError> {
1099 let (sign, hour) = if self.ianav3plus {
1100 let sign = self.parse_optional_sign()?.unwrap_or(1);
1101 let hour = self.parse_hour_ianav3plus()?;
1102 (sign, hour)
1103 } else {
1104 (1, i16::from(self.parse_hour_posix()?))
1105 };
1106 let (mut minute, mut second) = (0, 0);
1107 if self.maybe_byte() == Some(b':') {
1108 if !self.bump() {
1109 return Err(PosixTimeError::IncompleteMinutes);
1110 }
1111 minute = self.parse_minute()?;
1112 if self.maybe_byte() == Some(b':') {
1113 if !self.bump() {
1114 return Err(PosixTimeError::IncompleteSeconds);
1115 }
1116 second = self.parse_second()?;
1117 }
1118 }
1119 let mut time = PosixTime { second: i32::from(hour) * 3600 };
1120 time.second += i32::from(minute) * 60;
1121 time.second += i32::from(second);
1122 time.second *= i32::from(sign);
1123 assert!(
1127 -604799 <= time.second && time.second <= 604799,
1128 "POSIX time seconds {} is out of range",
1129 time.second
1130 );
1131 Ok(time)
1132 }
1133
1134 fn parse_month(&self) -> Result<i8, MonthError> {
1140 let number = self
1141 .parse_number_with_upto_n_digits(2)
1142 .map_err(MonthError::Parse)?;
1143 let number = i8::try_from(number).map_err(|_| MonthError::Range)?;
1144 if !(1 <= number && number <= 12) {
1145 return Err(MonthError::Range);
1146 }
1147 Ok(number)
1148 }
1149
1150 fn parse_week(&self) -> Result<i8, WeekOfMonthError> {
1155 let number = self
1156 .parse_number_with_exactly_n_digits(1)
1157 .map_err(WeekOfMonthError::Parse)?;
1158 let number =
1159 i8::try_from(number).map_err(|_| WeekOfMonthError::Range)?;
1160 if !(1 <= number && number <= 5) {
1161 return Err(WeekOfMonthError::Range);
1162 }
1163 Ok(number)
1164 }
1165
1166 fn parse_weekday(&self) -> Result<i8, WeekdayError> {
1174 let number = self
1175 .parse_number_with_exactly_n_digits(1)
1176 .map_err(WeekdayError::Parse)?;
1177 let number = i8::try_from(number).map_err(|_| WeekdayError::Range)?;
1178 if !(0 <= number && number <= 6) {
1179 return Err(WeekdayError::Range);
1180 }
1181 Ok(number)
1182 }
1183
1184 fn parse_hour_ianav3plus(&self) -> Result<i16, HourIanaError> {
1196 assert!(self.ianav3plus);
1199 let number = self
1200 .parse_number_with_upto_n_digits(3)
1201 .map_err(HourIanaError::Parse)?;
1202 let number =
1203 i16::try_from(number).map_err(|_| HourIanaError::Range)?;
1204 if !(0 <= number && number <= 167) {
1205 return Err(HourIanaError::Range);
1209 }
1210 Ok(number)
1211 }
1212
1213 fn parse_hour_posix(&self) -> Result<i8, HourPosixError> {
1223 let number = self
1224 .parse_number_with_upto_n_digits(2)
1225 .map_err(HourPosixError::Parse)?;
1226 let number =
1227 i8::try_from(number).map_err(|_| HourPosixError::Range)?;
1228 if !(0 <= number && number <= 24) {
1229 return Err(HourPosixError::Range);
1230 }
1231 Ok(number)
1232 }
1233
1234 fn parse_minute(&self) -> Result<i8, MinuteError> {
1242 let number = self
1243 .parse_number_with_exactly_n_digits(2)
1244 .map_err(MinuteError::Parse)?;
1245 let number = i8::try_from(number).map_err(|_| MinuteError::Range)?;
1246 if !(0 <= number && number <= 59) {
1247 return Err(MinuteError::Range);
1248 }
1249 Ok(number)
1250 }
1251
1252 fn parse_second(&self) -> Result<i8, SecondError> {
1260 let number = self
1261 .parse_number_with_exactly_n_digits(2)
1262 .map_err(SecondError::Parse)?;
1263 let number = i8::try_from(number).map_err(|_| SecondError::Range)?;
1264 if !(0 <= number && number <= 59) {
1265 return Err(SecondError::Range);
1266 }
1267 Ok(number)
1268 }
1269
1270 fn parse_number_with_exactly_n_digits(
1279 &self,
1280 n: usize,
1281 ) -> Result<i32, NumberError> {
1282 assert!(n >= 1, "numbers must have at least 1 digit");
1283 let mut number: i32 = 0;
1284 for _ in 0..n {
1285 if self.is_done() {
1286 return Err(NumberError::ExpectedLength);
1287 }
1288 let byte = self.byte();
1289 let digit = match byte.checked_sub(b'0') {
1290 None => {
1291 return Err(NumberError::InvalidDigit);
1292 }
1293 Some(digit) if digit > 9 => {
1294 return Err(NumberError::InvalidDigit);
1295 }
1296 Some(digit) => {
1297 debug_assert!((0..=9).contains(&digit));
1298 i32::from(digit)
1299 }
1300 };
1301 number = number
1302 .checked_mul(10)
1303 .and_then(|n| n.checked_add(digit))
1304 .ok_or(NumberError::TooBig)?;
1305 self.bump();
1306 }
1307 Ok(number)
1308 }
1309
1310 fn parse_number_with_upto_n_digits(
1317 &self,
1318 n: usize,
1319 ) -> Result<i32, NumberError> {
1320 assert!(n >= 1, "numbers must have at least 1 digit");
1321 let mut number: i32 = 0;
1322 for i in 0..n {
1323 if self.is_done() || !self.byte().is_ascii_digit() {
1324 if i == 0 {
1325 return Err(NumberError::Empty);
1326 }
1327 break;
1328 }
1329 let digit = i32::from(self.byte() - b'0');
1330 number = number
1331 .checked_mul(10)
1332 .and_then(|n| n.checked_add(digit))
1333 .ok_or(NumberError::TooBig)?;
1334 self.bump();
1335 }
1336 Ok(number)
1337 }
1338
1339 fn parse_optional_sign(&self) -> Result<Option<i8>, OptionalSignError> {
1347 if self.is_done() {
1348 return Ok(None);
1349 }
1350 Ok(match self.byte() {
1351 b'-' => {
1352 if !self.bump() {
1353 return Err(OptionalSignError::ExpectedDigitAfterMinus);
1354 }
1355 Some(-1)
1356 }
1357 b'+' => {
1358 if !self.bump() {
1359 return Err(OptionalSignError::ExpectedDigitAfterPlus);
1360 }
1361 Some(1)
1362 }
1363 _ => None,
1364 })
1365 }
1366}
1367
1368impl<'s> Parser<'s> {
1370 fn bump(&self) -> bool {
1374 if self.is_done() {
1375 return false;
1376 }
1377 self.pos.set(
1378 self.pos().checked_add(1).expect("pos cannot overflow usize"),
1379 );
1380 !self.is_done()
1381 }
1382
1383 fn is_done(&self) -> bool {
1385 self.pos() == self.tz.len()
1386 }
1387
1388 fn byte(&self) -> u8 {
1393 self.tz[self.pos()]
1394 }
1395
1396 fn maybe_byte(&self) -> Option<u8> {
1399 self.tz.get(self.pos()).copied()
1400 }
1401
1402 fn pos(&self) -> usize {
1406 self.pos.get()
1407 }
1408
1409 fn remaining(&self) -> &'s [u8] {
1413 &self.tz[self.pos()..]
1414 }
1415}
1416
1417#[derive(Clone, Debug, Eq, PartialEq)]
1418pub struct PosixTimeZoneError {
1419 kind: ErrorKind,
1420}
1421
1422#[derive(Clone, Debug, Eq, PartialEq)]
1423enum ErrorKind {
1424 AbbreviationDst(AbbreviationError),
1425 AbbreviationStd(AbbreviationError),
1426 Empty,
1427 ExpectedCommaAfterDst,
1428 FoundDstNoRule,
1429 FoundDstNoRuleWithOffset,
1430 FoundEndAfterComma,
1431 FoundRemaining,
1432 OffsetDst(PosixOffsetError),
1433 OffsetStd(PosixOffsetError),
1434 Rule(PosixRuleError),
1435}
1436
1437impl core::fmt::Display for PosixTimeZoneError {
1438 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1439 use self::ErrorKind::*;
1440 match self.kind {
1441 AbbreviationDst(ref err) => {
1442 f.write_str("failed to parse DST time zone abbreviation: ")?;
1443 core::fmt::Display::fmt(err, f)
1444 }
1445 AbbreviationStd(ref err) => {
1446 f.write_str(
1447 "failed to parse standard time zone abbreviation: ",
1448 )?;
1449 core::fmt::Display::fmt(err, f)
1450 }
1451 Empty => f.write_str(
1452 "an empty string is not a valid POSIX time zone \
1453 transition rule",
1454 ),
1455 ExpectedCommaAfterDst => f.write_str(
1456 "expected `,` after parsing DST offset \
1457 in POSIX time zone string",
1458 ),
1459 FoundDstNoRule => f.write_str(
1460 "found DST abbreviation in POSIX time zone string, \
1461 but no transition rule \
1462 (this is technically allowed by POSIX, but has \
1463 unspecified behavior)",
1464 ),
1465 FoundDstNoRuleWithOffset => f.write_str(
1466 "found DST abbreviation and offset in POSIX time zone string, \
1467 but no transition rule \
1468 (this is technically allowed by POSIX, but has \
1469 unspecified behavior)",
1470 ),
1471 FoundEndAfterComma => f.write_str(
1472 "after parsing DST offset in POSIX time zone string, \
1473 found end of string after a trailing `,`",
1474 ),
1475 FoundRemaining => f.write_str(
1476 "expected entire POSIX TZ string to be a valid \
1477 time zone transition rule, but found data after \
1478 parsing a valid time zone transition rule",
1479 ),
1480 OffsetDst(ref err) => {
1481 f.write_str("failed to parse DST offset: ")?;
1482 core::fmt::Display::fmt(err, f)
1483 }
1484 OffsetStd(ref err) => {
1485 f.write_str("failed to parse standard offset: ")?;
1486 core::fmt::Display::fmt(err, f)
1487 }
1488 Rule(ref err) => core::fmt::Display::fmt(err, f),
1489 }
1490 }
1491}
1492
1493impl From<ErrorKind> for PosixTimeZoneError {
1494 fn from(kind: ErrorKind) -> PosixTimeZoneError {
1495 PosixTimeZoneError { kind }
1496 }
1497}
1498
1499#[derive(Clone, Debug, Eq, PartialEq)]
1500enum PosixOffsetError {
1501 HourPosix(HourPosixError),
1502 IncompleteMinutes,
1503 IncompleteSeconds,
1504 Minute(MinuteError),
1505 OptionalSign(OptionalSignError),
1506 Second(SecondError),
1507}
1508
1509impl core::fmt::Display for PosixOffsetError {
1510 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1511 use self::PosixOffsetError::*;
1512 match *self {
1513 HourPosix(ref err) => core::fmt::Display::fmt(err, f),
1514 IncompleteMinutes => f.write_str(
1515 "incomplete time in \
1516 POSIX time zone string (missing minutes)",
1517 ),
1518 IncompleteSeconds => f.write_str(
1519 "incomplete time in \
1520 POSIX time zone string (missing seconds)",
1521 ),
1522 Minute(ref err) => core::fmt::Display::fmt(err, f),
1523 Second(ref err) => core::fmt::Display::fmt(err, f),
1524 OptionalSign(ref err) => {
1525 f.write_str(
1526 "failed to parse sign for time offset \
1527 POSIX time zone string",
1528 )?;
1529 core::fmt::Display::fmt(err, f)
1530 }
1531 }
1532 }
1533}
1534
1535impl From<HourPosixError> for PosixOffsetError {
1536 fn from(err: HourPosixError) -> PosixOffsetError {
1537 PosixOffsetError::HourPosix(err)
1538 }
1539}
1540
1541impl From<MinuteError> for PosixOffsetError {
1542 fn from(err: MinuteError) -> PosixOffsetError {
1543 PosixOffsetError::Minute(err)
1544 }
1545}
1546
1547impl From<OptionalSignError> for PosixOffsetError {
1548 fn from(err: OptionalSignError) -> PosixOffsetError {
1549 PosixOffsetError::OptionalSign(err)
1550 }
1551}
1552
1553impl From<SecondError> for PosixOffsetError {
1554 fn from(err: SecondError) -> PosixOffsetError {
1555 PosixOffsetError::Second(err)
1556 }
1557}
1558
1559#[derive(Clone, Debug, Eq, PartialEq)]
1560enum PosixRuleError {
1561 DateTimeEnd(PosixDateTimeError),
1562 DateTimeStart(PosixDateTimeError),
1563 ExpectedEnd,
1564}
1565
1566impl core::fmt::Display for PosixRuleError {
1567 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1568 use self::PosixRuleError::*;
1569 match *self {
1570 DateTimeEnd(ref err) => {
1571 f.write_str("failed to parse end of DST transition rule: ")?;
1572 core::fmt::Display::fmt(err, f)
1573 }
1574 DateTimeStart(ref err) => {
1575 f.write_str("failed to parse start of DST transition rule: ")?;
1576 core::fmt::Display::fmt(err, f)
1577 }
1578 ExpectedEnd => f.write_str(
1579 "expected end of DST rule after parsing the start \
1580 of the DST rule",
1581 ),
1582 }
1583 }
1584}
1585
1586#[derive(Clone, Debug, Eq, PartialEq)]
1587enum PosixDateTimeError {
1588 Date(PosixDateError),
1589 ExpectedTime,
1590 Time(PosixTimeError),
1591}
1592
1593impl core::fmt::Display for PosixDateTimeError {
1594 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1595 use self::PosixDateTimeError::*;
1596 match *self {
1597 Date(ref err) => core::fmt::Display::fmt(err, f),
1598 ExpectedTime => f.write_str(
1599 "expected time specification after `/` following a date
1600 specification in a POSIX time zone DST transition rule",
1601 ),
1602 Time(ref err) => core::fmt::Display::fmt(err, f),
1603 }
1604 }
1605}
1606
1607impl From<PosixDateError> for PosixDateTimeError {
1608 fn from(err: PosixDateError) -> PosixDateTimeError {
1609 PosixDateTimeError::Date(err)
1610 }
1611}
1612
1613impl From<PosixTimeError> for PosixDateTimeError {
1614 fn from(err: PosixTimeError) -> PosixDateTimeError {
1615 PosixDateTimeError::Time(err)
1616 }
1617}
1618
1619#[derive(Clone, Debug, Eq, PartialEq)]
1620enum PosixDateError {
1621 ExpectedJulianNoLeap,
1622 ExpectedMonthWeekWeekday,
1623 JulianLeap(PosixJulianLeapError),
1624 JulianNoLeap(PosixJulianNoLeapError),
1625 UnexpectedByte,
1626 WeekdayOfMonth(WeekdayOfMonthError),
1627}
1628
1629impl core::fmt::Display for PosixDateError {
1630 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1631 use self::PosixDateError::*;
1632 match *self {
1633 ExpectedJulianNoLeap => f.write_str(
1634 "expected one-based Julian day after `J` in date \
1635 specification of a POSIX time zone DST \
1636 transition rule, but found the end of input",
1637 ),
1638 ExpectedMonthWeekWeekday => f.write_str(
1639 "expected month-week-weekday after `M` in date \
1640 specification of a POSIX time zone DST \
1641 transition rule, but found the end of input",
1642 ),
1643 JulianLeap(ref err) => core::fmt::Display::fmt(err, f),
1644 JulianNoLeap(ref err) => core::fmt::Display::fmt(err, f),
1645 UnexpectedByte => f.write_str(
1646 "expected `J`, a digit or `M` at the beginning of a date \
1647 specification of a POSIX time zone DST transition rule",
1648 ),
1649 WeekdayOfMonth(ref err) => core::fmt::Display::fmt(err, f),
1650 }
1651 }
1652}
1653
1654impl From<PosixJulianLeapError> for PosixDateError {
1655 fn from(err: PosixJulianLeapError) -> PosixDateError {
1656 PosixDateError::JulianLeap(err)
1657 }
1658}
1659
1660impl From<PosixJulianNoLeapError> for PosixDateError {
1661 fn from(err: PosixJulianNoLeapError) -> PosixDateError {
1662 PosixDateError::JulianNoLeap(err)
1663 }
1664}
1665
1666impl From<WeekdayOfMonthError> for PosixDateError {
1667 fn from(err: WeekdayOfMonthError) -> PosixDateError {
1668 PosixDateError::WeekdayOfMonth(err)
1669 }
1670}
1671
1672#[derive(Clone, Debug, Eq, PartialEq)]
1673enum PosixJulianNoLeapError {
1674 Parse(NumberError),
1675 Range,
1676}
1677
1678impl core::fmt::Display for PosixJulianNoLeapError {
1679 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1680 use self::PosixJulianNoLeapError::*;
1681 match *self {
1682 Parse(ref err) => {
1683 f.write_str("invalid one-based Julian day digits: ")?;
1684 core::fmt::Display::fmt(err, f)
1685 }
1686 Range => f.write_str(
1687 "parsed one-based Julian day, but it's not in supported \
1688 range of `1..=365`",
1689 ),
1690 }
1691 }
1692}
1693
1694#[derive(Clone, Debug, Eq, PartialEq)]
1695enum PosixJulianLeapError {
1696 Parse(NumberError),
1697 Range,
1698}
1699
1700impl core::fmt::Display for PosixJulianLeapError {
1701 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1702 use self::PosixJulianLeapError::*;
1703 match *self {
1704 Parse(ref err) => {
1705 f.write_str("invalid zero-based Julian day digits: ")?;
1706 core::fmt::Display::fmt(err, f)
1707 }
1708 Range => f.write_str(
1709 "parsed zero-based Julian day, but it's not in supported \
1710 range of `0..=365`",
1711 ),
1712 }
1713 }
1714}
1715
1716#[derive(Clone, Debug, Eq, PartialEq)]
1717enum AbbreviationError {
1718 Quoted(QuotedAbbreviationError),
1719 Unquoted(UnquotedAbbreviationError),
1720}
1721
1722impl core::fmt::Display for AbbreviationError {
1723 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1724 use self::AbbreviationError::*;
1725 match *self {
1726 Quoted(ref err) => core::fmt::Display::fmt(err, f),
1727 Unquoted(ref err) => core::fmt::Display::fmt(err, f),
1728 }
1729 }
1730}
1731
1732#[derive(Clone, Debug, Eq, PartialEq)]
1733enum UnquotedAbbreviationError {
1734 InvalidUtf8,
1735 TooLong,
1736 TooShort,
1737}
1738
1739impl core::fmt::Display for UnquotedAbbreviationError {
1740 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1741 use self::UnquotedAbbreviationError::*;
1742 match *self {
1743 InvalidUtf8 => f.write_str(
1744 "unquoted time zone abbreviation must be valid UTF-8",
1745 ),
1746 TooLong => write!(
1747 f,
1748 "expected unquoted time zone abbreviation with at most \
1749 {} bytes, but found an abbreviation that is longer",
1750 Abbreviation::capacity(),
1751 ),
1752 TooShort => f.write_str(
1753 "expected unquoted time zone abbreviation to have length of \
1754 3 or more bytes, but an abbreviation that is shorter",
1755 ),
1756 }
1757 }
1758}
1759
1760#[derive(Clone, Debug, Eq, PartialEq)]
1761enum QuotedAbbreviationError {
1762 InvalidUtf8,
1763 TooLong,
1764 TooShort,
1765 UnexpectedEnd,
1766 UnexpectedEndAfterOpening,
1767 UnexpectedLastByte,
1768}
1769
1770impl core::fmt::Display for QuotedAbbreviationError {
1771 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1772 use self::QuotedAbbreviationError::*;
1773 match *self {
1774 InvalidUtf8 => f.write_str(
1775 "quoted time zone abbreviation must be valid UTF-8",
1776 ),
1777 TooLong => write!(
1778 f,
1779 "expected quoted time zone abbreviation with at most \
1780 {} bytes, but found an abbreviation that is longer",
1781 Abbreviation::capacity(),
1782 ),
1783 TooShort => f.write_str(
1784 "expected quoted time zone abbreviation to have length of \
1785 3 or more bytes, but an abbreviation that is shorter",
1786 ),
1787 UnexpectedEnd => f.write_str(
1788 "found non-empty quoted time zone abbreviation, but \
1789 found end of input before an end-of-quoted abbreviation \
1790 `>` character",
1791 ),
1792 UnexpectedEndAfterOpening => f.write_str(
1793 "found opening `<` quote for time zone abbreviation in \
1794 POSIX time zone transition rule, and expected a name \
1795 following it, but found the end of input instead",
1796 ),
1797 UnexpectedLastByte => f.write_str(
1798 "found non-empty quoted time zone abbreviation, but \
1799 found did not find end-of-quoted abbreviation `>` \
1800 character",
1801 ),
1802 }
1803 }
1804}
1805
1806#[derive(Clone, Debug, Eq, PartialEq)]
1807enum WeekdayOfMonthError {
1808 ExpectedDayOfWeekAfterWeek,
1809 ExpectedDotAfterMonth,
1810 ExpectedDotAfterWeek,
1811 ExpectedWeekAfterMonth,
1812 Month(MonthError),
1813 WeekOfMonth(WeekOfMonthError),
1814 Weekday(WeekdayError),
1815}
1816
1817impl core::fmt::Display for WeekdayOfMonthError {
1818 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1819 use self::WeekdayOfMonthError::*;
1820 match *self {
1821 ExpectedDayOfWeekAfterWeek => f.write_str(
1822 "expected day-of-week after week in POSIX time zone rule",
1823 ),
1824 ExpectedDotAfterMonth => {
1825 f.write_str("expected `.` after month in POSIX time zone rule")
1826 }
1827 ExpectedWeekAfterMonth => f.write_str(
1828 "expected week after month in POSIX time zone rule",
1829 ),
1830 ExpectedDotAfterWeek => {
1831 f.write_str("expected `.` after week in POSIX time zone rule")
1832 }
1833 Month(ref err) => core::fmt::Display::fmt(err, f),
1834 WeekOfMonth(ref err) => core::fmt::Display::fmt(err, f),
1835 Weekday(ref err) => core::fmt::Display::fmt(err, f),
1836 }
1837 }
1838}
1839
1840impl From<MonthError> for WeekdayOfMonthError {
1841 fn from(err: MonthError) -> WeekdayOfMonthError {
1842 WeekdayOfMonthError::Month(err)
1843 }
1844}
1845
1846impl From<WeekOfMonthError> for WeekdayOfMonthError {
1847 fn from(err: WeekOfMonthError) -> WeekdayOfMonthError {
1848 WeekdayOfMonthError::WeekOfMonth(err)
1849 }
1850}
1851
1852impl From<WeekdayError> for WeekdayOfMonthError {
1853 fn from(err: WeekdayError) -> WeekdayOfMonthError {
1854 WeekdayOfMonthError::Weekday(err)
1855 }
1856}
1857
1858#[derive(Clone, Debug, Eq, PartialEq)]
1859enum PosixTimeError {
1860 HourIana(HourIanaError),
1861 HourPosix(HourPosixError),
1862 IncompleteMinutes,
1863 IncompleteSeconds,
1864 Minute(MinuteError),
1865 OptionalSign(OptionalSignError),
1866 Second(SecondError),
1867}
1868
1869impl core::fmt::Display for PosixTimeError {
1870 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1871 use self::PosixTimeError::*;
1872 match *self {
1873 HourIana(ref err) => core::fmt::Display::fmt(err, f),
1874 HourPosix(ref err) => core::fmt::Display::fmt(err, f),
1875 IncompleteMinutes => f.write_str(
1876 "incomplete time zone transition time in \
1877 POSIX time zone string (missing minutes)",
1878 ),
1879 IncompleteSeconds => f.write_str(
1880 "incomplete time zone transition time in \
1881 POSIX time zone string (missing seconds)",
1882 ),
1883 Minute(ref err) => core::fmt::Display::fmt(err, f),
1884 Second(ref err) => core::fmt::Display::fmt(err, f),
1885 OptionalSign(ref err) => {
1886 f.write_str(
1887 "failed to parse sign for time zone transition time",
1888 )?;
1889 core::fmt::Display::fmt(err, f)
1890 }
1891 }
1892 }
1893}
1894
1895impl From<HourIanaError> for PosixTimeError {
1896 fn from(err: HourIanaError) -> PosixTimeError {
1897 PosixTimeError::HourIana(err)
1898 }
1899}
1900
1901impl From<HourPosixError> for PosixTimeError {
1902 fn from(err: HourPosixError) -> PosixTimeError {
1903 PosixTimeError::HourPosix(err)
1904 }
1905}
1906
1907impl From<MinuteError> for PosixTimeError {
1908 fn from(err: MinuteError) -> PosixTimeError {
1909 PosixTimeError::Minute(err)
1910 }
1911}
1912
1913impl From<OptionalSignError> for PosixTimeError {
1914 fn from(err: OptionalSignError) -> PosixTimeError {
1915 PosixTimeError::OptionalSign(err)
1916 }
1917}
1918
1919impl From<SecondError> for PosixTimeError {
1920 fn from(err: SecondError) -> PosixTimeError {
1921 PosixTimeError::Second(err)
1922 }
1923}
1924
1925#[derive(Clone, Debug, Eq, PartialEq)]
1926enum MonthError {
1927 Parse(NumberError),
1928 Range,
1929}
1930
1931impl core::fmt::Display for MonthError {
1932 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1933 use self::MonthError::*;
1934 match *self {
1935 Parse(ref err) => {
1936 f.write_str("invalid month digits: ")?;
1937 core::fmt::Display::fmt(err, f)
1938 }
1939 Range => f.write_str(
1940 "parsed month, but it's not in supported \
1941 range of `1..=12`",
1942 ),
1943 }
1944 }
1945}
1946
1947#[derive(Clone, Debug, Eq, PartialEq)]
1948enum WeekOfMonthError {
1949 Parse(NumberError),
1950 Range,
1951}
1952
1953impl core::fmt::Display for WeekOfMonthError {
1954 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1955 use self::WeekOfMonthError::*;
1956 match *self {
1957 Parse(ref err) => {
1958 f.write_str("invalid week-of-month digits: ")?;
1959 core::fmt::Display::fmt(err, f)
1960 }
1961 Range => f.write_str(
1962 "parsed week-of-month, but it's not in supported \
1963 range of `1..=5`",
1964 ),
1965 }
1966 }
1967}
1968
1969#[derive(Clone, Debug, Eq, PartialEq)]
1970enum WeekdayError {
1971 Parse(NumberError),
1972 Range,
1973}
1974
1975impl core::fmt::Display for WeekdayError {
1976 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1977 use self::WeekdayError::*;
1978 match *self {
1979 Parse(ref err) => {
1980 f.write_str("invalid weekday digits: ")?;
1981 core::fmt::Display::fmt(err, f)
1982 }
1983 Range => f.write_str(
1984 "parsed weekday, but it's not in supported \
1985 range of `0..=6` (with `0` corresponding to Sunday)",
1986 ),
1987 }
1988 }
1989}
1990
1991#[derive(Clone, Debug, Eq, PartialEq)]
1992enum HourIanaError {
1993 Parse(NumberError),
1994 Range,
1995}
1996
1997impl core::fmt::Display for HourIanaError {
1998 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1999 use self::HourIanaError::*;
2000 match *self {
2001 Parse(ref err) => {
2002 f.write_str("invalid hour digits: ")?;
2003 core::fmt::Display::fmt(err, f)
2004 }
2005 Range => f.write_str(
2006 "parsed hours, but it's not in supported \
2007 range of `-167..=167`",
2008 ),
2009 }
2010 }
2011}
2012
2013#[derive(Clone, Debug, Eq, PartialEq)]
2014enum HourPosixError {
2015 Parse(NumberError),
2016 Range,
2017}
2018
2019impl core::fmt::Display for HourPosixError {
2020 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2021 use self::HourPosixError::*;
2022 match *self {
2023 Parse(ref err) => {
2024 f.write_str("invalid hour digits: ")?;
2025 core::fmt::Display::fmt(err, f)
2026 }
2027 Range => f.write_str(
2028 "parsed hours, but it's not in supported \
2029 range of `0..=24`",
2030 ),
2031 }
2032 }
2033}
2034
2035#[derive(Clone, Debug, Eq, PartialEq)]
2036enum MinuteError {
2037 Parse(NumberError),
2038 Range,
2039}
2040
2041impl core::fmt::Display for MinuteError {
2042 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2043 use self::MinuteError::*;
2044 match *self {
2045 Parse(ref err) => {
2046 f.write_str("invalid minute digits: ")?;
2047 core::fmt::Display::fmt(err, f)
2048 }
2049 Range => f.write_str(
2050 "parsed minutes, but it's not in supported \
2051 range of `0..=59`",
2052 ),
2053 }
2054 }
2055}
2056
2057#[derive(Clone, Debug, Eq, PartialEq)]
2058enum SecondError {
2059 Parse(NumberError),
2060 Range,
2061}
2062
2063impl core::fmt::Display for SecondError {
2064 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2065 use self::SecondError::*;
2066 match *self {
2067 Parse(ref err) => {
2068 f.write_str("invalid second digits: ")?;
2069 core::fmt::Display::fmt(err, f)
2070 }
2071 Range => f.write_str(
2072 "parsed seconds, but it's not in supported \
2073 range of `0..=59`",
2074 ),
2075 }
2076 }
2077}
2078
2079#[derive(Clone, Debug, Eq, PartialEq)]
2080enum NumberError {
2081 Empty,
2082 ExpectedLength,
2083 InvalidDigit,
2084 TooBig,
2085}
2086
2087impl core::fmt::Display for NumberError {
2088 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2089 use self::NumberError::*;
2090 match *self {
2091 Empty => f.write_str("invalid number, no digits found"),
2092 ExpectedLength => f.write_str(
2093 "expected a fixed number of digits, \
2094 but found incorrect number",
2095 ),
2096 InvalidDigit => f.write_str("expected digit in range `0..=9`"),
2097 TooBig => f.write_str(
2098 "parsed number too big to fit into a 32-bit signed integer",
2099 ),
2100 }
2101 }
2102}
2103
2104#[derive(Clone, Debug, Eq, PartialEq)]
2105enum OptionalSignError {
2106 ExpectedDigitAfterMinus,
2107 ExpectedDigitAfterPlus,
2108}
2109
2110impl core::fmt::Display for OptionalSignError {
2111 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2112 use self::OptionalSignError::*;
2113 match *self {
2114 ExpectedDigitAfterMinus => f.write_str(
2115 "expected digit after `-` sign, \
2116 but got end of input",
2117 ),
2118 ExpectedDigitAfterPlus => f.write_str(
2119 "expected digit after `+` sign, \
2120 but got end of input",
2121 ),
2122 }
2123 }
2124}
2125
2126#[cfg(test)]
2127mod tests {
2128 use super::*;
2129
2130 fn posix_time_zone(
2131 input: impl AsRef<[u8]>,
2132 ) -> PosixTimeZone<Abbreviation> {
2133 let input = input.as_ref();
2134 let tz = PosixTimeZone::parse(input).unwrap();
2135 #[cfg(feature = "alloc")]
2136 {
2137 use alloc::string::ToString;
2138
2139 let reparsed =
2151 PosixTimeZone::parse(tz.to_string().as_bytes()).unwrap();
2152 assert_eq!(tz, reparsed);
2153 assert_eq!(tz.to_string(), reparsed.to_string());
2154 }
2155 tz
2156 }
2157
2158 fn parser(s: &str) -> Parser<'_> {
2159 Parser::new(s.as_bytes())
2160 }
2161
2162 fn date(year: i16, month: i8, day: i8) -> IDate {
2163 IDate { year, month, day }
2164 }
2165
2166 #[test]
2167 fn parse() {
2168 let p = parser("NZST-12NZDT,J60,J300");
2169 assert_eq!(
2170 p.parse().unwrap(),
2171 PosixTimeZone {
2172 std_abbrev: "NZST".into(),
2173 std_offset: PosixOffset { second: 12 * 60 * 60 },
2174 dst: Some(PosixDst {
2175 abbrev: "NZDT".into(),
2176 offset: PosixOffset { second: 13 * 60 * 60 },
2177 rule: PosixRule {
2178 start: PosixDayTime {
2179 date: PosixDay::JulianOne(60),
2180 time: PosixTime { second: 2 * 60 * 60 },
2181 },
2182 end: PosixDayTime {
2183 date: PosixDay::JulianOne(300),
2184 time: PosixTime { second: 2 * 60 * 60 },
2185 },
2186 },
2187 }),
2188 },
2189 );
2190
2191 let p = Parser::new("NZST-12NZDT,J60,J300WAT");
2192 assert!(p.parse().is_err());
2193 }
2194
2195 #[test]
2196 fn parse_posix_time_zone() {
2197 let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3");
2198 assert_eq!(
2199 p.parse_posix_time_zone().unwrap(),
2200 PosixTimeZone {
2201 std_abbrev: "NZST".into(),
2202 std_offset: PosixOffset { second: 12 * 60 * 60 },
2203 dst: Some(PosixDst {
2204 abbrev: "NZDT".into(),
2205 offset: PosixOffset { second: 13 * 60 * 60 },
2206 rule: PosixRule {
2207 start: PosixDayTime {
2208 date: PosixDay::WeekdayOfMonth {
2209 month: 9,
2210 week: 5,
2211 weekday: 0,
2212 },
2213 time: PosixTime { second: 2 * 60 * 60 },
2214 },
2215 end: PosixDayTime {
2216 date: PosixDay::WeekdayOfMonth {
2217 month: 4,
2218 week: 1,
2219 weekday: 0,
2220 },
2221 time: PosixTime { second: 3 * 60 * 60 },
2222 },
2223 },
2224 }),
2225 },
2226 );
2227
2228 let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3WAT");
2229 assert_eq!(
2230 p.parse_posix_time_zone().unwrap(),
2231 PosixTimeZone {
2232 std_abbrev: "NZST".into(),
2233 std_offset: PosixOffset { second: 12 * 60 * 60 },
2234 dst: Some(PosixDst {
2235 abbrev: "NZDT".into(),
2236 offset: PosixOffset { second: 13 * 60 * 60 },
2237 rule: PosixRule {
2238 start: PosixDayTime {
2239 date: PosixDay::WeekdayOfMonth {
2240 month: 9,
2241 week: 5,
2242 weekday: 0,
2243 },
2244 time: PosixTime { second: 2 * 60 * 60 },
2245 },
2246 end: PosixDayTime {
2247 date: PosixDay::WeekdayOfMonth {
2248 month: 4,
2249 week: 1,
2250 weekday: 0,
2251 },
2252 time: PosixTime { second: 3 * 60 * 60 },
2253 },
2254 },
2255 }),
2256 },
2257 );
2258
2259 let p = Parser::new("NZST-12NZDT,J60,J300");
2260 assert_eq!(
2261 p.parse_posix_time_zone().unwrap(),
2262 PosixTimeZone {
2263 std_abbrev: "NZST".into(),
2264 std_offset: PosixOffset { second: 12 * 60 * 60 },
2265 dst: Some(PosixDst {
2266 abbrev: "NZDT".into(),
2267 offset: PosixOffset { second: 13 * 60 * 60 },
2268 rule: PosixRule {
2269 start: PosixDayTime {
2270 date: PosixDay::JulianOne(60),
2271 time: PosixTime { second: 2 * 60 * 60 },
2272 },
2273 end: PosixDayTime {
2274 date: PosixDay::JulianOne(300),
2275 time: PosixTime { second: 2 * 60 * 60 },
2276 },
2277 },
2278 }),
2279 },
2280 );
2281
2282 let p = Parser::new("NZST-12NZDT,J60,J300WAT");
2283 assert_eq!(
2284 p.parse_posix_time_zone().unwrap(),
2285 PosixTimeZone {
2286 std_abbrev: "NZST".into(),
2287 std_offset: PosixOffset { second: 12 * 60 * 60 },
2288 dst: Some(PosixDst {
2289 abbrev: "NZDT".into(),
2290 offset: PosixOffset { second: 13 * 60 * 60 },
2291 rule: PosixRule {
2292 start: PosixDayTime {
2293 date: PosixDay::JulianOne(60),
2294 time: PosixTime { second: 2 * 60 * 60 },
2295 },
2296 end: PosixDayTime {
2297 date: PosixDay::JulianOne(300),
2298 time: PosixTime { second: 2 * 60 * 60 },
2299 },
2300 },
2301 }),
2302 },
2303 );
2304 }
2305
2306 #[test]
2307 fn parse_posix_dst() {
2308 let p = Parser::new("NZDT,M9.5.0,M4.1.0/3");
2309 assert_eq!(
2310 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
2311 PosixDst {
2312 abbrev: "NZDT".into(),
2313 offset: PosixOffset { second: 13 * 60 * 60 },
2314 rule: PosixRule {
2315 start: PosixDayTime {
2316 date: PosixDay::WeekdayOfMonth {
2317 month: 9,
2318 week: 5,
2319 weekday: 0,
2320 },
2321 time: PosixTime { second: 2 * 60 * 60 },
2322 },
2323 end: PosixDayTime {
2324 date: PosixDay::WeekdayOfMonth {
2325 month: 4,
2326 week: 1,
2327 weekday: 0,
2328 },
2329 time: PosixTime { second: 3 * 60 * 60 },
2330 },
2331 },
2332 },
2333 );
2334
2335 let p = Parser::new("NZDT,J60,J300");
2336 assert_eq!(
2337 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
2338 PosixDst {
2339 abbrev: "NZDT".into(),
2340 offset: PosixOffset { second: 13 * 60 * 60 },
2341 rule: PosixRule {
2342 start: PosixDayTime {
2343 date: PosixDay::JulianOne(60),
2344 time: PosixTime { second: 2 * 60 * 60 },
2345 },
2346 end: PosixDayTime {
2347 date: PosixDay::JulianOne(300),
2348 time: PosixTime { second: 2 * 60 * 60 },
2349 },
2350 },
2351 },
2352 );
2353
2354 let p = Parser::new("NZDT-7,J60,J300");
2355 assert_eq!(
2356 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
2357 PosixDst {
2358 abbrev: "NZDT".into(),
2359 offset: PosixOffset { second: 7 * 60 * 60 },
2360 rule: PosixRule {
2361 start: PosixDayTime {
2362 date: PosixDay::JulianOne(60),
2363 time: PosixTime { second: 2 * 60 * 60 },
2364 },
2365 end: PosixDayTime {
2366 date: PosixDay::JulianOne(300),
2367 time: PosixTime { second: 2 * 60 * 60 },
2368 },
2369 },
2370 },
2371 );
2372
2373 let p = Parser::new("NZDT+7,J60,J300");
2374 assert_eq!(
2375 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
2376 PosixDst {
2377 abbrev: "NZDT".into(),
2378 offset: PosixOffset { second: -7 * 60 * 60 },
2379 rule: PosixRule {
2380 start: PosixDayTime {
2381 date: PosixDay::JulianOne(60),
2382 time: PosixTime { second: 2 * 60 * 60 },
2383 },
2384 end: PosixDayTime {
2385 date: PosixDay::JulianOne(300),
2386 time: PosixTime { second: 2 * 60 * 60 },
2387 },
2388 },
2389 },
2390 );
2391
2392 let p = Parser::new("NZDT7,J60,J300");
2393 assert_eq!(
2394 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
2395 PosixDst {
2396 abbrev: "NZDT".into(),
2397 offset: PosixOffset { second: -7 * 60 * 60 },
2398 rule: PosixRule {
2399 start: PosixDayTime {
2400 date: PosixDay::JulianOne(60),
2401 time: PosixTime { second: 2 * 60 * 60 },
2402 },
2403 end: PosixDayTime {
2404 date: PosixDay::JulianOne(300),
2405 time: PosixTime { second: 2 * 60 * 60 },
2406 },
2407 },
2408 },
2409 );
2410
2411 let p = Parser::new("NZDT7,");
2412 assert!(p
2413 .parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 })
2414 .is_err());
2415
2416 let p = Parser::new("NZDT7!");
2417 assert!(p
2418 .parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 })
2419 .is_err());
2420 }
2421
2422 #[test]
2423 fn parse_abbreviation() {
2424 let p = Parser::new("ABC");
2425 assert_eq!(p.parse_abbreviation().unwrap(), "ABC");
2426
2427 let p = Parser::new("<ABC>");
2428 assert_eq!(p.parse_abbreviation().unwrap(), "ABC");
2429
2430 let p = Parser::new("<+09>");
2431 assert_eq!(p.parse_abbreviation().unwrap(), "+09");
2432
2433 let p = Parser::new("+09");
2434 assert!(p.parse_abbreviation().is_err());
2435 }
2436
2437 #[test]
2438 fn parse_unquoted_abbreviation() {
2439 let p = Parser::new("ABC");
2440 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC");
2441
2442 let p = Parser::new("ABCXYZ");
2443 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABCXYZ");
2444
2445 let p = Parser::new("ABC123");
2446 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC");
2447
2448 let tz = "a".repeat(30);
2449 let p = Parser::new(&tz);
2450 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), &*tz);
2451
2452 let p = Parser::new("a");
2453 assert!(p.parse_unquoted_abbreviation().is_err());
2454
2455 let p = Parser::new("ab");
2456 assert!(p.parse_unquoted_abbreviation().is_err());
2457
2458 let p = Parser::new("ab1");
2459 assert!(p.parse_unquoted_abbreviation().is_err());
2460
2461 let tz = "a".repeat(31);
2462 let p = Parser::new(&tz);
2463 assert!(p.parse_unquoted_abbreviation().is_err());
2464
2465 let p = Parser::new(b"ab\xFFcd");
2466 assert!(p.parse_unquoted_abbreviation().is_err());
2467 }
2468
2469 #[test]
2470 fn parse_quoted_abbreviation() {
2471 let p = Parser::new("ABC>");
2476 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC");
2477
2478 let p = Parser::new("ABCXYZ>");
2479 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABCXYZ");
2480
2481 let p = Parser::new("ABC>123");
2482 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC");
2483
2484 let p = Parser::new("ABC123>");
2485 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC123");
2486
2487 let p = Parser::new("ab1>");
2488 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ab1");
2489
2490 let p = Parser::new("+09>");
2491 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "+09");
2492
2493 let p = Parser::new("-09>");
2494 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "-09");
2495
2496 let tz = alloc::format!("{}>", "a".repeat(30));
2497 let p = Parser::new(&tz);
2498 assert_eq!(
2499 p.parse_quoted_abbreviation().unwrap(),
2500 tz.trim_end_matches(">")
2501 );
2502
2503 let p = Parser::new("a>");
2504 assert!(p.parse_quoted_abbreviation().is_err());
2505
2506 let p = Parser::new("ab>");
2507 assert!(p.parse_quoted_abbreviation().is_err());
2508
2509 let tz = alloc::format!("{}>", "a".repeat(31));
2510 let p = Parser::new(&tz);
2511 assert!(p.parse_quoted_abbreviation().is_err());
2512
2513 let p = Parser::new(b"ab\xFFcd>");
2514 assert!(p.parse_quoted_abbreviation().is_err());
2515
2516 let p = Parser::new("ABC");
2517 assert!(p.parse_quoted_abbreviation().is_err());
2518
2519 let p = Parser::new("ABC!>");
2520 assert!(p.parse_quoted_abbreviation().is_err());
2521 }
2522
2523 #[test]
2524 fn parse_posix_offset() {
2525 let p = Parser::new("5");
2526 assert_eq!(p.parse_posix_offset().unwrap().second, -5 * 60 * 60);
2527
2528 let p = Parser::new("+5");
2529 assert_eq!(p.parse_posix_offset().unwrap().second, -5 * 60 * 60);
2530
2531 let p = Parser::new("-5");
2532 assert_eq!(p.parse_posix_offset().unwrap().second, 5 * 60 * 60);
2533
2534 let p = Parser::new("-12:34:56");
2535 assert_eq!(
2536 p.parse_posix_offset().unwrap().second,
2537 12 * 60 * 60 + 34 * 60 + 56,
2538 );
2539
2540 let p = Parser::new("a");
2541 assert!(p.parse_posix_offset().is_err());
2542
2543 let p = Parser::new("-");
2544 assert!(p.parse_posix_offset().is_err());
2545
2546 let p = Parser::new("+");
2547 assert!(p.parse_posix_offset().is_err());
2548
2549 let p = Parser::new("-a");
2550 assert!(p.parse_posix_offset().is_err());
2551
2552 let p = Parser::new("+a");
2553 assert!(p.parse_posix_offset().is_err());
2554
2555 let p = Parser::new("-25");
2556 assert!(p.parse_posix_offset().is_err());
2557
2558 let p = Parser::new("+25");
2559 assert!(p.parse_posix_offset().is_err());
2560
2561 let p = Parser { ianav3plus: true, ..Parser::new("25") };
2569 assert!(p.parse_posix_offset().is_err());
2570 let p = Parser { ianav3plus: true, ..Parser::new("+25") };
2571 assert!(p.parse_posix_offset().is_err());
2572 let p = Parser { ianav3plus: true, ..Parser::new("-25") };
2573 assert!(p.parse_posix_offset().is_err());
2574 }
2575
2576 #[test]
2577 fn parse_rule() {
2578 let p = Parser::new("M9.5.0,M4.1.0/3");
2579 assert_eq!(
2580 p.parse_rule().unwrap(),
2581 PosixRule {
2582 start: PosixDayTime {
2583 date: PosixDay::WeekdayOfMonth {
2584 month: 9,
2585 week: 5,
2586 weekday: 0,
2587 },
2588 time: PosixTime { second: 2 * 60 * 60 },
2589 },
2590 end: PosixDayTime {
2591 date: PosixDay::WeekdayOfMonth {
2592 month: 4,
2593 week: 1,
2594 weekday: 0,
2595 },
2596 time: PosixTime { second: 3 * 60 * 60 },
2597 },
2598 },
2599 );
2600
2601 let p = Parser::new("M9.5.0");
2602 assert!(p.parse_rule().is_err());
2603
2604 let p = Parser::new(",M9.5.0,M4.1.0/3");
2605 assert!(p.parse_rule().is_err());
2606
2607 let p = Parser::new("M9.5.0/");
2608 assert!(p.parse_rule().is_err());
2609
2610 let p = Parser::new("M9.5.0,M4.1.0/");
2611 assert!(p.parse_rule().is_err());
2612 }
2613
2614 #[test]
2615 fn parse_posix_datetime() {
2616 let p = Parser::new("J1");
2617 assert_eq!(
2618 p.parse_posix_datetime().unwrap(),
2619 PosixDayTime {
2620 date: PosixDay::JulianOne(1),
2621 time: PosixTime { second: 2 * 60 * 60 }
2622 },
2623 );
2624
2625 let p = Parser::new("J1/3");
2626 assert_eq!(
2627 p.parse_posix_datetime().unwrap(),
2628 PosixDayTime {
2629 date: PosixDay::JulianOne(1),
2630 time: PosixTime { second: 3 * 60 * 60 }
2631 },
2632 );
2633
2634 let p = Parser::new("M4.1.0/3");
2635 assert_eq!(
2636 p.parse_posix_datetime().unwrap(),
2637 PosixDayTime {
2638 date: PosixDay::WeekdayOfMonth {
2639 month: 4,
2640 week: 1,
2641 weekday: 0,
2642 },
2643 time: PosixTime { second: 3 * 60 * 60 },
2644 },
2645 );
2646
2647 let p = Parser::new("1/3:45:05");
2648 assert_eq!(
2649 p.parse_posix_datetime().unwrap(),
2650 PosixDayTime {
2651 date: PosixDay::JulianZero(1),
2652 time: PosixTime { second: 3 * 60 * 60 + 45 * 60 + 5 },
2653 },
2654 );
2655
2656 let p = Parser::new("a");
2657 assert!(p.parse_posix_datetime().is_err());
2658
2659 let p = Parser::new("J1/");
2660 assert!(p.parse_posix_datetime().is_err());
2661
2662 let p = Parser::new("1/");
2663 assert!(p.parse_posix_datetime().is_err());
2664
2665 let p = Parser::new("M4.1.0/");
2666 assert!(p.parse_posix_datetime().is_err());
2667 }
2668
2669 #[test]
2670 fn parse_posix_date() {
2671 let p = Parser::new("J1");
2672 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianOne(1));
2673 let p = Parser::new("J365");
2674 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianOne(365));
2675
2676 let p = Parser::new("0");
2677 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(0));
2678 let p = Parser::new("1");
2679 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(1));
2680 let p = Parser::new("365");
2681 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(365));
2682
2683 let p = Parser::new("M9.5.0");
2684 assert_eq!(
2685 p.parse_posix_date().unwrap(),
2686 PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 0 },
2687 );
2688 let p = Parser::new("M9.5.6");
2689 assert_eq!(
2690 p.parse_posix_date().unwrap(),
2691 PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 6 },
2692 );
2693 let p = Parser::new("M09.5.6");
2694 assert_eq!(
2695 p.parse_posix_date().unwrap(),
2696 PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 6 },
2697 );
2698 let p = Parser::new("M12.1.1");
2699 assert_eq!(
2700 p.parse_posix_date().unwrap(),
2701 PosixDay::WeekdayOfMonth { month: 12, week: 1, weekday: 1 },
2702 );
2703
2704 let p = Parser::new("a");
2705 assert!(p.parse_posix_date().is_err());
2706
2707 let p = Parser::new("j");
2708 assert!(p.parse_posix_date().is_err());
2709
2710 let p = Parser::new("m");
2711 assert!(p.parse_posix_date().is_err());
2712
2713 let p = Parser::new("n");
2714 assert!(p.parse_posix_date().is_err());
2715
2716 let p = Parser::new("J366");
2717 assert!(p.parse_posix_date().is_err());
2718
2719 let p = Parser::new("366");
2720 assert!(p.parse_posix_date().is_err());
2721 }
2722
2723 #[test]
2724 fn parse_posix_julian_day_no_leap() {
2725 let p = Parser::new("1");
2726 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1);
2727
2728 let p = Parser::new("001");
2729 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1);
2730
2731 let p = Parser::new("365");
2732 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365);
2733
2734 let p = Parser::new("3655");
2735 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365);
2736
2737 let p = Parser::new("0");
2738 assert!(p.parse_posix_julian_day_no_leap().is_err());
2739
2740 let p = Parser::new("366");
2741 assert!(p.parse_posix_julian_day_no_leap().is_err());
2742 }
2743
2744 #[test]
2745 fn parse_posix_julian_day_with_leap() {
2746 let p = Parser::new("0");
2747 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 0);
2748
2749 let p = Parser::new("1");
2750 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1);
2751
2752 let p = Parser::new("001");
2753 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1);
2754
2755 let p = Parser::new("365");
2756 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365);
2757
2758 let p = Parser::new("3655");
2759 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365);
2760
2761 let p = Parser::new("366");
2762 assert!(p.parse_posix_julian_day_with_leap().is_err());
2763 }
2764
2765 #[test]
2766 fn parse_weekday_of_month() {
2767 let p = Parser::new("9.5.0");
2768 assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 5, 0));
2769
2770 let p = Parser::new("9.1.6");
2771 assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 1, 6));
2772
2773 let p = Parser::new("09.1.6");
2774 assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 1, 6));
2775
2776 let p = Parser::new("9");
2777 assert!(p.parse_weekday_of_month().is_err());
2778
2779 let p = Parser::new("9.");
2780 assert!(p.parse_weekday_of_month().is_err());
2781
2782 let p = Parser::new("9.5");
2783 assert!(p.parse_weekday_of_month().is_err());
2784
2785 let p = Parser::new("9.5.");
2786 assert!(p.parse_weekday_of_month().is_err());
2787
2788 let p = Parser::new("0.5.0");
2789 assert!(p.parse_weekday_of_month().is_err());
2790
2791 let p = Parser::new("13.5.0");
2792 assert!(p.parse_weekday_of_month().is_err());
2793
2794 let p = Parser::new("9.0.0");
2795 assert!(p.parse_weekday_of_month().is_err());
2796
2797 let p = Parser::new("9.6.0");
2798 assert!(p.parse_weekday_of_month().is_err());
2799
2800 let p = Parser::new("9.5.7");
2801 assert!(p.parse_weekday_of_month().is_err());
2802 }
2803
2804 #[test]
2805 fn parse_posix_time() {
2806 let p = Parser::new("5");
2807 assert_eq!(p.parse_posix_time().unwrap().second, 5 * 60 * 60);
2808
2809 let p = Parser::new("22");
2810 assert_eq!(p.parse_posix_time().unwrap().second, 22 * 60 * 60);
2811
2812 let p = Parser::new("02");
2813 assert_eq!(p.parse_posix_time().unwrap().second, 2 * 60 * 60);
2814
2815 let p = Parser::new("5:45");
2816 assert_eq!(
2817 p.parse_posix_time().unwrap().second,
2818 5 * 60 * 60 + 45 * 60
2819 );
2820
2821 let p = Parser::new("5:45:12");
2822 assert_eq!(
2823 p.parse_posix_time().unwrap().second,
2824 5 * 60 * 60 + 45 * 60 + 12
2825 );
2826
2827 let p = Parser::new("5:45:129");
2828 assert_eq!(
2829 p.parse_posix_time().unwrap().second,
2830 5 * 60 * 60 + 45 * 60 + 12
2831 );
2832
2833 let p = Parser::new("5:45:12:");
2834 assert_eq!(
2835 p.parse_posix_time().unwrap().second,
2836 5 * 60 * 60 + 45 * 60 + 12
2837 );
2838
2839 let p = Parser { ianav3plus: true, ..Parser::new("+5:45:12") };
2840 assert_eq!(
2841 p.parse_posix_time().unwrap().second,
2842 5 * 60 * 60 + 45 * 60 + 12
2843 );
2844
2845 let p = Parser { ianav3plus: true, ..Parser::new("-5:45:12") };
2846 assert_eq!(
2847 p.parse_posix_time().unwrap().second,
2848 -(5 * 60 * 60 + 45 * 60 + 12)
2849 );
2850
2851 let p = Parser { ianav3plus: true, ..Parser::new("-167:45:12") };
2852 assert_eq!(
2853 p.parse_posix_time().unwrap().second,
2854 -(167 * 60 * 60 + 45 * 60 + 12),
2855 );
2856
2857 let p = Parser::new("25");
2858 assert!(p.parse_posix_time().is_err());
2859
2860 let p = Parser::new("12:2");
2861 assert!(p.parse_posix_time().is_err());
2862
2863 let p = Parser::new("12:");
2864 assert!(p.parse_posix_time().is_err());
2865
2866 let p = Parser::new("12:23:5");
2867 assert!(p.parse_posix_time().is_err());
2868
2869 let p = Parser::new("12:23:");
2870 assert!(p.parse_posix_time().is_err());
2871
2872 let p = Parser { ianav3plus: true, ..Parser::new("168") };
2873 assert!(p.parse_posix_time().is_err());
2874
2875 let p = Parser { ianav3plus: true, ..Parser::new("-168") };
2876 assert!(p.parse_posix_time().is_err());
2877
2878 let p = Parser { ianav3plus: true, ..Parser::new("+168") };
2879 assert!(p.parse_posix_time().is_err());
2880 }
2881
2882 #[test]
2883 fn parse_month() {
2884 let p = Parser::new("1");
2885 assert_eq!(p.parse_month().unwrap(), 1);
2886
2887 let p = Parser::new("01");
2892 assert_eq!(p.parse_month().unwrap(), 1);
2893
2894 let p = Parser::new("12");
2895 assert_eq!(p.parse_month().unwrap(), 12);
2896
2897 let p = Parser::new("0");
2898 assert!(p.parse_month().is_err());
2899
2900 let p = Parser::new("00");
2901 assert!(p.parse_month().is_err());
2902
2903 let p = Parser::new("001");
2904 assert!(p.parse_month().is_err());
2905
2906 let p = Parser::new("13");
2907 assert!(p.parse_month().is_err());
2908 }
2909
2910 #[test]
2911 fn parse_week() {
2912 let p = Parser::new("1");
2913 assert_eq!(p.parse_week().unwrap(), 1);
2914
2915 let p = Parser::new("5");
2916 assert_eq!(p.parse_week().unwrap(), 5);
2917
2918 let p = Parser::new("55");
2919 assert_eq!(p.parse_week().unwrap(), 5);
2920
2921 let p = Parser::new("0");
2922 assert!(p.parse_week().is_err());
2923
2924 let p = Parser::new("6");
2925 assert!(p.parse_week().is_err());
2926
2927 let p = Parser::new("00");
2928 assert!(p.parse_week().is_err());
2929
2930 let p = Parser::new("01");
2931 assert!(p.parse_week().is_err());
2932
2933 let p = Parser::new("05");
2934 assert!(p.parse_week().is_err());
2935 }
2936
2937 #[test]
2938 fn parse_weekday() {
2939 let p = Parser::new("0");
2940 assert_eq!(p.parse_weekday().unwrap(), 0);
2941
2942 let p = Parser::new("1");
2943 assert_eq!(p.parse_weekday().unwrap(), 1);
2944
2945 let p = Parser::new("6");
2946 assert_eq!(p.parse_weekday().unwrap(), 6);
2947
2948 let p = Parser::new("00");
2949 assert_eq!(p.parse_weekday().unwrap(), 0);
2950
2951 let p = Parser::new("06");
2952 assert_eq!(p.parse_weekday().unwrap(), 0);
2953
2954 let p = Parser::new("60");
2955 assert_eq!(p.parse_weekday().unwrap(), 6);
2956
2957 let p = Parser::new("7");
2958 assert!(p.parse_weekday().is_err());
2959 }
2960
2961 #[test]
2962 fn parse_hour_posix() {
2963 let p = Parser::new("5");
2964 assert_eq!(p.parse_hour_posix().unwrap(), 5);
2965
2966 let p = Parser::new("0");
2967 assert_eq!(p.parse_hour_posix().unwrap(), 0);
2968
2969 let p = Parser::new("00");
2970 assert_eq!(p.parse_hour_posix().unwrap(), 0);
2971
2972 let p = Parser::new("24");
2973 assert_eq!(p.parse_hour_posix().unwrap(), 24);
2974
2975 let p = Parser::new("100");
2976 assert_eq!(p.parse_hour_posix().unwrap(), 10);
2977
2978 let p = Parser::new("25");
2979 assert!(p.parse_hour_posix().is_err());
2980
2981 let p = Parser::new("99");
2982 assert!(p.parse_hour_posix().is_err());
2983 }
2984
2985 #[test]
2986 fn parse_hour_ianav3plus() {
2987 let new = |input| Parser { ianav3plus: true, ..Parser::new(input) };
2988
2989 let p = new("5");
2990 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 5);
2991
2992 let p = new("0");
2993 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
2994
2995 let p = new("00");
2996 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
2997
2998 let p = new("000");
2999 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
3000
3001 let p = new("24");
3002 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 24);
3003
3004 let p = new("100");
3005 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100);
3006
3007 let p = new("1000");
3008 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100);
3009
3010 let p = new("167");
3011 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 167);
3012
3013 let p = new("168");
3014 assert!(p.parse_hour_ianav3plus().is_err());
3015
3016 let p = new("999");
3017 assert!(p.parse_hour_ianav3plus().is_err());
3018 }
3019
3020 #[test]
3021 fn parse_minute() {
3022 let p = Parser::new("00");
3023 assert_eq!(p.parse_minute().unwrap(), 0);
3024
3025 let p = Parser::new("24");
3026 assert_eq!(p.parse_minute().unwrap(), 24);
3027
3028 let p = Parser::new("59");
3029 assert_eq!(p.parse_minute().unwrap(), 59);
3030
3031 let p = Parser::new("599");
3032 assert_eq!(p.parse_minute().unwrap(), 59);
3033
3034 let p = Parser::new("0");
3035 assert!(p.parse_minute().is_err());
3036
3037 let p = Parser::new("1");
3038 assert!(p.parse_minute().is_err());
3039
3040 let p = Parser::new("9");
3041 assert!(p.parse_minute().is_err());
3042
3043 let p = Parser::new("60");
3044 assert!(p.parse_minute().is_err());
3045 }
3046
3047 #[test]
3048 fn parse_second() {
3049 let p = Parser::new("00");
3050 assert_eq!(p.parse_second().unwrap(), 0);
3051
3052 let p = Parser::new("24");
3053 assert_eq!(p.parse_second().unwrap(), 24);
3054
3055 let p = Parser::new("59");
3056 assert_eq!(p.parse_second().unwrap(), 59);
3057
3058 let p = Parser::new("599");
3059 assert_eq!(p.parse_second().unwrap(), 59);
3060
3061 let p = Parser::new("0");
3062 assert!(p.parse_second().is_err());
3063
3064 let p = Parser::new("1");
3065 assert!(p.parse_second().is_err());
3066
3067 let p = Parser::new("9");
3068 assert!(p.parse_second().is_err());
3069
3070 let p = Parser::new("60");
3071 assert!(p.parse_second().is_err());
3072 }
3073
3074 #[test]
3075 fn parse_number_with_exactly_n_digits() {
3076 let p = Parser::new("1");
3077 assert_eq!(p.parse_number_with_exactly_n_digits(1).unwrap(), 1);
3078
3079 let p = Parser::new("12");
3080 assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12);
3081
3082 let p = Parser::new("123");
3083 assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12);
3084
3085 let p = Parser::new("");
3086 assert!(p.parse_number_with_exactly_n_digits(1).is_err());
3087
3088 let p = Parser::new("1");
3089 assert!(p.parse_number_with_exactly_n_digits(2).is_err());
3090
3091 let p = Parser::new("12");
3092 assert!(p.parse_number_with_exactly_n_digits(3).is_err());
3093 }
3094
3095 #[test]
3096 fn parse_number_with_upto_n_digits() {
3097 let p = Parser::new("1");
3098 assert_eq!(p.parse_number_with_upto_n_digits(1).unwrap(), 1);
3099
3100 let p = Parser::new("1");
3101 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 1);
3102
3103 let p = Parser::new("12");
3104 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12);
3105
3106 let p = Parser::new("12");
3107 assert_eq!(p.parse_number_with_upto_n_digits(3).unwrap(), 12);
3108
3109 let p = Parser::new("123");
3110 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12);
3111
3112 let p = Parser::new("");
3113 assert!(p.parse_number_with_upto_n_digits(1).is_err());
3114
3115 let p = Parser::new("a");
3116 assert!(p.parse_number_with_upto_n_digits(1).is_err());
3117 }
3118
3119 #[test]
3120 fn to_dst_civil_datetime_utc_range() {
3121 let tz = posix_time_zone("WART4WARST,J1/-3,J365/20");
3122 let dst_info = DstInfo {
3123 dst: tz.dst.as_ref().unwrap(),
3127 start: date(2024, 1, 1).at(1, 0, 0, 0),
3128 end: date(2024, 12, 31).at(23, 0, 0, 0),
3129 };
3130 assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
3131
3132 let tz = posix_time_zone("WART4WARST,J1/-4,J365/21");
3133 let dst_info = DstInfo {
3134 dst: tz.dst.as_ref().unwrap(),
3135 start: date(2024, 1, 1).at(0, 0, 0, 0),
3136 end: date(2024, 12, 31).at(23, 59, 59, 999_999_999),
3137 };
3138 assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
3139
3140 let tz = posix_time_zone("EST5EDT,M3.2.0,M11.1.0");
3141 let dst_info = DstInfo {
3142 dst: tz.dst.as_ref().unwrap(),
3143 start: date(2024, 3, 10).at(7, 0, 0, 0),
3144 end: date(2024, 11, 3).at(6, 0, 0, 0),
3145 };
3146 assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
3147 }
3148
3149 #[test]
3151 fn regression_permanent_dst() {
3152 let tz = posix_time_zone("XXX-2<+01>-1,0/0,J365/23");
3153 let dst_info = DstInfo {
3154 dst: tz.dst.as_ref().unwrap(),
3155 start: date(2087, 1, 1).at(0, 0, 0, 0),
3156 end: date(2087, 12, 31).at(23, 59, 59, 999_999_999),
3157 };
3158 assert_eq!(tz.dst_info_utc(2087), Some(dst_info));
3159 }
3160
3161 #[test]
3162 fn reasonable() {
3163 assert!(PosixTimeZone::parse(b"EST5").is_ok());
3164 assert!(PosixTimeZone::parse(b"EST5EDT").is_err());
3165 assert!(PosixTimeZone::parse(b"EST5EDT,J1,J365").is_ok());
3166
3167 let tz = posix_time_zone("EST24EDT,J1,J365");
3168 assert_eq!(
3169 tz,
3170 PosixTimeZone {
3171 std_abbrev: "EST".into(),
3172 std_offset: PosixOffset { second: -24 * 60 * 60 },
3173 dst: Some(PosixDst {
3174 abbrev: "EDT".into(),
3175 offset: PosixOffset { second: -23 * 60 * 60 },
3176 rule: PosixRule {
3177 start: PosixDayTime {
3178 date: PosixDay::JulianOne(1),
3179 time: PosixTime::DEFAULT,
3180 },
3181 end: PosixDayTime {
3182 date: PosixDay::JulianOne(365),
3183 time: PosixTime::DEFAULT,
3184 },
3185 },
3186 }),
3187 },
3188 );
3189
3190 let tz = posix_time_zone("EST-24EDT,J1,J365");
3191 assert_eq!(
3192 tz,
3193 PosixTimeZone {
3194 std_abbrev: "EST".into(),
3195 std_offset: PosixOffset { second: 24 * 60 * 60 },
3196 dst: Some(PosixDst {
3197 abbrev: "EDT".into(),
3198 offset: PosixOffset { second: 25 * 60 * 60 },
3199 rule: PosixRule {
3200 start: PosixDayTime {
3201 date: PosixDay::JulianOne(1),
3202 time: PosixTime::DEFAULT,
3203 },
3204 end: PosixDayTime {
3205 date: PosixDay::JulianOne(365),
3206 time: PosixTime::DEFAULT,
3207 },
3208 },
3209 }),
3210 },
3211 );
3212 }
3213
3214 #[test]
3215 fn posix_date_time_spec_to_datetime() {
3216 let to_datetime = |daytime: &PosixDayTime, year: i16| {
3219 daytime.to_datetime(year, IOffset::UTC)
3220 };
3221
3222 let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
3223 assert_eq!(
3224 to_datetime(&tz.rule().start, 2023),
3225 date(2023, 1, 1).at(2, 0, 0, 0),
3226 );
3227 assert_eq!(
3228 to_datetime(&tz.rule().end, 2023),
3229 date(2023, 12, 31).at(5, 12, 34, 0),
3230 );
3231
3232 let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2");
3233 assert_eq!(
3234 to_datetime(&tz.rule().start, 2024),
3235 date(2024, 3, 10).at(2, 0, 0, 0),
3236 );
3237 assert_eq!(
3238 to_datetime(&tz.rule().end, 2024),
3239 date(2024, 11, 3).at(2, 0, 0, 0),
3240 );
3241
3242 let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2");
3243 assert_eq!(
3244 to_datetime(&tz.rule().start, 2024),
3245 date(2024, 1, 1).at(2, 0, 0, 0),
3246 );
3247 assert_eq!(
3248 to_datetime(&tz.rule().end, 2024),
3249 date(2024, 12, 31).at(2, 0, 0, 0),
3250 );
3251
3252 let tz = posix_time_zone("EST5EDT,0/0,J365/25");
3253 assert_eq!(
3254 to_datetime(&tz.rule().start, 2024),
3255 date(2024, 1, 1).at(0, 0, 0, 0),
3256 );
3257 assert_eq!(
3258 to_datetime(&tz.rule().end, 2024),
3259 date(2024, 12, 31).at(23, 59, 59, 999_999_999),
3260 );
3261
3262 let tz = posix_time_zone("XXX3EDT4,0/0,J365/23");
3263 assert_eq!(
3264 to_datetime(&tz.rule().start, 2024),
3265 date(2024, 1, 1).at(0, 0, 0, 0),
3266 );
3267 assert_eq!(
3268 to_datetime(&tz.rule().end, 2024),
3269 date(2024, 12, 31).at(23, 0, 0, 0),
3270 );
3271
3272 let tz = posix_time_zone("XXX3EDT4,0/0,365");
3273 assert_eq!(
3274 to_datetime(&tz.rule().end, 2023),
3275 date(2023, 12, 31).at(23, 59, 59, 999_999_999),
3276 );
3277 assert_eq!(
3278 to_datetime(&tz.rule().end, 2024),
3279 date(2024, 12, 31).at(2, 0, 0, 0),
3280 );
3281
3282 let tz = posix_time_zone("XXX3EDT4,J1/-167:59:59,J365/167:59:59");
3283 assert_eq!(
3284 to_datetime(&tz.rule().start, 2024),
3285 date(2024, 1, 1).at(0, 0, 0, 0),
3286 );
3287 assert_eq!(
3288 to_datetime(&tz.rule().end, 2024),
3289 date(2024, 12, 31).at(23, 59, 59, 999_999_999),
3290 );
3291 }
3292
3293 #[test]
3294 fn posix_date_time_spec_time() {
3295 let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
3296 assert_eq!(tz.rule().start.time, PosixTime::DEFAULT);
3297 assert_eq!(
3298 tz.rule().end.time,
3299 PosixTime { second: 5 * 60 * 60 + 12 * 60 + 34 },
3300 );
3301 }
3302
3303 #[test]
3304 fn posix_date_spec_to_date() {
3305 let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2");
3306 let start = tz.rule().start.date.to_date(2023);
3307 assert_eq!(start, Some(date(2023, 3, 12)));
3308 let end = tz.rule().end.date.to_date(2023);
3309 assert_eq!(end, Some(date(2023, 11, 5)));
3310 let start = tz.rule().start.date.to_date(2024);
3311 assert_eq!(start, Some(date(2024, 3, 10)));
3312 let end = tz.rule().end.date.to_date(2024);
3313 assert_eq!(end, Some(date(2024, 11, 3)));
3314
3315 let tz = posix_time_zone("EST+5EDT,J60,J365");
3316 let start = tz.rule().start.date.to_date(2023);
3317 assert_eq!(start, Some(date(2023, 3, 1)));
3318 let end = tz.rule().end.date.to_date(2023);
3319 assert_eq!(end, Some(date(2023, 12, 31)));
3320 let start = tz.rule().start.date.to_date(2024);
3321 assert_eq!(start, Some(date(2024, 3, 1)));
3322 let end = tz.rule().end.date.to_date(2024);
3323 assert_eq!(end, Some(date(2024, 12, 31)));
3324
3325 let tz = posix_time_zone("EST+5EDT,59,365");
3326 let start = tz.rule().start.date.to_date(2023);
3327 assert_eq!(start, Some(date(2023, 3, 1)));
3328 let end = tz.rule().end.date.to_date(2023);
3329 assert_eq!(end, None);
3330 let start = tz.rule().start.date.to_date(2024);
3331 assert_eq!(start, Some(date(2024, 2, 29)));
3332 let end = tz.rule().end.date.to_date(2024);
3333 assert_eq!(end, Some(date(2024, 12, 31)));
3334
3335 let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2");
3336 let start = tz.rule().start.date.to_date(2024);
3337 assert_eq!(start, Some(date(2024, 1, 1)));
3338 let end = tz.rule().end.date.to_date(2024);
3339 assert_eq!(end, Some(date(2024, 12, 31)));
3340 }
3341
3342 #[test]
3343 fn posix_time_spec_to_civil_time() {
3344 let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
3345 assert_eq!(
3346 tz.dst.as_ref().unwrap().rule.start.time.second,
3347 2 * 60 * 60,
3348 );
3349 assert_eq!(
3350 tz.dst.as_ref().unwrap().rule.end.time.second,
3351 5 * 60 * 60 + 12 * 60 + 34,
3352 );
3353
3354 let tz = posix_time_zone("EST5EDT,J1/23:59:59,J365/24:00:00");
3355 assert_eq!(
3356 tz.dst.as_ref().unwrap().rule.start.time.second,
3357 23 * 60 * 60 + 59 * 60 + 59,
3358 );
3359 assert_eq!(
3360 tz.dst.as_ref().unwrap().rule.end.time.second,
3361 24 * 60 * 60,
3362 );
3363
3364 let tz = posix_time_zone("EST5EDT,J1/-1,J365/167:00:00");
3365 assert_eq!(
3366 tz.dst.as_ref().unwrap().rule.start.time.second,
3367 -1 * 60 * 60,
3368 );
3369 assert_eq!(
3370 tz.dst.as_ref().unwrap().rule.end.time.second,
3371 167 * 60 * 60,
3372 );
3373 }
3374
3375 #[test]
3376 fn parse_iana() {
3377 let p = PosixTimeZone::parse(b"CRAZY5SHORT,M12.5.0/50,0/2").unwrap();
3379 assert_eq!(
3380 p,
3381 PosixTimeZone {
3382 std_abbrev: "CRAZY".into(),
3383 std_offset: PosixOffset { second: -5 * 60 * 60 },
3384 dst: Some(PosixDst {
3385 abbrev: "SHORT".into(),
3386 offset: PosixOffset { second: -4 * 60 * 60 },
3387 rule: PosixRule {
3388 start: PosixDayTime {
3389 date: PosixDay::WeekdayOfMonth {
3390 month: 12,
3391 week: 5,
3392 weekday: 0,
3393 },
3394 time: PosixTime { second: 50 * 60 * 60 },
3395 },
3396 end: PosixDayTime {
3397 date: PosixDay::JulianZero(0),
3398 time: PosixTime { second: 2 * 60 * 60 },
3399 },
3400 },
3401 }),
3402 },
3403 );
3404
3405 assert!(PosixTimeZone::parse(b"America/New_York").is_err());
3406 assert!(PosixTimeZone::parse(b":America/New_York").is_err());
3407 }
3408
3409 #[test]
3411 fn parse_empty_is_err() {
3412 assert!(PosixTimeZone::parse(b"").is_err());
3413 }
3414
3415 #[test]
3417 fn parse_weird_is_err() {
3418 let s =
3419 b"AAAAAAAAAAAAAAACAAAAAAAAAAAAQA8AACAAAAAAAAAAAAAAAAACAAAAAAAAAAA";
3420 assert!(PosixTimeZone::parse(s).is_err());
3421
3422 let s =
3423 b"<AAAAAAAAAAAAAAACAAAAAAAAAAAAQA>8<AACAAAAAAAAAAAAAAAAACAAAAAAAAAAA>";
3424 assert!(PosixTimeZone::parse(s).is_err());
3425
3426 let s = b"PPPPPPPPPPPPPPPPPPPPnoofPPPAAA6DaPPPPPPPPPPPPPPPPPPPPPnoofPPPPP,n";
3427 assert!(PosixTimeZone::parse(s).is_err());
3428
3429 let s = b"oooooooooovooooooooooooooooool9<ooooo2o-o-oooooookoorooooooooroo8";
3430 assert!(PosixTimeZone::parse(s).is_err());
3431 }
3432}