1use core::fmt::Debug;
2
3use super::{
4 util::{
5 array_str::Abbreviation,
6 error::{err, Error},
7 escape::{Byte, Bytes},
8 itime::{
9 IAmbiguousOffset, IDate, IDateTime, IOffset, ITime, ITimeSecond,
10 ITimestamp, IWeekday,
11 },
12 },
13 PosixDay, PosixDayTime, PosixDst, PosixOffset, PosixRule, PosixTime,
14 PosixTimeZone,
15};
16
17impl PosixTimeZone<Abbreviation> {
18 #[cfg(feature = "alloc")]
21 pub fn parse(bytes: &[u8]) -> Result<PosixTimeZone<Abbreviation>, Error> {
22 let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
28 parser.parse()
29 }
30
31 #[cfg(feature = "alloc")]
35 pub fn parse_prefix<'b>(
36 bytes: &'b [u8],
37 ) -> Result<(PosixTimeZone<Abbreviation>, &'b [u8]), Error> {
38 let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
39 parser.parse_prefix()
40 }
41 }
43
44impl<ABBREV: AsRef<str> + Debug> PosixTimeZone<ABBREV> {
45 pub(crate) fn to_offset(&self, timestamp: ITimestamp) -> IOffset {
53 let std_offset = self.std_offset.to_ioffset();
54 if self.dst.is_none() {
55 return std_offset;
56 }
57
58 let dt = timestamp.to_datetime(IOffset::UTC);
59 self.dst_info_utc(dt.date.year)
60 .filter(|dst_info| dst_info.in_dst(dt))
61 .map(|dst_info| dst_info.offset().to_ioffset())
62 .unwrap_or_else(|| std_offset)
63 }
64
65 pub(crate) fn to_offset_info(
72 &self,
73 timestamp: ITimestamp,
74 ) -> (IOffset, &'_ str, bool) {
75 let std_offset = self.std_offset.to_ioffset();
76 if self.dst.is_none() {
77 return (std_offset, self.std_abbrev.as_ref(), false);
78 }
79
80 let dt = timestamp.to_datetime(IOffset::UTC);
81 self.dst_info_utc(dt.date.year)
82 .filter(|dst_info| dst_info.in_dst(dt))
83 .map(|dst_info| {
84 (
85 dst_info.offset().to_ioffset(),
86 dst_info.dst.abbrev.as_ref(),
87 true,
88 )
89 })
90 .unwrap_or_else(|| (std_offset, self.std_abbrev.as_ref(), false))
91 }
92
93 pub(crate) fn to_ambiguous_kind(&self, dt: IDateTime) -> IAmbiguousOffset {
105 let year = dt.date.year;
106 let std_offset = self.std_offset.to_ioffset();
107 let Some(dst_info) = self.dst_info_wall(year) else {
108 return IAmbiguousOffset::Unambiguous { offset: std_offset };
109 };
110 let dst_offset = dst_info.offset().to_ioffset();
111 let diff = dst_offset.second - std_offset.second;
112 if diff == 0 {
127 debug_assert_eq!(std_offset, dst_offset);
128 IAmbiguousOffset::Unambiguous { offset: std_offset }
129 } else if diff.is_negative() {
130 if dst_info.in_dst(dt) {
134 IAmbiguousOffset::Unambiguous { offset: dst_offset }
135 } else {
136 let fold_start = dst_info.start.saturating_add_seconds(diff);
137 let gap_end =
138 dst_info.end.saturating_add_seconds(diff.saturating_neg());
139 if fold_start <= dt && dt < dst_info.start {
140 IAmbiguousOffset::Fold {
141 before: std_offset,
142 after: dst_offset,
143 }
144 } else if dst_info.end <= dt && dt < gap_end {
145 IAmbiguousOffset::Gap {
146 before: dst_offset,
147 after: std_offset,
148 }
149 } else {
150 IAmbiguousOffset::Unambiguous { offset: std_offset }
151 }
152 }
153 } else {
154 if !dst_info.in_dst(dt) {
158 IAmbiguousOffset::Unambiguous { offset: std_offset }
159 } else {
160 let gap_end = dst_info.start.saturating_add_seconds(diff);
165 let fold_start =
166 dst_info.end.saturating_add_seconds(diff.saturating_neg());
167 if dst_info.start <= dt && dt < gap_end {
168 IAmbiguousOffset::Gap {
169 before: std_offset,
170 after: dst_offset,
171 }
172 } else if fold_start <= dt && dt < dst_info.end {
173 IAmbiguousOffset::Fold {
174 before: dst_offset,
175 after: std_offset,
176 }
177 } else {
178 IAmbiguousOffset::Unambiguous { offset: dst_offset }
179 }
180 }
181 }
182 }
183
184 pub(crate) fn previous_transition(
187 &self,
188 timestamp: ITimestamp,
189 ) -> Option<(ITimestamp, IOffset, &'_ str, bool)> {
190 let dt = timestamp.to_datetime(IOffset::UTC);
191 let dst_info = self.dst_info_utc(dt.date.year)?;
192 let (earlier, later) = dst_info.ordered();
193 let (prev, dst_info) = if dt > later {
194 (later, dst_info)
195 } else if dt > earlier {
196 (earlier, dst_info)
197 } else {
198 let prev_year = dt.date.prev_year().ok()?;
199 let dst_info = self.dst_info_utc(prev_year)?;
200 let (_, later) = dst_info.ordered();
201 (later, dst_info)
202 };
203
204 let timestamp = prev.to_timestamp_checked(IOffset::UTC)?;
205 let dt = timestamp.to_datetime(IOffset::UTC);
206 let (offset, abbrev, dst) = if dst_info.in_dst(dt) {
207 (dst_info.offset(), dst_info.dst.abbrev.as_ref(), true)
208 } else {
209 (&self.std_offset, self.std_abbrev.as_ref(), false)
210 };
211 Some((timestamp, offset.to_ioffset(), abbrev, dst))
212 }
213
214 pub(crate) fn next_transition(
217 &self,
218 timestamp: ITimestamp,
219 ) -> Option<(ITimestamp, IOffset, &'_ str, bool)> {
220 let dt = timestamp.to_datetime(IOffset::UTC);
221 let dst_info = self.dst_info_utc(dt.date.year)?;
222 let (earlier, later) = dst_info.ordered();
223 let (next, dst_info) = if dt < earlier {
224 (earlier, dst_info)
225 } else if dt < later {
226 (later, dst_info)
227 } else {
228 let next_year = dt.date.next_year().ok()?;
229 let dst_info = self.dst_info_utc(next_year)?;
230 let (earlier, _) = dst_info.ordered();
231 (earlier, dst_info)
232 };
233
234 let timestamp = next.to_timestamp_checked(IOffset::UTC)?;
235 let dt = timestamp.to_datetime(IOffset::UTC);
236 let (offset, abbrev, dst) = if dst_info.in_dst(dt) {
237 (dst_info.offset(), dst_info.dst.abbrev.as_ref(), true)
238 } else {
239 (&self.std_offset, self.std_abbrev.as_ref(), false)
240 };
241 Some((timestamp, offset.to_ioffset(), abbrev, dst))
242 }
243
244 fn dst_info_utc(&self, year: i16) -> Option<DstInfo<'_, ABBREV>> {
249 let dst = self.dst.as_ref()?;
250 let start =
253 dst.rule.start.to_datetime(year, self.std_offset.to_ioffset());
254 let mut end = dst.rule.end.to_datetime(year, dst.offset.to_ioffset());
257 if start.date.month == 1
308 && start.date.day == 1
309 && start.time == ITime::MIN
310 && year
312 != end.saturating_add_seconds(self.std_offset.second).date.year
313 {
314 end = IDateTime {
315 date: IDate { year, month: 12, day: 31 },
316 time: ITime::MAX,
317 };
318 }
319 Some(DstInfo { dst, start, end })
320 }
321
322 fn dst_info_wall(&self, year: i16) -> Option<DstInfo<'_, ABBREV>> {
328 let dst = self.dst.as_ref()?;
329 let start = dst.rule.start.to_datetime(year, IOffset::UTC);
333 let end = dst.rule.end.to_datetime(year, IOffset::UTC);
334 Some(DstInfo { dst, start, end })
335 }
336
337 #[cfg(test)]
340 fn rule(&self) -> &PosixRule {
341 &self.dst.as_ref().unwrap().rule
342 }
343}
344
345impl<ABBREV: AsRef<str>> core::fmt::Display for PosixTimeZone<ABBREV> {
346 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
347 write!(
348 f,
349 "{}{}",
350 AbbreviationDisplay(self.std_abbrev.as_ref()),
351 self.std_offset
352 )?;
353 if let Some(ref dst) = self.dst {
354 dst.display(&self.std_offset, f)?;
355 }
356 Ok(())
357 }
358}
359
360impl<ABBREV: AsRef<str>> PosixDst<ABBREV> {
361 fn display(
362 &self,
363 std_offset: &PosixOffset,
364 f: &mut core::fmt::Formatter,
365 ) -> core::fmt::Result {
366 write!(f, "{}", AbbreviationDisplay(self.abbrev.as_ref()))?;
367 let default = PosixOffset { second: std_offset.second + 3600 };
371 if self.offset != default {
372 write!(f, "{}", self.offset)?;
373 }
374 write!(f, ",{}", self.rule)?;
375 Ok(())
376 }
377}
378
379impl core::fmt::Display for PosixRule {
380 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
381 write!(f, "{},{}", self.start, self.end)
382 }
383}
384
385impl PosixDayTime {
386 pub(crate) fn to_datetime(&self, year: i16, offset: IOffset) -> IDateTime {
398 let mkmin = || IDateTime {
399 date: IDate { year, month: 1, day: 1 },
400 time: ITime::MIN,
401 };
402 let mkmax = || IDateTime {
403 date: IDate { year, month: 12, day: 31 },
404 time: ITime::MAX,
405 };
406 let Some(date) = self.date.to_date(year) else { return mkmax() };
407 let offset = self.time.second - offset.second;
411 let days = offset.div_euclid(86400);
414 let second = offset.rem_euclid(86400);
415
416 let Ok(date) = date.checked_add_days(days) else {
417 return if offset < 0 { mkmin() } else { mkmax() };
418 };
419 if date.year < year {
420 mkmin()
421 } else if date.year > year {
422 mkmax()
423 } else {
424 let time = ITimeSecond { second }.to_time();
425 IDateTime { date, time }
426 }
427 }
428}
429
430impl core::fmt::Display for PosixDayTime {
431 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
432 write!(f, "{}", self.date)?;
433 if self.time != PosixTime::DEFAULT {
436 write!(f, "/{}", self.time)?;
437 }
438 Ok(())
439 }
440}
441
442impl PosixDay {
443 fn to_date(&self, year: i16) -> Option<IDate> {
450 match *self {
451 PosixDay::JulianOne(day) => {
452 Some(
456 IDate::from_day_of_year_no_leap(year, day)
457 .expect("Julian `J day` should be in bounds"),
458 )
459 }
460 PosixDay::JulianZero(day) => {
461 IDate::from_day_of_year(year, day + 1).ok()
471 }
472 PosixDay::WeekdayOfMonth { month, week, weekday } => {
473 let weekday = IWeekday::from_sunday_zero_offset(weekday);
474 let first = IDate { year, month, day: 1 };
475 let week = if week == 5 { -1 } else { week };
476 debug_assert!(week == -1 || (1..=4).contains(&week));
477 Some(
492 first
493 .nth_weekday_of_month(week, weekday)
494 .expect("nth weekday always exists"),
495 )
496 }
497 }
498 }
499}
500
501impl core::fmt::Display for PosixDay {
502 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
503 match *self {
504 PosixDay::JulianOne(n) => write!(f, "J{n}"),
505 PosixDay::JulianZero(n) => write!(f, "{n}"),
506 PosixDay::WeekdayOfMonth { month, week, weekday } => {
507 write!(f, "M{month}.{week}.{weekday}")
508 }
509 }
510 }
511}
512
513impl PosixTime {
514 const DEFAULT: PosixTime = PosixTime { second: 2 * 60 * 60 };
515}
516
517impl core::fmt::Display for PosixTime {
518 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
519 if self.second.is_negative() {
520 write!(f, "-")?;
521 }
524 let second = self.second.unsigned_abs();
525 let h = second / 3600;
526 let m = (second / 60) % 60;
527 let s = second % 60;
528 write!(f, "{h}")?;
529 if m != 0 || s != 0 {
530 write!(f, ":{m:02}")?;
531 if s != 0 {
532 write!(f, ":{s:02}")?;
533 }
534 }
535 Ok(())
536 }
537}
538
539impl PosixOffset {
540 fn to_ioffset(&self) -> IOffset {
541 IOffset { second: self.second }
542 }
543}
544
545impl core::fmt::Display for PosixOffset {
546 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
547 if self.second > 0 {
551 write!(f, "-")?;
552 }
553 let second = self.second.unsigned_abs();
554 let h = second / 3600;
555 let m = (second / 60) % 60;
556 let s = second % 60;
557 write!(f, "{h}")?;
558 if m != 0 || s != 0 {
559 write!(f, ":{m:02}")?;
560 if s != 0 {
561 write!(f, ":{s:02}")?;
562 }
563 }
564 Ok(())
565 }
566}
567
568#[derive(Debug)]
573struct AbbreviationDisplay<S>(S);
574
575impl<S: AsRef<str>> core::fmt::Display for AbbreviationDisplay<S> {
576 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
577 let s = self.0.as_ref();
578 if s.chars().any(|ch| ch == '+' || ch == '-') {
579 write!(f, "<{s}>")
580 } else {
581 write!(f, "{s}")
582 }
583 }
584}
585
586#[derive(Debug, Eq, PartialEq)]
589struct DstInfo<'a, ABBREV> {
590 dst: &'a PosixDst<ABBREV>,
592 start: IDateTime,
601 end: IDateTime,
610}
611
612impl<'a, ABBREV> DstInfo<'a, ABBREV> {
613 fn in_dst(&self, utc_dt: IDateTime) -> bool {
616 if self.start <= self.end {
617 self.start <= utc_dt && utc_dt < self.end
618 } else {
619 !(self.end <= utc_dt && utc_dt < self.start)
620 }
621 }
622
623 fn ordered(&self) -> (IDateTime, IDateTime) {
625 if self.start <= self.end {
626 (self.start, self.end)
627 } else {
628 (self.end, self.start)
629 }
630 }
631
632 fn offset(&self) -> &PosixOffset {
634 &self.dst.offset
635 }
636}
637
638#[derive(Debug)]
640struct Parser<'s> {
641 tz: &'s [u8],
643 pos: core::cell::Cell<usize>,
645 ianav3plus: bool,
659}
660
661impl<'s> Parser<'s> {
662 fn new<B: ?Sized + AsRef<[u8]>>(tz: &'s B) -> Parser<'s> {
665 Parser {
666 tz: tz.as_ref(),
667 pos: core::cell::Cell::new(0),
668 ianav3plus: false,
669 }
670 }
671
672 fn parse(&self) -> Result<PosixTimeZone<Abbreviation>, Error> {
676 let (time_zone, remaining) = self.parse_prefix()?;
677 if !remaining.is_empty() {
678 return Err(err!(
679 "expected entire TZ string to be a valid POSIX \
680 time zone, but found `{}` after what would otherwise \
681 be a valid POSIX TZ string",
682 Bytes(remaining),
683 ));
684 }
685 Ok(time_zone)
686 }
687
688 fn parse_prefix(
691 &self,
692 ) -> Result<(PosixTimeZone<Abbreviation>, &'s [u8]), Error> {
693 let time_zone = self.parse_posix_time_zone()?;
694 Ok((time_zone, self.remaining()))
695 }
696
697 fn parse_posix_time_zone(
702 &self,
703 ) -> Result<PosixTimeZone<Abbreviation>, Error> {
704 if self.is_done() {
705 return Err(err!(
706 "an empty string is not a valid POSIX time zone"
707 ));
708 }
709 let std_abbrev = self
710 .parse_abbreviation()
711 .map_err(|e| err!("failed to parse standard abbreviation: {e}"))?;
712 let std_offset = self
713 .parse_posix_offset()
714 .map_err(|e| err!("failed to parse standard offset: {e}"))?;
715 let mut dst = None;
716 if !self.is_done()
717 && (self.byte().is_ascii_alphabetic() || self.byte() == b'<')
718 {
719 dst = Some(self.parse_posix_dst(&std_offset)?);
720 }
721 Ok(PosixTimeZone { std_abbrev, std_offset, dst })
722 }
723
724 fn parse_posix_dst(
733 &self,
734 std_offset: &PosixOffset,
735 ) -> Result<PosixDst<Abbreviation>, Error> {
736 let abbrev = self
737 .parse_abbreviation()
738 .map_err(|e| err!("failed to parse DST abbreviation: {e}"))?;
739 if self.is_done() {
740 return Err(err!(
741 "found DST abbreviation `{abbrev}`, but no transition \
742 rule (this is technically allowed by POSIX, but has \
743 unspecified behavior)",
744 ));
745 }
746 let mut offset = PosixOffset { second: std_offset.second + 3600 };
750 if self.byte() != b',' {
751 offset = self
752 .parse_posix_offset()
753 .map_err(|e| err!("failed to parse DST offset: {e}"))?;
754 if self.is_done() {
755 return Err(err!(
756 "found DST abbreviation `{abbrev}` and offset \
757 `{offset}s`, but no transition rule (this is \
758 technically allowed by POSIX, but has \
759 unspecified behavior)",
760 offset = offset.second,
761 ));
762 }
763 }
764 if self.byte() != b',' {
765 return Err(err!(
766 "after parsing DST offset in POSIX time zone string, \
767 found `{}` but expected a ','",
768 Byte(self.byte()),
769 ));
770 }
771 if !self.bump() {
772 return Err(err!(
773 "after parsing DST offset in POSIX time zone string, \
774 found end of string after a trailing ','",
775 ));
776 }
777 let rule = self.parse_rule()?;
778 Ok(PosixDst { abbrev, offset, rule })
779 }
780
781 fn parse_abbreviation(&self) -> Result<Abbreviation, Error> {
794 if self.byte() == b'<' {
795 if !self.bump() {
796 return Err(err!(
797 "found opening '<' quote for abbreviation in \
798 POSIX time zone string, and expected a name \
799 following it, but found the end of string instead"
800 ));
801 }
802 self.parse_quoted_abbreviation()
803 } else {
804 self.parse_unquoted_abbreviation()
805 }
806 }
807
808 fn parse_unquoted_abbreviation(&self) -> Result<Abbreviation, Error> {
820 let start = self.pos();
821 for i in 0.. {
822 if !self.byte().is_ascii_alphabetic() {
823 break;
824 }
825 if i >= Abbreviation::capacity() {
826 return Err(err!(
827 "expected abbreviation with at most {} bytes, \
828 but found a longer abbreviation beginning with `{}`",
829 Abbreviation::capacity(),
830 Bytes(&self.tz[start..][..i]),
831 ));
832 }
833 if !self.bump() {
834 break;
835 }
836 }
837 let end = self.pos();
838 let abbrev =
839 core::str::from_utf8(&self.tz[start..end]).map_err(|_| {
840 err!(
847 "found abbreviation `{}`, but it is not valid UTF-8",
848 Bytes(&self.tz[start..end]),
849 )
850 })?;
851 if abbrev.len() < 3 {
852 return Err(err!(
853 "expected abbreviation with 3 or more bytes, but found \
854 abbreviation {:?} with {} bytes",
855 abbrev,
856 abbrev.len(),
857 ));
858 }
859 Ok(Abbreviation::new(abbrev).unwrap())
862 }
863
864 fn parse_quoted_abbreviation(&self) -> Result<Abbreviation, Error> {
876 let start = self.pos();
877 for i in 0.. {
878 if !self.byte().is_ascii_alphanumeric()
879 && self.byte() != b'+'
880 && self.byte() != b'-'
881 {
882 break;
883 }
884 if i >= Abbreviation::capacity() {
885 return Err(err!(
886 "expected abbreviation with at most {} bytes, \
887 but found a longer abbreviation beginning with `{}`",
888 Abbreviation::capacity(),
889 Bytes(&self.tz[start..][..i]),
890 ));
891 }
892 if !self.bump() {
893 break;
894 }
895 }
896 let end = self.pos();
897 let abbrev =
898 core::str::from_utf8(&self.tz[start..end]).map_err(|_| {
899 err!(
906 "found abbreviation `{}`, but it is not valid UTF-8",
907 Bytes(&self.tz[start..end]),
908 )
909 })?;
910 if self.is_done() {
911 return Err(err!(
912 "found non-empty quoted abbreviation {abbrev:?}, but \
913 did not find expected end-of-quoted abbreviation \
914 '>' character",
915 ));
916 }
917 if self.byte() != b'>' {
918 return Err(err!(
919 "found non-empty quoted abbreviation {abbrev:?}, but \
920 found `{}` instead of end-of-quoted abbreviation '>' \
921 character",
922 Byte(self.byte()),
923 ));
924 }
925 self.bump();
926 if abbrev.len() < 3 {
927 return Err(err!(
928 "expected abbreviation with 3 or more bytes, but found \
929 abbreviation {abbrev:?} with {} bytes",
930 abbrev.len(),
931 ));
932 }
933 Ok(Abbreviation::new(abbrev).unwrap())
936 }
937
938 fn parse_posix_offset(&self) -> Result<PosixOffset, Error> {
947 let sign = self
948 .parse_optional_sign()
949 .map_err(|e| {
950 err!(
951 "failed to parse sign for time offset \
952 in POSIX time zone string: {e}",
953 )
954 })?
955 .unwrap_or(1);
956 let hour = self.parse_hour_posix()?;
957 let (mut minute, mut second) = (0, 0);
958 if self.maybe_byte() == Some(b':') {
959 if !self.bump() {
960 return Err(err!(
961 "incomplete time in POSIX timezone (missing minutes)",
962 ));
963 }
964 minute = self.parse_minute()?;
965 if self.maybe_byte() == Some(b':') {
966 if !self.bump() {
967 return Err(err!(
968 "incomplete time in POSIX timezone (missing seconds)",
969 ));
970 }
971 second = self.parse_second()?;
972 }
973 }
974 let mut offset = PosixOffset { second: i32::from(hour) * 3600 };
975 offset.second += i32::from(minute) * 60;
976 offset.second += i32::from(second);
977 offset.second *= i32::from(-sign);
980 assert!(
984 -89999 <= offset.second && offset.second <= 89999,
985 "POSIX offset seconds {} is out of range",
986 offset.second
987 );
988 Ok(offset)
989 }
990
991 fn parse_rule(&self) -> Result<PosixRule, Error> {
1001 let start = self.parse_posix_datetime().map_err(|e| {
1002 err!("failed to parse start of DST transition rule: {e}")
1003 })?;
1004 if self.maybe_byte() != Some(b',') || !self.bump() {
1005 return Err(err!(
1006 "expected end of DST rule after parsing the start \
1007 of the DST rule"
1008 ));
1009 }
1010 let end = self.parse_posix_datetime().map_err(|e| {
1011 err!("failed to parse end of DST transition rule: {e}")
1012 })?;
1013 Ok(PosixRule { start, end })
1014 }
1015
1016 fn parse_posix_datetime(&self) -> Result<PosixDayTime, Error> {
1025 let mut daytime = PosixDayTime {
1026 date: self.parse_posix_date()?,
1027 time: PosixTime::DEFAULT,
1028 };
1029 if self.maybe_byte() != Some(b'/') {
1030 return Ok(daytime);
1031 }
1032 if !self.bump() {
1033 return Err(err!(
1034 "expected time specification after '/' following a date
1035 specification in a POSIX time zone DST transition rule",
1036 ));
1037 }
1038 daytime.time = self.parse_posix_time()?;
1039 Ok(daytime)
1040 }
1041
1042 fn parse_posix_date(&self) -> Result<PosixDay, Error> {
1056 match self.byte() {
1057 b'J' => {
1058 if !self.bump() {
1059 return Err(err!(
1060 "expected one-based Julian day after 'J' in date \
1061 specification of a POSIX time zone DST \
1062 transition rule, but got the end of the string \
1063 instead"
1064 ));
1065 }
1066 Ok(PosixDay::JulianOne(self.parse_posix_julian_day_no_leap()?))
1067 }
1068 b'0'..=b'9' => Ok(PosixDay::JulianZero(
1069 self.parse_posix_julian_day_with_leap()?,
1070 )),
1071 b'M' => {
1072 if !self.bump() {
1073 return Err(err!(
1074 "expected month-week-weekday after 'M' in date \
1075 specification of a POSIX time zone DST \
1076 transition rule, but got the end of the string \
1077 instead"
1078 ));
1079 }
1080 let (month, week, weekday) = self.parse_weekday_of_month()?;
1081 Ok(PosixDay::WeekdayOfMonth { month, week, weekday })
1082 }
1083 _ => Err(err!(
1084 "expected 'J', a digit or 'M' at the beginning of a date \
1085 specification of a POSIX time zone DST transition rule, \
1086 but got `{}` instead",
1087 Byte(self.byte()),
1088 )),
1089 }
1090 }
1091
1092 fn parse_posix_julian_day_no_leap(&self) -> Result<i16, Error> {
1099 let number = self
1100 .parse_number_with_upto_n_digits(3)
1101 .map_err(|e| err!("invalid one based Julian day: {e}"))?;
1102 let number = i16::try_from(number).map_err(|_| {
1103 err!(
1104 "one based Julian day `{number}` in POSIX time zone \
1105 does not fit into 16-bit integer"
1106 )
1107 })?;
1108 if !(1 <= number && number <= 365) {
1109 return Err(err!(
1110 "parsed one based Julian day `{number}`, \
1111 but one based Julian day in POSIX time zone \
1112 must be in range 1..=365",
1113 ));
1114 }
1115 Ok(number)
1116 }
1117
1118 fn parse_posix_julian_day_with_leap(&self) -> Result<i16, Error> {
1125 let number = self
1126 .parse_number_with_upto_n_digits(3)
1127 .map_err(|e| err!("invalid zero based Julian day: {e}"))?;
1128 let number = i16::try_from(number).map_err(|_| {
1129 err!(
1130 "zero based Julian day `{number}` in POSIX time zone \
1131 does not fit into 16-bit integer"
1132 )
1133 })?;
1134 if !(0 <= number && number <= 365) {
1135 return Err(err!(
1136 "parsed zero based Julian day `{number}`, \
1137 but zero based Julian day in POSIX time zone \
1138 must be in range 0..=365",
1139 ));
1140 }
1141 Ok(number)
1142 }
1143
1144 fn parse_weekday_of_month(&self) -> Result<(i8, i8, i8), Error> {
1154 let month = self.parse_month()?;
1155 if self.maybe_byte() != Some(b'.') {
1156 return Err(err!(
1157 "expected '.' after month `{month}` in \
1158 POSIX time zone rule"
1159 ));
1160 }
1161 if !self.bump() {
1162 return Err(err!(
1163 "expected week after month `{month}` in \
1164 POSIX time zone rule"
1165 ));
1166 }
1167 let week = self.parse_week()?;
1168 if self.maybe_byte() != Some(b'.') {
1169 return Err(err!(
1170 "expected '.' after week `{week}` in POSIX time zone rule"
1171 ));
1172 }
1173 if !self.bump() {
1174 return Err(err!(
1175 "expected day-of-week after week `{week}` in \
1176 POSIX time zone rule"
1177 ));
1178 }
1179 let weekday = self.parse_weekday()?;
1180 Ok((month, week, weekday))
1181 }
1182
1183 fn parse_posix_time(&self) -> Result<PosixTime, Error> {
1190 let (sign, hour) = if self.ianav3plus {
1191 let sign = self
1192 .parse_optional_sign()
1193 .map_err(|e| {
1194 err!(
1195 "failed to parse sign for transition time \
1196 in POSIX time zone string: {e}",
1197 )
1198 })?
1199 .unwrap_or(1);
1200 let hour = self.parse_hour_ianav3plus()?;
1201 (sign, hour)
1202 } else {
1203 (1, i16::from(self.parse_hour_posix()?))
1204 };
1205 let (mut minute, mut second) = (0, 0);
1206 if self.maybe_byte() == Some(b':') {
1207 if !self.bump() {
1208 return Err(err!(
1209 "incomplete transition time in \
1210 POSIX time zone string (missing minutes)",
1211 ));
1212 }
1213 minute = self.parse_minute()?;
1214 if self.maybe_byte() == Some(b':') {
1215 if !self.bump() {
1216 return Err(err!(
1217 "incomplete transition time in \
1218 POSIX time zone string (missing seconds)",
1219 ));
1220 }
1221 second = self.parse_second()?;
1222 }
1223 }
1224 let mut time = PosixTime { second: i32::from(hour) * 3600 };
1225 time.second += i32::from(minute) * 60;
1226 time.second += i32::from(second);
1227 time.second *= i32::from(sign);
1228 assert!(
1232 -604799 <= time.second && time.second <= 604799,
1233 "POSIX time seconds {} is out of range",
1234 time.second
1235 );
1236 Ok(time)
1237 }
1238
1239 fn parse_month(&self) -> Result<i8, Error> {
1245 let number = self.parse_number_with_upto_n_digits(2)?;
1246 let number = i8::try_from(number).map_err(|_| {
1247 err!(
1248 "month `{number}` in POSIX time zone \
1249 does not fit into 8-bit integer"
1250 )
1251 })?;
1252 if !(1 <= number && number <= 12) {
1253 return Err(err!(
1254 "parsed month `{number}`, but month in \
1255 POSIX time zone must be in range 1..=12",
1256 ));
1257 }
1258 Ok(number)
1259 }
1260
1261 fn parse_week(&self) -> Result<i8, Error> {
1266 let number = self.parse_number_with_exactly_n_digits(1)?;
1267 let number = i8::try_from(number).map_err(|_| {
1268 err!(
1269 "week `{number}` in POSIX time zone \
1270 does not fit into 8-bit integer"
1271 )
1272 })?;
1273 if !(1 <= number && number <= 5) {
1274 return Err(err!(
1275 "parsed week `{number}`, but week in \
1276 POSIX time zone must be in range 1..=5"
1277 ));
1278 }
1279 Ok(number)
1280 }
1281
1282 fn parse_weekday(&self) -> Result<i8, Error> {
1290 let number = self.parse_number_with_exactly_n_digits(1)?;
1291 let number = i8::try_from(number).map_err(|_| {
1292 err!(
1293 "weekday `{number}` in POSIX time zone \
1294 does not fit into 8-bit integer"
1295 )
1296 })?;
1297 if !(0 <= number && number <= 6) {
1298 return Err(err!(
1299 "parsed weekday `{number}`, but weekday in \
1300 POSIX time zone must be in range `0..=6` \
1301 (with `0` corresponding to Sunday)",
1302 ));
1303 }
1304 Ok(number)
1305 }
1306
1307 fn parse_hour_ianav3plus(&self) -> Result<i16, Error> {
1319 assert!(self.ianav3plus);
1322 let number = self
1323 .parse_number_with_upto_n_digits(3)
1324 .map_err(|e| err!("invalid hour digits: {e}"))?;
1325 let number = i16::try_from(number).map_err(|_| {
1326 err!(
1327 "hour `{number}` in POSIX time zone \
1328 does not fit into 16-bit integer"
1329 )
1330 })?;
1331 if !(0 <= number && number <= 167) {
1332 return Err(err!(
1336 "parsed hour `{number}`, but hour in IANA v3+ \
1337 POSIX time zone must be in range `-167..=167`",
1338 ));
1339 }
1340 Ok(number)
1341 }
1342
1343 fn parse_hour_posix(&self) -> Result<i8, Error> {
1353 let number = self
1354 .parse_number_with_upto_n_digits(2)
1355 .map_err(|e| err!("invalid hour digits: {e}"))?;
1356 let number = i8::try_from(number).map_err(|_| {
1357 err!(
1358 "hour `{number}` in POSIX time zone \
1359 does not fit into 8-bit integer"
1360 )
1361 })?;
1362 if !(0 <= number && number <= 24) {
1363 return Err(err!(
1364 "parsed hour `{number}`, but hour in \
1365 POSIX time zone must be in range `0..=24`",
1366 ));
1367 }
1368 Ok(number)
1369 }
1370
1371 fn parse_minute(&self) -> Result<i8, Error> {
1379 let number = self
1380 .parse_number_with_exactly_n_digits(2)
1381 .map_err(|e| err!("invalid minute digits: {e}"))?;
1382 let number = i8::try_from(number).map_err(|_| {
1383 err!(
1384 "minute `{number}` in POSIX time zone \
1385 does not fit into 8-bit integer"
1386 )
1387 })?;
1388 if !(0 <= number && number <= 59) {
1389 return Err(err!(
1390 "parsed minute `{number}`, but minute in \
1391 POSIX time zone must be in range `0..=59`",
1392 ));
1393 }
1394 Ok(number)
1395 }
1396
1397 fn parse_second(&self) -> Result<i8, Error> {
1405 let number = self
1406 .parse_number_with_exactly_n_digits(2)
1407 .map_err(|e| err!("invalid second digits: {e}"))?;
1408 let number = i8::try_from(number).map_err(|_| {
1409 err!(
1410 "second `{number}` in POSIX time zone \
1411 does not fit into 8-bit integer"
1412 )
1413 })?;
1414 if !(0 <= number && number <= 59) {
1415 return Err(err!(
1416 "parsed second `{number}`, but second in \
1417 POSIX time zone must be in range `0..=59`",
1418 ));
1419 }
1420 Ok(number)
1421 }
1422
1423 fn parse_number_with_exactly_n_digits(
1432 &self,
1433 n: usize,
1434 ) -> Result<i32, Error> {
1435 assert!(n >= 1, "numbers must have at least 1 digit");
1436 let start = self.pos();
1437 let mut number: i32 = 0;
1438 for i in 0..n {
1439 if self.is_done() {
1440 return Err(err!("expected {n} digits, but found {i}"));
1441 }
1442 let byte = self.byte();
1443 let digit = match byte.checked_sub(b'0') {
1444 None => {
1445 return Err(err!(
1446 "invalid digit, expected 0-9 but got {}",
1447 Byte(byte),
1448 ));
1449 }
1450 Some(digit) if digit > 9 => {
1451 return Err(err!(
1452 "invalid digit, expected 0-9 but got {}",
1453 Byte(byte),
1454 ))
1455 }
1456 Some(digit) => {
1457 debug_assert!((0..=9).contains(&digit));
1458 i32::from(digit)
1459 }
1460 };
1461 number = number
1462 .checked_mul(10)
1463 .and_then(|n| n.checked_add(digit))
1464 .ok_or_else(|| {
1465 err!(
1466 "number `{}` too big to parse into 64-bit integer",
1467 Bytes(&self.tz[start..][..i]),
1468 )
1469 })?;
1470 self.bump();
1471 }
1472 Ok(number)
1473 }
1474
1475 fn parse_number_with_upto_n_digits(&self, n: usize) -> Result<i32, Error> {
1482 assert!(n >= 1, "numbers must have at least 1 digit");
1483 let start = self.pos();
1484 let mut number: i32 = 0;
1485 for i in 0..n {
1486 if self.is_done() || !self.byte().is_ascii_digit() {
1487 if i == 0 {
1488 return Err(err!("invalid number, no digits found"));
1489 }
1490 break;
1491 }
1492 let digit = i32::from(self.byte() - b'0');
1493 number = number
1494 .checked_mul(10)
1495 .and_then(|n| n.checked_add(digit))
1496 .ok_or_else(|| {
1497 err!(
1498 "number `{}` too big to parse into 64-bit integer",
1499 Bytes(&self.tz[start..][..i]),
1500 )
1501 })?;
1502 self.bump();
1503 }
1504 Ok(number)
1505 }
1506
1507 fn parse_optional_sign(&self) -> Result<Option<i8>, Error> {
1515 if self.is_done() {
1516 return Ok(None);
1517 }
1518 Ok(match self.byte() {
1519 b'-' => {
1520 if !self.bump() {
1521 return Err(err!(
1522 "expected digit after '-' sign, \
1523 but got end of input",
1524 ));
1525 }
1526 Some(-1)
1527 }
1528 b'+' => {
1529 if !self.bump() {
1530 return Err(err!(
1531 "expected digit after '+' sign, \
1532 but got end of input",
1533 ));
1534 }
1535 Some(1)
1536 }
1537 _ => None,
1538 })
1539 }
1540}
1541
1542impl<'s> Parser<'s> {
1544 fn bump(&self) -> bool {
1548 if self.is_done() {
1549 return false;
1550 }
1551 self.pos.set(
1552 self.pos().checked_add(1).expect("pos cannot overflow usize"),
1553 );
1554 !self.is_done()
1555 }
1556
1557 fn is_done(&self) -> bool {
1559 self.pos() == self.tz.len()
1560 }
1561
1562 fn byte(&self) -> u8 {
1567 self.tz[self.pos()]
1568 }
1569
1570 fn maybe_byte(&self) -> Option<u8> {
1573 self.tz.get(self.pos()).copied()
1574 }
1575
1576 fn pos(&self) -> usize {
1580 self.pos.get()
1581 }
1582
1583 fn remaining(&self) -> &'s [u8] {
1587 &self.tz[self.pos()..]
1588 }
1589}
1590
1591#[cfg(feature = "alloc")]
1593#[cfg(test)]
1594mod tests {
1595 use alloc::string::ToString;
1596
1597 use super::*;
1598
1599 fn posix_time_zone(
1600 input: impl AsRef<[u8]>,
1601 ) -> PosixTimeZone<Abbreviation> {
1602 let input = input.as_ref();
1603 let tz = PosixTimeZone::parse(input).unwrap();
1604 let reparsed =
1616 PosixTimeZone::parse(tz.to_string().as_bytes()).unwrap();
1617 assert_eq!(tz, reparsed);
1618 assert_eq!(tz.to_string(), reparsed.to_string());
1619 tz
1620 }
1621
1622 fn parser(s: &str) -> Parser<'_> {
1623 Parser::new(s.as_bytes())
1624 }
1625
1626 fn date(year: i16, month: i8, day: i8) -> IDate {
1627 IDate { year, month, day }
1628 }
1629
1630 #[test]
1631 fn parse() {
1632 let p = parser("NZST-12NZDT,J60,J300");
1633 assert_eq!(
1634 p.parse().unwrap(),
1635 PosixTimeZone {
1636 std_abbrev: "NZST".into(),
1637 std_offset: PosixOffset { second: 12 * 60 * 60 },
1638 dst: Some(PosixDst {
1639 abbrev: "NZDT".into(),
1640 offset: PosixOffset { second: 13 * 60 * 60 },
1641 rule: PosixRule {
1642 start: PosixDayTime {
1643 date: PosixDay::JulianOne(60),
1644 time: PosixTime { second: 2 * 60 * 60 },
1645 },
1646 end: PosixDayTime {
1647 date: PosixDay::JulianOne(300),
1648 time: PosixTime { second: 2 * 60 * 60 },
1649 },
1650 },
1651 }),
1652 },
1653 );
1654
1655 let p = Parser::new("NZST-12NZDT,J60,J300WAT");
1656 assert!(p.parse().is_err());
1657 }
1658
1659 #[test]
1660 fn parse_posix_time_zone() {
1661 let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3");
1662 assert_eq!(
1663 p.parse_posix_time_zone().unwrap(),
1664 PosixTimeZone {
1665 std_abbrev: "NZST".into(),
1666 std_offset: PosixOffset { second: 12 * 60 * 60 },
1667 dst: Some(PosixDst {
1668 abbrev: "NZDT".into(),
1669 offset: PosixOffset { second: 13 * 60 * 60 },
1670 rule: PosixRule {
1671 start: PosixDayTime {
1672 date: PosixDay::WeekdayOfMonth {
1673 month: 9,
1674 week: 5,
1675 weekday: 0,
1676 },
1677 time: PosixTime { second: 2 * 60 * 60 },
1678 },
1679 end: PosixDayTime {
1680 date: PosixDay::WeekdayOfMonth {
1681 month: 4,
1682 week: 1,
1683 weekday: 0,
1684 },
1685 time: PosixTime { second: 3 * 60 * 60 },
1686 },
1687 },
1688 }),
1689 },
1690 );
1691
1692 let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3WAT");
1693 assert_eq!(
1694 p.parse_posix_time_zone().unwrap(),
1695 PosixTimeZone {
1696 std_abbrev: "NZST".into(),
1697 std_offset: PosixOffset { second: 12 * 60 * 60 },
1698 dst: Some(PosixDst {
1699 abbrev: "NZDT".into(),
1700 offset: PosixOffset { second: 13 * 60 * 60 },
1701 rule: PosixRule {
1702 start: PosixDayTime {
1703 date: PosixDay::WeekdayOfMonth {
1704 month: 9,
1705 week: 5,
1706 weekday: 0,
1707 },
1708 time: PosixTime { second: 2 * 60 * 60 },
1709 },
1710 end: PosixDayTime {
1711 date: PosixDay::WeekdayOfMonth {
1712 month: 4,
1713 week: 1,
1714 weekday: 0,
1715 },
1716 time: PosixTime { second: 3 * 60 * 60 },
1717 },
1718 },
1719 }),
1720 },
1721 );
1722
1723 let p = Parser::new("NZST-12NZDT,J60,J300");
1724 assert_eq!(
1725 p.parse_posix_time_zone().unwrap(),
1726 PosixTimeZone {
1727 std_abbrev: "NZST".into(),
1728 std_offset: PosixOffset { second: 12 * 60 * 60 },
1729 dst: Some(PosixDst {
1730 abbrev: "NZDT".into(),
1731 offset: PosixOffset { second: 13 * 60 * 60 },
1732 rule: PosixRule {
1733 start: PosixDayTime {
1734 date: PosixDay::JulianOne(60),
1735 time: PosixTime { second: 2 * 60 * 60 },
1736 },
1737 end: PosixDayTime {
1738 date: PosixDay::JulianOne(300),
1739 time: PosixTime { second: 2 * 60 * 60 },
1740 },
1741 },
1742 }),
1743 },
1744 );
1745
1746 let p = Parser::new("NZST-12NZDT,J60,J300WAT");
1747 assert_eq!(
1748 p.parse_posix_time_zone().unwrap(),
1749 PosixTimeZone {
1750 std_abbrev: "NZST".into(),
1751 std_offset: PosixOffset { second: 12 * 60 * 60 },
1752 dst: Some(PosixDst {
1753 abbrev: "NZDT".into(),
1754 offset: PosixOffset { second: 13 * 60 * 60 },
1755 rule: PosixRule {
1756 start: PosixDayTime {
1757 date: PosixDay::JulianOne(60),
1758 time: PosixTime { second: 2 * 60 * 60 },
1759 },
1760 end: PosixDayTime {
1761 date: PosixDay::JulianOne(300),
1762 time: PosixTime { second: 2 * 60 * 60 },
1763 },
1764 },
1765 }),
1766 },
1767 );
1768 }
1769
1770 #[test]
1771 fn parse_posix_dst() {
1772 let p = Parser::new("NZDT,M9.5.0,M4.1.0/3");
1773 assert_eq!(
1774 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
1775 PosixDst {
1776 abbrev: "NZDT".into(),
1777 offset: PosixOffset { second: 13 * 60 * 60 },
1778 rule: PosixRule {
1779 start: PosixDayTime {
1780 date: PosixDay::WeekdayOfMonth {
1781 month: 9,
1782 week: 5,
1783 weekday: 0,
1784 },
1785 time: PosixTime { second: 2 * 60 * 60 },
1786 },
1787 end: PosixDayTime {
1788 date: PosixDay::WeekdayOfMonth {
1789 month: 4,
1790 week: 1,
1791 weekday: 0,
1792 },
1793 time: PosixTime { second: 3 * 60 * 60 },
1794 },
1795 },
1796 },
1797 );
1798
1799 let p = Parser::new("NZDT,J60,J300");
1800 assert_eq!(
1801 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
1802 PosixDst {
1803 abbrev: "NZDT".into(),
1804 offset: PosixOffset { second: 13 * 60 * 60 },
1805 rule: PosixRule {
1806 start: PosixDayTime {
1807 date: PosixDay::JulianOne(60),
1808 time: PosixTime { second: 2 * 60 * 60 },
1809 },
1810 end: PosixDayTime {
1811 date: PosixDay::JulianOne(300),
1812 time: PosixTime { second: 2 * 60 * 60 },
1813 },
1814 },
1815 },
1816 );
1817
1818 let p = Parser::new("NZDT-7,J60,J300");
1819 assert_eq!(
1820 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
1821 PosixDst {
1822 abbrev: "NZDT".into(),
1823 offset: PosixOffset { second: 7 * 60 * 60 },
1824 rule: PosixRule {
1825 start: PosixDayTime {
1826 date: PosixDay::JulianOne(60),
1827 time: PosixTime { second: 2 * 60 * 60 },
1828 },
1829 end: PosixDayTime {
1830 date: PosixDay::JulianOne(300),
1831 time: PosixTime { second: 2 * 60 * 60 },
1832 },
1833 },
1834 },
1835 );
1836
1837 let p = Parser::new("NZDT+7,J60,J300");
1838 assert_eq!(
1839 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
1840 PosixDst {
1841 abbrev: "NZDT".into(),
1842 offset: PosixOffset { second: -7 * 60 * 60 },
1843 rule: PosixRule {
1844 start: PosixDayTime {
1845 date: PosixDay::JulianOne(60),
1846 time: PosixTime { second: 2 * 60 * 60 },
1847 },
1848 end: PosixDayTime {
1849 date: PosixDay::JulianOne(300),
1850 time: PosixTime { second: 2 * 60 * 60 },
1851 },
1852 },
1853 },
1854 );
1855
1856 let p = Parser::new("NZDT7,J60,J300");
1857 assert_eq!(
1858 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
1859 PosixDst {
1860 abbrev: "NZDT".into(),
1861 offset: PosixOffset { second: -7 * 60 * 60 },
1862 rule: PosixRule {
1863 start: PosixDayTime {
1864 date: PosixDay::JulianOne(60),
1865 time: PosixTime { second: 2 * 60 * 60 },
1866 },
1867 end: PosixDayTime {
1868 date: PosixDay::JulianOne(300),
1869 time: PosixTime { second: 2 * 60 * 60 },
1870 },
1871 },
1872 },
1873 );
1874
1875 let p = Parser::new("NZDT7,");
1876 assert!(p
1877 .parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 })
1878 .is_err());
1879
1880 let p = Parser::new("NZDT7!");
1881 assert!(p
1882 .parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 })
1883 .is_err());
1884 }
1885
1886 #[test]
1887 fn parse_abbreviation() {
1888 let p = Parser::new("ABC");
1889 assert_eq!(p.parse_abbreviation().unwrap(), "ABC");
1890
1891 let p = Parser::new("<ABC>");
1892 assert_eq!(p.parse_abbreviation().unwrap(), "ABC");
1893
1894 let p = Parser::new("<+09>");
1895 assert_eq!(p.parse_abbreviation().unwrap(), "+09");
1896
1897 let p = Parser::new("+09");
1898 assert!(p.parse_abbreviation().is_err());
1899 }
1900
1901 #[test]
1902 fn parse_unquoted_abbreviation() {
1903 let p = Parser::new("ABC");
1904 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC");
1905
1906 let p = Parser::new("ABCXYZ");
1907 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABCXYZ");
1908
1909 let p = Parser::new("ABC123");
1910 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC");
1911
1912 let tz = "a".repeat(30);
1913 let p = Parser::new(&tz);
1914 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), &*tz);
1915
1916 let p = Parser::new("a");
1917 assert!(p.parse_unquoted_abbreviation().is_err());
1918
1919 let p = Parser::new("ab");
1920 assert!(p.parse_unquoted_abbreviation().is_err());
1921
1922 let p = Parser::new("ab1");
1923 assert!(p.parse_unquoted_abbreviation().is_err());
1924
1925 let tz = "a".repeat(31);
1926 let p = Parser::new(&tz);
1927 assert!(p.parse_unquoted_abbreviation().is_err());
1928
1929 let p = Parser::new(b"ab\xFFcd");
1930 assert!(p.parse_unquoted_abbreviation().is_err());
1931 }
1932
1933 #[test]
1934 fn parse_quoted_abbreviation() {
1935 let p = Parser::new("ABC>");
1940 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC");
1941
1942 let p = Parser::new("ABCXYZ>");
1943 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABCXYZ");
1944
1945 let p = Parser::new("ABC>123");
1946 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC");
1947
1948 let p = Parser::new("ABC123>");
1949 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC123");
1950
1951 let p = Parser::new("ab1>");
1952 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ab1");
1953
1954 let p = Parser::new("+09>");
1955 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "+09");
1956
1957 let p = Parser::new("-09>");
1958 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "-09");
1959
1960 let tz = alloc::format!("{}>", "a".repeat(30));
1961 let p = Parser::new(&tz);
1962 assert_eq!(
1963 p.parse_quoted_abbreviation().unwrap(),
1964 tz.trim_end_matches(">")
1965 );
1966
1967 let p = Parser::new("a>");
1968 assert!(p.parse_quoted_abbreviation().is_err());
1969
1970 let p = Parser::new("ab>");
1971 assert!(p.parse_quoted_abbreviation().is_err());
1972
1973 let tz = alloc::format!("{}>", "a".repeat(31));
1974 let p = Parser::new(&tz);
1975 assert!(p.parse_quoted_abbreviation().is_err());
1976
1977 let p = Parser::new(b"ab\xFFcd>");
1978 assert!(p.parse_quoted_abbreviation().is_err());
1979
1980 let p = Parser::new("ABC");
1981 assert!(p.parse_quoted_abbreviation().is_err());
1982
1983 let p = Parser::new("ABC!>");
1984 assert!(p.parse_quoted_abbreviation().is_err());
1985 }
1986
1987 #[test]
1988 fn parse_posix_offset() {
1989 let p = Parser::new("5");
1990 assert_eq!(p.parse_posix_offset().unwrap().second, -5 * 60 * 60);
1991
1992 let p = Parser::new("+5");
1993 assert_eq!(p.parse_posix_offset().unwrap().second, -5 * 60 * 60);
1994
1995 let p = Parser::new("-5");
1996 assert_eq!(p.parse_posix_offset().unwrap().second, 5 * 60 * 60);
1997
1998 let p = Parser::new("-12:34:56");
1999 assert_eq!(
2000 p.parse_posix_offset().unwrap().second,
2001 12 * 60 * 60 + 34 * 60 + 56,
2002 );
2003
2004 let p = Parser::new("a");
2005 assert!(p.parse_posix_offset().is_err());
2006
2007 let p = Parser::new("-");
2008 assert!(p.parse_posix_offset().is_err());
2009
2010 let p = Parser::new("+");
2011 assert!(p.parse_posix_offset().is_err());
2012
2013 let p = Parser::new("-a");
2014 assert!(p.parse_posix_offset().is_err());
2015
2016 let p = Parser::new("+a");
2017 assert!(p.parse_posix_offset().is_err());
2018
2019 let p = Parser::new("-25");
2020 assert!(p.parse_posix_offset().is_err());
2021
2022 let p = Parser::new("+25");
2023 assert!(p.parse_posix_offset().is_err());
2024
2025 let p = Parser { ianav3plus: true, ..Parser::new("25") };
2033 assert!(p.parse_posix_offset().is_err());
2034 let p = Parser { ianav3plus: true, ..Parser::new("+25") };
2035 assert!(p.parse_posix_offset().is_err());
2036 let p = Parser { ianav3plus: true, ..Parser::new("-25") };
2037 assert!(p.parse_posix_offset().is_err());
2038 }
2039
2040 #[test]
2041 fn parse_rule() {
2042 let p = Parser::new("M9.5.0,M4.1.0/3");
2043 assert_eq!(
2044 p.parse_rule().unwrap(),
2045 PosixRule {
2046 start: PosixDayTime {
2047 date: PosixDay::WeekdayOfMonth {
2048 month: 9,
2049 week: 5,
2050 weekday: 0,
2051 },
2052 time: PosixTime { second: 2 * 60 * 60 },
2053 },
2054 end: PosixDayTime {
2055 date: PosixDay::WeekdayOfMonth {
2056 month: 4,
2057 week: 1,
2058 weekday: 0,
2059 },
2060 time: PosixTime { second: 3 * 60 * 60 },
2061 },
2062 },
2063 );
2064
2065 let p = Parser::new("M9.5.0");
2066 assert!(p.parse_rule().is_err());
2067
2068 let p = Parser::new(",M9.5.0,M4.1.0/3");
2069 assert!(p.parse_rule().is_err());
2070
2071 let p = Parser::new("M9.5.0/");
2072 assert!(p.parse_rule().is_err());
2073
2074 let p = Parser::new("M9.5.0,M4.1.0/");
2075 assert!(p.parse_rule().is_err());
2076 }
2077
2078 #[test]
2079 fn parse_posix_datetime() {
2080 let p = Parser::new("J1");
2081 assert_eq!(
2082 p.parse_posix_datetime().unwrap(),
2083 PosixDayTime {
2084 date: PosixDay::JulianOne(1),
2085 time: PosixTime { second: 2 * 60 * 60 }
2086 },
2087 );
2088
2089 let p = Parser::new("J1/3");
2090 assert_eq!(
2091 p.parse_posix_datetime().unwrap(),
2092 PosixDayTime {
2093 date: PosixDay::JulianOne(1),
2094 time: PosixTime { second: 3 * 60 * 60 }
2095 },
2096 );
2097
2098 let p = Parser::new("M4.1.0/3");
2099 assert_eq!(
2100 p.parse_posix_datetime().unwrap(),
2101 PosixDayTime {
2102 date: PosixDay::WeekdayOfMonth {
2103 month: 4,
2104 week: 1,
2105 weekday: 0,
2106 },
2107 time: PosixTime { second: 3 * 60 * 60 },
2108 },
2109 );
2110
2111 let p = Parser::new("1/3:45:05");
2112 assert_eq!(
2113 p.parse_posix_datetime().unwrap(),
2114 PosixDayTime {
2115 date: PosixDay::JulianZero(1),
2116 time: PosixTime { second: 3 * 60 * 60 + 45 * 60 + 5 },
2117 },
2118 );
2119
2120 let p = Parser::new("a");
2121 assert!(p.parse_posix_datetime().is_err());
2122
2123 let p = Parser::new("J1/");
2124 assert!(p.parse_posix_datetime().is_err());
2125
2126 let p = Parser::new("1/");
2127 assert!(p.parse_posix_datetime().is_err());
2128
2129 let p = Parser::new("M4.1.0/");
2130 assert!(p.parse_posix_datetime().is_err());
2131 }
2132
2133 #[test]
2134 fn parse_posix_date() {
2135 let p = Parser::new("J1");
2136 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianOne(1));
2137 let p = Parser::new("J365");
2138 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianOne(365));
2139
2140 let p = Parser::new("0");
2141 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(0));
2142 let p = Parser::new("1");
2143 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(1));
2144 let p = Parser::new("365");
2145 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(365));
2146
2147 let p = Parser::new("M9.5.0");
2148 assert_eq!(
2149 p.parse_posix_date().unwrap(),
2150 PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 0 },
2151 );
2152 let p = Parser::new("M9.5.6");
2153 assert_eq!(
2154 p.parse_posix_date().unwrap(),
2155 PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 6 },
2156 );
2157 let p = Parser::new("M09.5.6");
2158 assert_eq!(
2159 p.parse_posix_date().unwrap(),
2160 PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 6 },
2161 );
2162 let p = Parser::new("M12.1.1");
2163 assert_eq!(
2164 p.parse_posix_date().unwrap(),
2165 PosixDay::WeekdayOfMonth { month: 12, week: 1, weekday: 1 },
2166 );
2167
2168 let p = Parser::new("a");
2169 assert!(p.parse_posix_date().is_err());
2170
2171 let p = Parser::new("j");
2172 assert!(p.parse_posix_date().is_err());
2173
2174 let p = Parser::new("m");
2175 assert!(p.parse_posix_date().is_err());
2176
2177 let p = Parser::new("n");
2178 assert!(p.parse_posix_date().is_err());
2179
2180 let p = Parser::new("J366");
2181 assert!(p.parse_posix_date().is_err());
2182
2183 let p = Parser::new("366");
2184 assert!(p.parse_posix_date().is_err());
2185 }
2186
2187 #[test]
2188 fn parse_posix_julian_day_no_leap() {
2189 let p = Parser::new("1");
2190 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1);
2191
2192 let p = Parser::new("001");
2193 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1);
2194
2195 let p = Parser::new("365");
2196 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365);
2197
2198 let p = Parser::new("3655");
2199 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365);
2200
2201 let p = Parser::new("0");
2202 assert!(p.parse_posix_julian_day_no_leap().is_err());
2203
2204 let p = Parser::new("366");
2205 assert!(p.parse_posix_julian_day_no_leap().is_err());
2206 }
2207
2208 #[test]
2209 fn parse_posix_julian_day_with_leap() {
2210 let p = Parser::new("0");
2211 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 0);
2212
2213 let p = Parser::new("1");
2214 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1);
2215
2216 let p = Parser::new("001");
2217 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1);
2218
2219 let p = Parser::new("365");
2220 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365);
2221
2222 let p = Parser::new("3655");
2223 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365);
2224
2225 let p = Parser::new("366");
2226 assert!(p.parse_posix_julian_day_with_leap().is_err());
2227 }
2228
2229 #[test]
2230 fn parse_weekday_of_month() {
2231 let p = Parser::new("9.5.0");
2232 assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 5, 0));
2233
2234 let p = Parser::new("9.1.6");
2235 assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 1, 6));
2236
2237 let p = Parser::new("09.1.6");
2238 assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 1, 6));
2239
2240 let p = Parser::new("9");
2241 assert!(p.parse_weekday_of_month().is_err());
2242
2243 let p = Parser::new("9.");
2244 assert!(p.parse_weekday_of_month().is_err());
2245
2246 let p = Parser::new("9.5");
2247 assert!(p.parse_weekday_of_month().is_err());
2248
2249 let p = Parser::new("9.5.");
2250 assert!(p.parse_weekday_of_month().is_err());
2251
2252 let p = Parser::new("0.5.0");
2253 assert!(p.parse_weekday_of_month().is_err());
2254
2255 let p = Parser::new("13.5.0");
2256 assert!(p.parse_weekday_of_month().is_err());
2257
2258 let p = Parser::new("9.0.0");
2259 assert!(p.parse_weekday_of_month().is_err());
2260
2261 let p = Parser::new("9.6.0");
2262 assert!(p.parse_weekday_of_month().is_err());
2263
2264 let p = Parser::new("9.5.7");
2265 assert!(p.parse_weekday_of_month().is_err());
2266 }
2267
2268 #[test]
2269 fn parse_posix_time() {
2270 let p = Parser::new("5");
2271 assert_eq!(p.parse_posix_time().unwrap().second, 5 * 60 * 60);
2272
2273 let p = Parser::new("22");
2274 assert_eq!(p.parse_posix_time().unwrap().second, 22 * 60 * 60);
2275
2276 let p = Parser::new("02");
2277 assert_eq!(p.parse_posix_time().unwrap().second, 2 * 60 * 60);
2278
2279 let p = Parser::new("5:45");
2280 assert_eq!(
2281 p.parse_posix_time().unwrap().second,
2282 5 * 60 * 60 + 45 * 60
2283 );
2284
2285 let p = Parser::new("5:45:12");
2286 assert_eq!(
2287 p.parse_posix_time().unwrap().second,
2288 5 * 60 * 60 + 45 * 60 + 12
2289 );
2290
2291 let p = Parser::new("5:45:129");
2292 assert_eq!(
2293 p.parse_posix_time().unwrap().second,
2294 5 * 60 * 60 + 45 * 60 + 12
2295 );
2296
2297 let p = Parser::new("5:45:12:");
2298 assert_eq!(
2299 p.parse_posix_time().unwrap().second,
2300 5 * 60 * 60 + 45 * 60 + 12
2301 );
2302
2303 let p = Parser { ianav3plus: true, ..Parser::new("+5:45:12") };
2304 assert_eq!(
2305 p.parse_posix_time().unwrap().second,
2306 5 * 60 * 60 + 45 * 60 + 12
2307 );
2308
2309 let p = Parser { ianav3plus: true, ..Parser::new("-5:45:12") };
2310 assert_eq!(
2311 p.parse_posix_time().unwrap().second,
2312 -(5 * 60 * 60 + 45 * 60 + 12)
2313 );
2314
2315 let p = Parser { ianav3plus: true, ..Parser::new("-167:45:12") };
2316 assert_eq!(
2317 p.parse_posix_time().unwrap().second,
2318 -(167 * 60 * 60 + 45 * 60 + 12),
2319 );
2320
2321 let p = Parser::new("25");
2322 assert!(p.parse_posix_time().is_err());
2323
2324 let p = Parser::new("12:2");
2325 assert!(p.parse_posix_time().is_err());
2326
2327 let p = Parser::new("12:");
2328 assert!(p.parse_posix_time().is_err());
2329
2330 let p = Parser::new("12:23:5");
2331 assert!(p.parse_posix_time().is_err());
2332
2333 let p = Parser::new("12:23:");
2334 assert!(p.parse_posix_time().is_err());
2335
2336 let p = Parser { ianav3plus: true, ..Parser::new("168") };
2337 assert!(p.parse_posix_time().is_err());
2338
2339 let p = Parser { ianav3plus: true, ..Parser::new("-168") };
2340 assert!(p.parse_posix_time().is_err());
2341
2342 let p = Parser { ianav3plus: true, ..Parser::new("+168") };
2343 assert!(p.parse_posix_time().is_err());
2344 }
2345
2346 #[test]
2347 fn parse_month() {
2348 let p = Parser::new("1");
2349 assert_eq!(p.parse_month().unwrap(), 1);
2350
2351 let p = Parser::new("01");
2356 assert_eq!(p.parse_month().unwrap(), 1);
2357
2358 let p = Parser::new("12");
2359 assert_eq!(p.parse_month().unwrap(), 12);
2360
2361 let p = Parser::new("0");
2362 assert!(p.parse_month().is_err());
2363
2364 let p = Parser::new("00");
2365 assert!(p.parse_month().is_err());
2366
2367 let p = Parser::new("001");
2368 assert!(p.parse_month().is_err());
2369
2370 let p = Parser::new("13");
2371 assert!(p.parse_month().is_err());
2372 }
2373
2374 #[test]
2375 fn parse_week() {
2376 let p = Parser::new("1");
2377 assert_eq!(p.parse_week().unwrap(), 1);
2378
2379 let p = Parser::new("5");
2380 assert_eq!(p.parse_week().unwrap(), 5);
2381
2382 let p = Parser::new("55");
2383 assert_eq!(p.parse_week().unwrap(), 5);
2384
2385 let p = Parser::new("0");
2386 assert!(p.parse_week().is_err());
2387
2388 let p = Parser::new("6");
2389 assert!(p.parse_week().is_err());
2390
2391 let p = Parser::new("00");
2392 assert!(p.parse_week().is_err());
2393
2394 let p = Parser::new("01");
2395 assert!(p.parse_week().is_err());
2396
2397 let p = Parser::new("05");
2398 assert!(p.parse_week().is_err());
2399 }
2400
2401 #[test]
2402 fn parse_weekday() {
2403 let p = Parser::new("0");
2404 assert_eq!(p.parse_weekday().unwrap(), 0);
2405
2406 let p = Parser::new("1");
2407 assert_eq!(p.parse_weekday().unwrap(), 1);
2408
2409 let p = Parser::new("6");
2410 assert_eq!(p.parse_weekday().unwrap(), 6);
2411
2412 let p = Parser::new("00");
2413 assert_eq!(p.parse_weekday().unwrap(), 0);
2414
2415 let p = Parser::new("06");
2416 assert_eq!(p.parse_weekday().unwrap(), 0);
2417
2418 let p = Parser::new("60");
2419 assert_eq!(p.parse_weekday().unwrap(), 6);
2420
2421 let p = Parser::new("7");
2422 assert!(p.parse_weekday().is_err());
2423 }
2424
2425 #[test]
2426 fn parse_hour_posix() {
2427 let p = Parser::new("5");
2428 assert_eq!(p.parse_hour_posix().unwrap(), 5);
2429
2430 let p = Parser::new("0");
2431 assert_eq!(p.parse_hour_posix().unwrap(), 0);
2432
2433 let p = Parser::new("00");
2434 assert_eq!(p.parse_hour_posix().unwrap(), 0);
2435
2436 let p = Parser::new("24");
2437 assert_eq!(p.parse_hour_posix().unwrap(), 24);
2438
2439 let p = Parser::new("100");
2440 assert_eq!(p.parse_hour_posix().unwrap(), 10);
2441
2442 let p = Parser::new("25");
2443 assert!(p.parse_hour_posix().is_err());
2444
2445 let p = Parser::new("99");
2446 assert!(p.parse_hour_posix().is_err());
2447 }
2448
2449 #[test]
2450 fn parse_hour_ianav3plus() {
2451 let new = |input| Parser { ianav3plus: true, ..Parser::new(input) };
2452
2453 let p = new("5");
2454 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 5);
2455
2456 let p = new("0");
2457 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
2458
2459 let p = new("00");
2460 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
2461
2462 let p = new("000");
2463 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
2464
2465 let p = new("24");
2466 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 24);
2467
2468 let p = new("100");
2469 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100);
2470
2471 let p = new("1000");
2472 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100);
2473
2474 let p = new("167");
2475 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 167);
2476
2477 let p = new("168");
2478 assert!(p.parse_hour_ianav3plus().is_err());
2479
2480 let p = new("999");
2481 assert!(p.parse_hour_ianav3plus().is_err());
2482 }
2483
2484 #[test]
2485 fn parse_minute() {
2486 let p = Parser::new("00");
2487 assert_eq!(p.parse_minute().unwrap(), 0);
2488
2489 let p = Parser::new("24");
2490 assert_eq!(p.parse_minute().unwrap(), 24);
2491
2492 let p = Parser::new("59");
2493 assert_eq!(p.parse_minute().unwrap(), 59);
2494
2495 let p = Parser::new("599");
2496 assert_eq!(p.parse_minute().unwrap(), 59);
2497
2498 let p = Parser::new("0");
2499 assert!(p.parse_minute().is_err());
2500
2501 let p = Parser::new("1");
2502 assert!(p.parse_minute().is_err());
2503
2504 let p = Parser::new("9");
2505 assert!(p.parse_minute().is_err());
2506
2507 let p = Parser::new("60");
2508 assert!(p.parse_minute().is_err());
2509 }
2510
2511 #[test]
2512 fn parse_second() {
2513 let p = Parser::new("00");
2514 assert_eq!(p.parse_second().unwrap(), 0);
2515
2516 let p = Parser::new("24");
2517 assert_eq!(p.parse_second().unwrap(), 24);
2518
2519 let p = Parser::new("59");
2520 assert_eq!(p.parse_second().unwrap(), 59);
2521
2522 let p = Parser::new("599");
2523 assert_eq!(p.parse_second().unwrap(), 59);
2524
2525 let p = Parser::new("0");
2526 assert!(p.parse_second().is_err());
2527
2528 let p = Parser::new("1");
2529 assert!(p.parse_second().is_err());
2530
2531 let p = Parser::new("9");
2532 assert!(p.parse_second().is_err());
2533
2534 let p = Parser::new("60");
2535 assert!(p.parse_second().is_err());
2536 }
2537
2538 #[test]
2539 fn parse_number_with_exactly_n_digits() {
2540 let p = Parser::new("1");
2541 assert_eq!(p.parse_number_with_exactly_n_digits(1).unwrap(), 1);
2542
2543 let p = Parser::new("12");
2544 assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12);
2545
2546 let p = Parser::new("123");
2547 assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12);
2548
2549 let p = Parser::new("");
2550 assert!(p.parse_number_with_exactly_n_digits(1).is_err());
2551
2552 let p = Parser::new("1");
2553 assert!(p.parse_number_with_exactly_n_digits(2).is_err());
2554
2555 let p = Parser::new("12");
2556 assert!(p.parse_number_with_exactly_n_digits(3).is_err());
2557 }
2558
2559 #[test]
2560 fn parse_number_with_upto_n_digits() {
2561 let p = Parser::new("1");
2562 assert_eq!(p.parse_number_with_upto_n_digits(1).unwrap(), 1);
2563
2564 let p = Parser::new("1");
2565 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 1);
2566
2567 let p = Parser::new("12");
2568 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12);
2569
2570 let p = Parser::new("12");
2571 assert_eq!(p.parse_number_with_upto_n_digits(3).unwrap(), 12);
2572
2573 let p = Parser::new("123");
2574 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12);
2575
2576 let p = Parser::new("");
2577 assert!(p.parse_number_with_upto_n_digits(1).is_err());
2578
2579 let p = Parser::new("a");
2580 assert!(p.parse_number_with_upto_n_digits(1).is_err());
2581 }
2582
2583 #[test]
2584 fn to_dst_civil_datetime_utc_range() {
2585 let tz = posix_time_zone("WART4WARST,J1/-3,J365/20");
2586 let dst_info = DstInfo {
2587 dst: tz.dst.as_ref().unwrap(),
2591 start: date(2024, 1, 1).at(1, 0, 0, 0),
2592 end: date(2024, 12, 31).at(23, 0, 0, 0),
2593 };
2594 assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
2595
2596 let tz = posix_time_zone("WART4WARST,J1/-4,J365/21");
2597 let dst_info = DstInfo {
2598 dst: tz.dst.as_ref().unwrap(),
2599 start: date(2024, 1, 1).at(0, 0, 0, 0),
2600 end: date(2024, 12, 31).at(23, 59, 59, 999_999_999),
2601 };
2602 assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
2603
2604 let tz = posix_time_zone("EST5EDT,M3.2.0,M11.1.0");
2605 let dst_info = DstInfo {
2606 dst: tz.dst.as_ref().unwrap(),
2607 start: date(2024, 3, 10).at(7, 0, 0, 0),
2608 end: date(2024, 11, 3).at(6, 0, 0, 0),
2609 };
2610 assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
2611 }
2612
2613 #[test]
2615 fn regression_permanent_dst() {
2616 let tz = posix_time_zone("XXX-2<+01>-1,0/0,J365/23");
2617 let dst_info = DstInfo {
2618 dst: tz.dst.as_ref().unwrap(),
2619 start: date(2087, 1, 1).at(0, 0, 0, 0),
2620 end: date(2087, 12, 31).at(23, 59, 59, 999_999_999),
2621 };
2622 assert_eq!(tz.dst_info_utc(2087), Some(dst_info));
2623 }
2624
2625 #[test]
2626 fn reasonable() {
2627 assert!(PosixTimeZone::parse(b"EST5").is_ok());
2628 assert!(PosixTimeZone::parse(b"EST5EDT").is_err());
2629 assert!(PosixTimeZone::parse(b"EST5EDT,J1,J365").is_ok());
2630
2631 let tz = posix_time_zone("EST24EDT,J1,J365");
2632 assert_eq!(
2633 tz,
2634 PosixTimeZone {
2635 std_abbrev: "EST".into(),
2636 std_offset: PosixOffset { second: -24 * 60 * 60 },
2637 dst: Some(PosixDst {
2638 abbrev: "EDT".into(),
2639 offset: PosixOffset { second: -23 * 60 * 60 },
2640 rule: PosixRule {
2641 start: PosixDayTime {
2642 date: PosixDay::JulianOne(1),
2643 time: PosixTime::DEFAULT,
2644 },
2645 end: PosixDayTime {
2646 date: PosixDay::JulianOne(365),
2647 time: PosixTime::DEFAULT,
2648 },
2649 },
2650 }),
2651 },
2652 );
2653
2654 let tz = posix_time_zone("EST-24EDT,J1,J365");
2655 assert_eq!(
2656 tz,
2657 PosixTimeZone {
2658 std_abbrev: "EST".into(),
2659 std_offset: PosixOffset { second: 24 * 60 * 60 },
2660 dst: Some(PosixDst {
2661 abbrev: "EDT".into(),
2662 offset: PosixOffset { second: 25 * 60 * 60 },
2663 rule: PosixRule {
2664 start: PosixDayTime {
2665 date: PosixDay::JulianOne(1),
2666 time: PosixTime::DEFAULT,
2667 },
2668 end: PosixDayTime {
2669 date: PosixDay::JulianOne(365),
2670 time: PosixTime::DEFAULT,
2671 },
2672 },
2673 }),
2674 },
2675 );
2676 }
2677
2678 #[test]
2679 fn posix_date_time_spec_to_datetime() {
2680 let to_datetime = |daytime: &PosixDayTime, year: i16| {
2683 daytime.to_datetime(year, IOffset::UTC)
2684 };
2685
2686 let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
2687 assert_eq!(
2688 to_datetime(&tz.rule().start, 2023),
2689 date(2023, 1, 1).at(2, 0, 0, 0),
2690 );
2691 assert_eq!(
2692 to_datetime(&tz.rule().end, 2023),
2693 date(2023, 12, 31).at(5, 12, 34, 0),
2694 );
2695
2696 let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2");
2697 assert_eq!(
2698 to_datetime(&tz.rule().start, 2024),
2699 date(2024, 3, 10).at(2, 0, 0, 0),
2700 );
2701 assert_eq!(
2702 to_datetime(&tz.rule().end, 2024),
2703 date(2024, 11, 3).at(2, 0, 0, 0),
2704 );
2705
2706 let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2");
2707 assert_eq!(
2708 to_datetime(&tz.rule().start, 2024),
2709 date(2024, 1, 1).at(2, 0, 0, 0),
2710 );
2711 assert_eq!(
2712 to_datetime(&tz.rule().end, 2024),
2713 date(2024, 12, 31).at(2, 0, 0, 0),
2714 );
2715
2716 let tz = posix_time_zone("EST5EDT,0/0,J365/25");
2717 assert_eq!(
2718 to_datetime(&tz.rule().start, 2024),
2719 date(2024, 1, 1).at(0, 0, 0, 0),
2720 );
2721 assert_eq!(
2722 to_datetime(&tz.rule().end, 2024),
2723 date(2024, 12, 31).at(23, 59, 59, 999_999_999),
2724 );
2725
2726 let tz = posix_time_zone("XXX3EDT4,0/0,J365/23");
2727 assert_eq!(
2728 to_datetime(&tz.rule().start, 2024),
2729 date(2024, 1, 1).at(0, 0, 0, 0),
2730 );
2731 assert_eq!(
2732 to_datetime(&tz.rule().end, 2024),
2733 date(2024, 12, 31).at(23, 0, 0, 0),
2734 );
2735
2736 let tz = posix_time_zone("XXX3EDT4,0/0,365");
2737 assert_eq!(
2738 to_datetime(&tz.rule().end, 2023),
2739 date(2023, 12, 31).at(23, 59, 59, 999_999_999),
2740 );
2741 assert_eq!(
2742 to_datetime(&tz.rule().end, 2024),
2743 date(2024, 12, 31).at(2, 0, 0, 0),
2744 );
2745
2746 let tz = posix_time_zone("XXX3EDT4,J1/-167:59:59,J365/167:59:59");
2747 assert_eq!(
2748 to_datetime(&tz.rule().start, 2024),
2749 date(2024, 1, 1).at(0, 0, 0, 0),
2750 );
2751 assert_eq!(
2752 to_datetime(&tz.rule().end, 2024),
2753 date(2024, 12, 31).at(23, 59, 59, 999_999_999),
2754 );
2755 }
2756
2757 #[test]
2758 fn posix_date_time_spec_time() {
2759 let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
2760 assert_eq!(tz.rule().start.time, PosixTime::DEFAULT);
2761 assert_eq!(
2762 tz.rule().end.time,
2763 PosixTime { second: 5 * 60 * 60 + 12 * 60 + 34 },
2764 );
2765 }
2766
2767 #[test]
2768 fn posix_date_spec_to_date() {
2769 let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2");
2770 let start = tz.rule().start.date.to_date(2023);
2771 assert_eq!(start, Some(date(2023, 3, 12)));
2772 let end = tz.rule().end.date.to_date(2023);
2773 assert_eq!(end, Some(date(2023, 11, 5)));
2774 let start = tz.rule().start.date.to_date(2024);
2775 assert_eq!(start, Some(date(2024, 3, 10)));
2776 let end = tz.rule().end.date.to_date(2024);
2777 assert_eq!(end, Some(date(2024, 11, 3)));
2778
2779 let tz = posix_time_zone("EST+5EDT,J60,J365");
2780 let start = tz.rule().start.date.to_date(2023);
2781 assert_eq!(start, Some(date(2023, 3, 1)));
2782 let end = tz.rule().end.date.to_date(2023);
2783 assert_eq!(end, Some(date(2023, 12, 31)));
2784 let start = tz.rule().start.date.to_date(2024);
2785 assert_eq!(start, Some(date(2024, 3, 1)));
2786 let end = tz.rule().end.date.to_date(2024);
2787 assert_eq!(end, Some(date(2024, 12, 31)));
2788
2789 let tz = posix_time_zone("EST+5EDT,59,365");
2790 let start = tz.rule().start.date.to_date(2023);
2791 assert_eq!(start, Some(date(2023, 3, 1)));
2792 let end = tz.rule().end.date.to_date(2023);
2793 assert_eq!(end, None);
2794 let start = tz.rule().start.date.to_date(2024);
2795 assert_eq!(start, Some(date(2024, 2, 29)));
2796 let end = tz.rule().end.date.to_date(2024);
2797 assert_eq!(end, Some(date(2024, 12, 31)));
2798
2799 let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2");
2800 let start = tz.rule().start.date.to_date(2024);
2801 assert_eq!(start, Some(date(2024, 1, 1)));
2802 let end = tz.rule().end.date.to_date(2024);
2803 assert_eq!(end, Some(date(2024, 12, 31)));
2804 }
2805
2806 #[test]
2807 fn posix_time_spec_to_civil_time() {
2808 let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
2809 assert_eq!(
2810 tz.dst.as_ref().unwrap().rule.start.time.second,
2811 2 * 60 * 60,
2812 );
2813 assert_eq!(
2814 tz.dst.as_ref().unwrap().rule.end.time.second,
2815 5 * 60 * 60 + 12 * 60 + 34,
2816 );
2817
2818 let tz = posix_time_zone("EST5EDT,J1/23:59:59,J365/24:00:00");
2819 assert_eq!(
2820 tz.dst.as_ref().unwrap().rule.start.time.second,
2821 23 * 60 * 60 + 59 * 60 + 59,
2822 );
2823 assert_eq!(
2824 tz.dst.as_ref().unwrap().rule.end.time.second,
2825 24 * 60 * 60,
2826 );
2827
2828 let tz = posix_time_zone("EST5EDT,J1/-1,J365/167:00:00");
2829 assert_eq!(
2830 tz.dst.as_ref().unwrap().rule.start.time.second,
2831 -1 * 60 * 60,
2832 );
2833 assert_eq!(
2834 tz.dst.as_ref().unwrap().rule.end.time.second,
2835 167 * 60 * 60,
2836 );
2837 }
2838
2839 #[test]
2840 fn parse_iana() {
2841 let p = PosixTimeZone::parse(b"CRAZY5SHORT,M12.5.0/50,0/2").unwrap();
2843 assert_eq!(
2844 p,
2845 PosixTimeZone {
2846 std_abbrev: "CRAZY".into(),
2847 std_offset: PosixOffset { second: -5 * 60 * 60 },
2848 dst: Some(PosixDst {
2849 abbrev: "SHORT".into(),
2850 offset: PosixOffset { second: -4 * 60 * 60 },
2851 rule: PosixRule {
2852 start: PosixDayTime {
2853 date: PosixDay::WeekdayOfMonth {
2854 month: 12,
2855 week: 5,
2856 weekday: 0,
2857 },
2858 time: PosixTime { second: 50 * 60 * 60 },
2859 },
2860 end: PosixDayTime {
2861 date: PosixDay::JulianZero(0),
2862 time: PosixTime { second: 2 * 60 * 60 },
2863 },
2864 },
2865 }),
2866 },
2867 );
2868
2869 assert!(PosixTimeZone::parse(b"America/New_York").is_err());
2870 assert!(PosixTimeZone::parse(b":America/New_York").is_err());
2871 }
2872
2873 #[test]
2875 fn parse_empty_is_err() {
2876 assert!(PosixTimeZone::parse(b"").is_err());
2877 }
2878
2879 #[test]
2881 fn parse_weird_is_err() {
2882 let s =
2883 b"AAAAAAAAAAAAAAACAAAAAAAAAAAAQA8AACAAAAAAAAAAAAAAAAACAAAAAAAAAAA";
2884 assert!(PosixTimeZone::parse(s).is_err());
2885
2886 let s =
2887 b"<AAAAAAAAAAAAAAACAAAAAAAAAAAAQA>8<AACAAAAAAAAAAAAAAAAACAAAAAAAAAAA>";
2888 assert!(PosixTimeZone::parse(s).is_err());
2889
2890 let s = b"PPPPPPPPPPPPPPPPPPPPnoofPPPAAA6DaPPPPPPPPPPPPPPPPPPPPPnoofPPPPP,n";
2891 assert!(PosixTimeZone::parse(s).is_err());
2892
2893 let s = b"oooooooooovooooooooooooooooool9<ooooo2o-o-oooooookoorooooooooroo8";
2894 assert!(PosixTimeZone::parse(s).is_err());
2895 }
2896}