1use super::{
2 util::{
3 array_str::Abbreviation,
4 error::{err, Error},
5 escape::{Byte, Bytes},
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 #[cfg(feature = "alloc")]
19 pub fn parse(bytes: &[u8]) -> Result<PosixTimeZone<Abbreviation>, Error> {
20 let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
26 parser.parse()
27 }
28
29 #[cfg(feature = "alloc")]
33 pub fn parse_prefix<'b>(
34 bytes: &'b [u8],
35 ) -> Result<(PosixTimeZone<Abbreviation>, &'b [u8]), Error> {
36 let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
37 parser.parse_prefix()
38 }
39 }
41
42impl<ABBREV: AsRef<str>> PosixTimeZone<ABBREV> {
43 pub(crate) fn to_offset(&self, timestamp: ITimestamp) -> IOffset {
51 let std_offset = self.std_offset.to_ioffset();
52 if self.dst.is_none() {
53 return std_offset;
54 }
55
56 let dt = timestamp.to_datetime(IOffset::UTC);
57 self.dst_info_utc(dt.date.year)
58 .filter(|dst_info| dst_info.in_dst(dt))
59 .map(|dst_info| dst_info.offset().to_ioffset())
60 .unwrap_or_else(|| std_offset)
61 }
62
63 pub(crate) fn to_offset_info(
70 &self,
71 timestamp: ITimestamp,
72 ) -> (IOffset, &'_ str, bool) {
73 let std_offset = self.std_offset.to_ioffset();
74 if self.dst.is_none() {
75 return (std_offset, self.std_abbrev.as_ref(), false);
76 }
77
78 let dt = timestamp.to_datetime(IOffset::UTC);
79 self.dst_info_utc(dt.date.year)
80 .filter(|dst_info| dst_info.in_dst(dt))
81 .map(|dst_info| {
82 (
83 dst_info.offset().to_ioffset(),
84 dst_info.dst.abbrev.as_ref(),
85 true,
86 )
87 })
88 .unwrap_or_else(|| (std_offset, self.std_abbrev.as_ref(), false))
89 }
90
91 pub(crate) fn to_ambiguous_kind(&self, dt: IDateTime) -> IAmbiguousOffset {
103 let year = dt.date.year;
104 let std_offset = self.std_offset.to_ioffset();
105 let Some(dst_info) = self.dst_info_wall(year) else {
106 return IAmbiguousOffset::Unambiguous { offset: std_offset };
107 };
108 let dst_offset = dst_info.offset().to_ioffset();
109 let diff = dst_offset.second - std_offset.second;
110 if diff == 0 {
125 debug_assert_eq!(std_offset, dst_offset);
126 IAmbiguousOffset::Unambiguous { offset: std_offset }
127 } else if diff.is_negative() {
128 if dst_info.in_dst(dt) {
132 IAmbiguousOffset::Unambiguous { offset: dst_offset }
133 } else {
134 let fold_start = dst_info.start.saturating_add_seconds(diff);
135 let gap_end =
136 dst_info.end.saturating_add_seconds(diff.saturating_neg());
137 if fold_start <= dt && dt < dst_info.start {
138 IAmbiguousOffset::Fold {
139 before: std_offset,
140 after: dst_offset,
141 }
142 } else if dst_info.end <= dt && dt < gap_end {
143 IAmbiguousOffset::Gap {
144 before: dst_offset,
145 after: std_offset,
146 }
147 } else {
148 IAmbiguousOffset::Unambiguous { offset: std_offset }
149 }
150 }
151 } else {
152 if !dst_info.in_dst(dt) {
156 IAmbiguousOffset::Unambiguous { offset: std_offset }
157 } else {
158 let gap_end = dst_info.start.saturating_add_seconds(diff);
163 let fold_start =
164 dst_info.end.saturating_add_seconds(diff.saturating_neg());
165 if dst_info.start <= dt && dt < gap_end {
166 IAmbiguousOffset::Gap {
167 before: std_offset,
168 after: dst_offset,
169 }
170 } else if fold_start <= dt && dt < dst_info.end {
171 IAmbiguousOffset::Fold {
172 before: dst_offset,
173 after: std_offset,
174 }
175 } else {
176 IAmbiguousOffset::Unambiguous { offset: dst_offset }
177 }
178 }
179 }
180 }
181
182 pub(crate) fn previous_transition(
185 &self,
186 timestamp: ITimestamp,
187 ) -> Option<(ITimestamp, IOffset, &'_ str, bool)> {
188 let dt = timestamp.to_datetime(IOffset::UTC);
189 let dst_info = self.dst_info_utc(dt.date.year)?;
190 let (earlier, later) = dst_info.ordered();
191 let (prev, dst_info) = if dt > later {
192 (later, dst_info)
193 } else if dt > earlier {
194 (earlier, dst_info)
195 } else {
196 let prev_year = dt.date.prev_year().ok()?;
197 let dst_info = self.dst_info_utc(prev_year)?;
198 let (_, later) = dst_info.ordered();
199 (later, dst_info)
200 };
201
202 let timestamp = prev.to_timestamp_checked(IOffset::UTC)?;
203 let dt = timestamp.to_datetime(IOffset::UTC);
204 let (offset, abbrev, dst) = if dst_info.in_dst(dt) {
205 (dst_info.offset(), dst_info.dst.abbrev.as_ref(), true)
206 } else {
207 (&self.std_offset, self.std_abbrev.as_ref(), false)
208 };
209 Some((timestamp, offset.to_ioffset(), abbrev, dst))
210 }
211
212 pub(crate) fn next_transition(
215 &self,
216 timestamp: ITimestamp,
217 ) -> Option<(ITimestamp, IOffset, &'_ str, bool)> {
218 let dt = timestamp.to_datetime(IOffset::UTC);
219 let dst_info = self.dst_info_utc(dt.date.year)?;
220 let (earlier, later) = dst_info.ordered();
221 let (next, dst_info) = if dt < earlier {
222 (earlier, dst_info)
223 } else if dt < later {
224 (later, dst_info)
225 } else {
226 let next_year = dt.date.next_year().ok()?;
227 let dst_info = self.dst_info_utc(next_year)?;
228 let (earlier, _) = dst_info.ordered();
229 (earlier, dst_info)
230 };
231
232 let timestamp = next.to_timestamp_checked(IOffset::UTC)?;
233 let dt = timestamp.to_datetime(IOffset::UTC);
234 let (offset, abbrev, dst) = if dst_info.in_dst(dt) {
235 (dst_info.offset(), dst_info.dst.abbrev.as_ref(), true)
236 } else {
237 (&self.std_offset, self.std_abbrev.as_ref(), false)
238 };
239 Some((timestamp, offset.to_ioffset(), abbrev, dst))
240 }
241
242 fn dst_info_utc(&self, year: i16) -> Option<DstInfo<'_, ABBREV>> {
247 let dst = self.dst.as_ref()?;
248 let start =
251 dst.rule.start.to_datetime(year, self.std_offset.to_ioffset());
252 let end = dst.rule.end.to_datetime(year, dst.offset.to_ioffset());
255 Some(DstInfo { dst, start, end })
256 }
257
258 fn dst_info_wall(&self, year: i16) -> Option<DstInfo<'_, ABBREV>> {
264 let dst = self.dst.as_ref()?;
265 let start = dst.rule.start.to_datetime(year, IOffset::UTC);
269 let end = dst.rule.end.to_datetime(year, IOffset::UTC);
270 Some(DstInfo { dst, start, end })
271 }
272
273 #[cfg(test)]
276 fn rule(&self) -> &PosixRule {
277 &self.dst.as_ref().unwrap().rule
278 }
279}
280
281impl<ABBREV: AsRef<str>> core::fmt::Display for PosixTimeZone<ABBREV> {
282 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
283 write!(
284 f,
285 "{}{}",
286 AbbreviationDisplay(self.std_abbrev.as_ref()),
287 self.std_offset
288 )?;
289 if let Some(ref dst) = self.dst {
290 dst.display(&self.std_offset, f)?;
291 }
292 Ok(())
293 }
294}
295
296impl<ABBREV: AsRef<str>> PosixDst<ABBREV> {
297 fn display(
298 &self,
299 std_offset: &PosixOffset,
300 f: &mut core::fmt::Formatter,
301 ) -> core::fmt::Result {
302 write!(f, "{}", AbbreviationDisplay(self.abbrev.as_ref()))?;
303 let default = PosixOffset { second: std_offset.second + 3600 };
307 if self.offset != default {
308 write!(f, "{}", self.offset)?;
309 }
310 write!(f, ",{}", self.rule)?;
311 Ok(())
312 }
313}
314
315impl core::fmt::Display for PosixRule {
316 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
317 write!(f, "{},{}", self.start, self.end)
318 }
319}
320
321impl PosixDayTime {
322 pub(crate) fn to_datetime(&self, year: i16, offset: IOffset) -> IDateTime {
334 let mkmin = || IDateTime {
335 date: IDate { year, month: 1, day: 1 },
336 time: ITime::MIN,
337 };
338 let mkmax = || IDateTime {
339 date: IDate { year, month: 12, day: 31 },
340 time: ITime::MAX,
341 };
342 let Some(date) = self.date.to_date(year) else { return mkmax() };
343 let offset = self.time.second - offset.second;
347 let days = offset.div_euclid(86400);
350 let second = offset.rem_euclid(86400);
351
352 let Ok(date) = date.checked_add_days(days) else {
353 return if offset < 0 { mkmin() } else { mkmax() };
354 };
355 if date.year < year {
356 mkmin()
357 } else if date.year > year {
358 mkmax()
359 } else {
360 let time = ITimeSecond { second }.to_time();
361 IDateTime { date, time }
362 }
363 }
364}
365
366impl core::fmt::Display for PosixDayTime {
367 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
368 write!(f, "{}", self.date)?;
369 if self.time != PosixTime::DEFAULT {
372 write!(f, "/{}", self.time)?;
373 }
374 Ok(())
375 }
376}
377
378impl PosixDay {
379 fn to_date(&self, year: i16) -> Option<IDate> {
386 match *self {
387 PosixDay::JulianOne(day) => {
388 Some(
392 IDate::from_day_of_year_no_leap(year, day)
393 .expect("Julian `J day` should be in bounds"),
394 )
395 }
396 PosixDay::JulianZero(day) => {
397 IDate::from_day_of_year(year, day + 1).ok()
407 }
408 PosixDay::WeekdayOfMonth { month, week, weekday } => {
409 let weekday = IWeekday::from_sunday_zero_offset(weekday);
410 let first = IDate { year, month, day: 1 };
411 let week = if week == 5 { -1 } else { week };
412 debug_assert!(week == -1 || (1..=4).contains(&week));
413 Some(
428 first
429 .nth_weekday_of_month(week, weekday)
430 .expect("nth weekday always exists"),
431 )
432 }
433 }
434 }
435}
436
437impl core::fmt::Display for PosixDay {
438 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
439 match *self {
440 PosixDay::JulianOne(n) => write!(f, "J{n}"),
441 PosixDay::JulianZero(n) => write!(f, "{n}"),
442 PosixDay::WeekdayOfMonth { month, week, weekday } => {
443 write!(f, "M{month}.{week}.{weekday}")
444 }
445 }
446 }
447}
448
449impl PosixTime {
450 const DEFAULT: PosixTime = PosixTime { second: 2 * 60 * 60 };
451}
452
453impl core::fmt::Display for PosixTime {
454 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
455 if self.second.is_negative() {
456 write!(f, "-")?;
457 }
460 let second = self.second.unsigned_abs();
461 let h = second / 3600;
462 let m = (second / 60) % 60;
463 let s = second % 60;
464 write!(f, "{h}")?;
465 if m != 0 || s != 0 {
466 write!(f, ":{m:02}")?;
467 if s != 0 {
468 write!(f, ":{s:02}")?;
469 }
470 }
471 Ok(())
472 }
473}
474
475impl PosixOffset {
476 fn to_ioffset(&self) -> IOffset {
477 IOffset { second: self.second }
478 }
479}
480
481impl core::fmt::Display for PosixOffset {
482 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
483 if self.second > 0 {
487 write!(f, "-")?;
488 }
489 let second = self.second.unsigned_abs();
490 let h = second / 3600;
491 let m = (second / 60) % 60;
492 let s = second % 60;
493 write!(f, "{h}")?;
494 if m != 0 || s != 0 {
495 write!(f, ":{m:02}")?;
496 if s != 0 {
497 write!(f, ":{s:02}")?;
498 }
499 }
500 Ok(())
501 }
502}
503
504#[derive(Debug)]
509struct AbbreviationDisplay<S>(S);
510
511impl<S: AsRef<str>> core::fmt::Display for AbbreviationDisplay<S> {
512 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
513 let s = self.0.as_ref();
514 if s.chars().any(|ch| ch == '+' || ch == '-') {
515 write!(f, "<{s}>")
516 } else {
517 write!(f, "{s}")
518 }
519 }
520}
521
522#[derive(Debug, Eq, PartialEq)]
525struct DstInfo<'a, ABBREV> {
526 dst: &'a PosixDst<ABBREV>,
528 start: IDateTime,
537 end: IDateTime,
546}
547
548impl<'a, ABBREV> DstInfo<'a, ABBREV> {
549 fn in_dst(&self, utc_dt: IDateTime) -> bool {
552 if self.start <= self.end {
553 self.start <= utc_dt && utc_dt < self.end
554 } else {
555 !(self.end <= utc_dt && utc_dt < self.start)
556 }
557 }
558
559 fn ordered(&self) -> (IDateTime, IDateTime) {
561 if self.start <= self.end {
562 (self.start, self.end)
563 } else {
564 (self.end, self.start)
565 }
566 }
567
568 fn offset(&self) -> &PosixOffset {
570 &self.dst.offset
571 }
572}
573
574#[derive(Debug)]
576struct Parser<'s> {
577 tz: &'s [u8],
579 pos: core::cell::Cell<usize>,
581 ianav3plus: bool,
595}
596
597impl<'s> Parser<'s> {
598 fn new<B: ?Sized + AsRef<[u8]>>(tz: &'s B) -> Parser<'s> {
601 Parser {
602 tz: tz.as_ref(),
603 pos: core::cell::Cell::new(0),
604 ianav3plus: false,
605 }
606 }
607
608 fn parse(&self) -> Result<PosixTimeZone<Abbreviation>, Error> {
612 let (time_zone, remaining) = self.parse_prefix()?;
613 if !remaining.is_empty() {
614 return Err(err!(
615 "expected entire TZ string to be a valid POSIX \
616 time zone, but found `{}` after what would otherwise \
617 be a valid POSIX TZ string",
618 Bytes(remaining),
619 ));
620 }
621 Ok(time_zone)
622 }
623
624 fn parse_prefix(
627 &self,
628 ) -> Result<(PosixTimeZone<Abbreviation>, &'s [u8]), Error> {
629 let time_zone = self.parse_posix_time_zone()?;
630 Ok((time_zone, self.remaining()))
631 }
632
633 fn parse_posix_time_zone(
638 &self,
639 ) -> Result<PosixTimeZone<Abbreviation>, Error> {
640 let std_abbrev = self
641 .parse_abbreviation()
642 .map_err(|e| err!("failed to parse standard abbreviation: {e}"))?;
643 let std_offset = self
644 .parse_posix_offset()
645 .map_err(|e| err!("failed to parse standard offset: {e}"))?;
646 let mut dst = None;
647 if !self.is_done()
648 && (self.byte().is_ascii_alphabetic() || self.byte() == b'<')
649 {
650 dst = Some(self.parse_posix_dst(&std_offset)?);
651 }
652 Ok(PosixTimeZone { std_abbrev, std_offset, dst })
653 }
654
655 fn parse_posix_dst(
664 &self,
665 std_offset: &PosixOffset,
666 ) -> Result<PosixDst<Abbreviation>, Error> {
667 let abbrev = self
668 .parse_abbreviation()
669 .map_err(|e| err!("failed to parse DST abbreviation: {e}"))?;
670 if self.is_done() {
671 return Err(err!(
672 "found DST abbreviation `{abbrev}`, but no transition \
673 rule (this is technically allowed by POSIX, but has \
674 unspecified behavior)",
675 ));
676 }
677 let mut offset = PosixOffset { second: std_offset.second + 3600 };
681 if self.byte() != b',' {
682 offset = self
683 .parse_posix_offset()
684 .map_err(|e| err!("failed to parse DST offset: {e}"))?;
685 if self.is_done() {
686 return Err(err!(
687 "found DST abbreviation `{abbrev}` and offset \
688 `{offset}s`, but no transition rule (this is \
689 technically allowed by POSIX, but has \
690 unspecified behavior)",
691 offset = offset.second,
692 ));
693 }
694 }
695 if self.byte() != b',' {
696 return Err(err!(
697 "after parsing DST offset in POSIX time zone string, \
698 found `{}` but expected a ','",
699 Byte(self.byte()),
700 ));
701 }
702 if !self.bump() {
703 return Err(err!(
704 "after parsing DST offset in POSIX time zone string, \
705 found end of string after a trailing ','",
706 ));
707 }
708 let rule = self.parse_rule()?;
709 Ok(PosixDst { abbrev, offset, rule })
710 }
711
712 fn parse_abbreviation(&self) -> Result<Abbreviation, Error> {
725 if self.byte() == b'<' {
726 if !self.bump() {
727 return Err(err!(
728 "found opening '<' quote for abbreviation in \
729 POSIX time zone string, and expected a name \
730 following it, but found the end of string instead"
731 ));
732 }
733 self.parse_quoted_abbreviation()
734 } else {
735 self.parse_unquoted_abbreviation()
736 }
737 }
738
739 fn parse_unquoted_abbreviation(&self) -> Result<Abbreviation, Error> {
751 let start = self.pos();
752 for i in 0.. {
753 if !self.byte().is_ascii_alphabetic() {
754 break;
755 }
756 if i >= Abbreviation::capacity() {
757 return Err(err!(
758 "expected abbreviation with at most {} bytes, \
759 but found a longer abbreviation beginning with `{}`",
760 Abbreviation::capacity(),
761 Bytes(&self.tz[start..i]),
762 ));
763 }
764 if !self.bump() {
765 break;
766 }
767 }
768 let end = self.pos();
769 let abbrev =
770 core::str::from_utf8(&self.tz[start..end]).map_err(|_| {
771 err!(
778 "found abbreviation `{}`, but it is not valid UTF-8",
779 Bytes(&self.tz[start..end]),
780 )
781 })?;
782 if abbrev.len() < 3 {
783 return Err(err!(
784 "expected abbreviation with 3 or more bytes, but found \
785 abbreviation {:?} with {} bytes",
786 abbrev,
787 abbrev.len(),
788 ));
789 }
790 Ok(Abbreviation::new(abbrev).unwrap())
793 }
794
795 fn parse_quoted_abbreviation(&self) -> Result<Abbreviation, Error> {
807 let start = self.pos();
808 for i in 0.. {
809 if !self.byte().is_ascii_alphanumeric()
810 && self.byte() != b'+'
811 && self.byte() != b'-'
812 {
813 break;
814 }
815 if i >= Abbreviation::capacity() {
816 return Err(err!(
817 "expected abbreviation with at most {} bytes, \
818 but found a longer abbreviation beginning with `{}`",
819 Abbreviation::capacity(),
820 Bytes(&self.tz[start..i]),
821 ));
822 }
823 if !self.bump() {
824 break;
825 }
826 }
827 let end = self.pos();
828 let abbrev =
829 core::str::from_utf8(&self.tz[start..end]).map_err(|_| {
830 err!(
837 "found abbreviation `{}`, but it is not valid UTF-8",
838 Bytes(&self.tz[start..end]),
839 )
840 })?;
841 if self.is_done() {
842 return Err(err!(
843 "found non-empty quoted abbreviation {abbrev:?}, but \
844 did not find expected end-of-quoted abbreviation \
845 '>' character",
846 ));
847 }
848 if self.byte() != b'>' {
849 return Err(err!(
850 "found non-empty quoted abbreviation {abbrev:?}, but \
851 found `{}` instead of end-of-quoted abbreviation '>' \
852 character",
853 Byte(self.byte()),
854 ));
855 }
856 self.bump();
857 if abbrev.len() < 3 {
858 return Err(err!(
859 "expected abbreviation with 3 or more bytes, but found \
860 abbreviation {abbrev:?} with {} bytes",
861 abbrev.len(),
862 ));
863 }
864 Ok(Abbreviation::new(abbrev).unwrap())
867 }
868
869 fn parse_posix_offset(&self) -> Result<PosixOffset, Error> {
878 let sign = self
879 .parse_optional_sign()
880 .map_err(|e| {
881 err!(
882 "failed to parse sign for time offset \
883 in POSIX time zone string: {e}",
884 )
885 })?
886 .unwrap_or(1);
887 let hour = self.parse_hour_posix()?;
888 let (mut minute, mut second) = (0, 0);
889 if self.maybe_byte() == Some(b':') {
890 if !self.bump() {
891 return Err(err!(
892 "incomplete time in POSIX timezone (missing minutes)",
893 ));
894 }
895 minute = self.parse_minute()?;
896 if self.maybe_byte() == Some(b':') {
897 if !self.bump() {
898 return Err(err!(
899 "incomplete time in POSIX timezone (missing seconds)",
900 ));
901 }
902 second = self.parse_second()?;
903 }
904 }
905 let mut offset = PosixOffset { second: i32::from(hour) * 3600 };
906 offset.second += i32::from(minute) * 60;
907 offset.second += i32::from(second);
908 offset.second *= i32::from(-sign);
911 assert!(
915 -89999 <= offset.second && offset.second <= 89999,
916 "POSIX offset seconds {} is out of range",
917 offset.second
918 );
919 Ok(offset)
920 }
921
922 fn parse_rule(&self) -> Result<PosixRule, Error> {
932 let start = self.parse_posix_datetime().map_err(|e| {
933 err!("failed to parse start of DST transition rule: {e}")
934 })?;
935 if self.maybe_byte() != Some(b',') || !self.bump() {
936 return Err(err!(
937 "expected end of DST rule after parsing the start \
938 of the DST rule"
939 ));
940 }
941 let end = self.parse_posix_datetime().map_err(|e| {
942 err!("failed to parse end of DST transition rule: {e}")
943 })?;
944 Ok(PosixRule { start, end })
945 }
946
947 fn parse_posix_datetime(&self) -> Result<PosixDayTime, Error> {
956 let mut daytime = PosixDayTime {
957 date: self.parse_posix_date()?,
958 time: PosixTime::DEFAULT,
959 };
960 if self.maybe_byte() != Some(b'/') {
961 return Ok(daytime);
962 }
963 if !self.bump() {
964 return Err(err!(
965 "expected time specification after '/' following a date
966 specification in a POSIX time zone DST transition rule",
967 ));
968 }
969 daytime.time = self.parse_posix_time()?;
970 Ok(daytime)
971 }
972
973 fn parse_posix_date(&self) -> Result<PosixDay, Error> {
987 match self.byte() {
988 b'J' => {
989 if !self.bump() {
990 return Err(err!(
991 "expected one-based Julian day after 'J' in date \
992 specification of a POSIX time zone DST \
993 transition rule, but got the end of the string \
994 instead"
995 ));
996 }
997 Ok(PosixDay::JulianOne(self.parse_posix_julian_day_no_leap()?))
998 }
999 b'0'..=b'9' => Ok(PosixDay::JulianZero(
1000 self.parse_posix_julian_day_with_leap()?,
1001 )),
1002 b'M' => {
1003 if !self.bump() {
1004 return Err(err!(
1005 "expected month-week-weekday after 'M' in date \
1006 specification of a POSIX time zone DST \
1007 transition rule, but got the end of the string \
1008 instead"
1009 ));
1010 }
1011 let (month, week, weekday) = self.parse_weekday_of_month()?;
1012 Ok(PosixDay::WeekdayOfMonth { month, week, weekday })
1013 }
1014 _ => Err(err!(
1015 "expected 'J', a digit or 'M' at the beginning of a date \
1016 specification of a POSIX time zone DST transition rule, \
1017 but got `{}` instead",
1018 Byte(self.byte()),
1019 )),
1020 }
1021 }
1022
1023 fn parse_posix_julian_day_no_leap(&self) -> Result<i16, Error> {
1030 let number = self
1031 .parse_number_with_upto_n_digits(3)
1032 .map_err(|e| err!("invalid one based Julian day: {e}"))?;
1033 let number = i16::try_from(number).map_err(|_| {
1034 err!(
1035 "one based Julian day `{number}` in POSIX time zone \
1036 does not fit into 16-bit integer"
1037 )
1038 })?;
1039 if !(1 <= number && number <= 365) {
1040 return Err(err!(
1041 "parsed one based Julian day `{number}`, \
1042 but one based Julian day in POSIX time zone \
1043 must be in range 1..=365",
1044 ));
1045 }
1046 Ok(number)
1047 }
1048
1049 fn parse_posix_julian_day_with_leap(&self) -> Result<i16, Error> {
1056 let number = self
1057 .parse_number_with_upto_n_digits(3)
1058 .map_err(|e| err!("invalid zero based Julian day: {e}"))?;
1059 let number = i16::try_from(number).map_err(|_| {
1060 err!(
1061 "zero based Julian day `{number}` in POSIX time zone \
1062 does not fit into 16-bit integer"
1063 )
1064 })?;
1065 if !(0 <= number && number <= 365) {
1066 return Err(err!(
1067 "parsed zero based Julian day `{number}`, \
1068 but zero based Julian day in POSIX time zone \
1069 must be in range 0..=365",
1070 ));
1071 }
1072 Ok(number)
1073 }
1074
1075 fn parse_weekday_of_month(&self) -> Result<(i8, i8, i8), Error> {
1085 let month = self.parse_month()?;
1086 if self.maybe_byte() != Some(b'.') {
1087 return Err(err!(
1088 "expected '.' after month `{month}` in \
1089 POSIX time zone rule"
1090 ));
1091 }
1092 if !self.bump() {
1093 return Err(err!(
1094 "expected week after month `{month}` in \
1095 POSIX time zone rule"
1096 ));
1097 }
1098 let week = self.parse_week()?;
1099 if self.maybe_byte() != Some(b'.') {
1100 return Err(err!(
1101 "expected '.' after week `{week}` in POSIX time zone rule"
1102 ));
1103 }
1104 if !self.bump() {
1105 return Err(err!(
1106 "expected day-of-week after week `{week}` in \
1107 POSIX time zone rule"
1108 ));
1109 }
1110 let weekday = self.parse_weekday()?;
1111 Ok((month, week, weekday))
1112 }
1113
1114 fn parse_posix_time(&self) -> Result<PosixTime, Error> {
1121 let (sign, hour) = if self.ianav3plus {
1122 let sign = self
1123 .parse_optional_sign()
1124 .map_err(|e| {
1125 err!(
1126 "failed to parse sign for transition time \
1127 in POSIX time zone string: {e}",
1128 )
1129 })?
1130 .unwrap_or(1);
1131 let hour = self.parse_hour_ianav3plus()?;
1132 (sign, hour)
1133 } else {
1134 (1, i16::from(self.parse_hour_posix()?))
1135 };
1136 let (mut minute, mut second) = (0, 0);
1137 if self.maybe_byte() == Some(b':') {
1138 if !self.bump() {
1139 return Err(err!(
1140 "incomplete transition time in \
1141 POSIX time zone string (missing minutes)",
1142 ));
1143 }
1144 minute = self.parse_minute()?;
1145 if self.maybe_byte() == Some(b':') {
1146 if !self.bump() {
1147 return Err(err!(
1148 "incomplete transition time in \
1149 POSIX time zone string (missing seconds)",
1150 ));
1151 }
1152 second = self.parse_second()?;
1153 }
1154 }
1155 let mut time = PosixTime { second: i32::from(hour) * 3600 };
1156 time.second += i32::from(minute) * 60;
1157 time.second += i32::from(second);
1158 time.second *= i32::from(sign);
1159 assert!(
1163 -604799 <= time.second && time.second <= 604799,
1164 "POSIX time seconds {} is out of range",
1165 time.second
1166 );
1167 Ok(time)
1168 }
1169
1170 fn parse_month(&self) -> Result<i8, Error> {
1176 let number = self.parse_number_with_upto_n_digits(2)?;
1177 let number = i8::try_from(number).map_err(|_| {
1178 err!(
1179 "month `{number}` in POSIX time zone \
1180 does not fit into 8-bit integer"
1181 )
1182 })?;
1183 if !(1 <= number && number <= 12) {
1184 return Err(err!(
1185 "parsed month `{number}`, but month in \
1186 POSIX time zone must be in range 1..=12",
1187 ));
1188 }
1189 Ok(number)
1190 }
1191
1192 fn parse_week(&self) -> Result<i8, Error> {
1197 let number = self.parse_number_with_exactly_n_digits(1)?;
1198 let number = i8::try_from(number).map_err(|_| {
1199 err!(
1200 "week `{number}` in POSIX time zone \
1201 does not fit into 8-bit integer"
1202 )
1203 })?;
1204 if !(1 <= number && number <= 5) {
1205 return Err(err!(
1206 "parsed week `{number}`, but week in \
1207 POSIX time zone must be in range 1..=5"
1208 ));
1209 }
1210 Ok(number)
1211 }
1212
1213 fn parse_weekday(&self) -> Result<i8, Error> {
1221 let number = self.parse_number_with_exactly_n_digits(1)?;
1222 let number = i8::try_from(number).map_err(|_| {
1223 err!(
1224 "weekday `{number}` in POSIX time zone \
1225 does not fit into 8-bit integer"
1226 )
1227 })?;
1228 if !(0 <= number && number <= 6) {
1229 return Err(err!(
1230 "parsed weekday `{number}`, but weekday in \
1231 POSIX time zone must be in range `0..=6` \
1232 (with `0` corresponding to Sunday)",
1233 ));
1234 }
1235 Ok(number)
1236 }
1237
1238 fn parse_hour_ianav3plus(&self) -> Result<i16, Error> {
1250 assert!(self.ianav3plus);
1253 let number = self
1254 .parse_number_with_upto_n_digits(3)
1255 .map_err(|e| err!("invalid hour digits: {e}"))?;
1256 let number = i16::try_from(number).map_err(|_| {
1257 err!(
1258 "hour `{number}` in POSIX time zone \
1259 does not fit into 16-bit integer"
1260 )
1261 })?;
1262 if !(0 <= number && number <= 167) {
1263 return Err(err!(
1267 "parsed hour `{number}`, but hour in IANA v3+ \
1268 POSIX time zone must be in range `-167..=167`",
1269 ));
1270 }
1271 Ok(number)
1272 }
1273
1274 fn parse_hour_posix(&self) -> Result<i8, Error> {
1284 let number = self
1285 .parse_number_with_upto_n_digits(2)
1286 .map_err(|e| err!("invalid hour digits: {e}"))?;
1287 let number = i8::try_from(number).map_err(|_| {
1288 err!(
1289 "hour `{number}` in POSIX time zone \
1290 does not fit into 8-bit integer"
1291 )
1292 })?;
1293 if !(0 <= number && number <= 24) {
1294 return Err(err!(
1295 "parsed hour `{number}`, but hour in \
1296 POSIX time zone must be in range `0..=24`",
1297 ));
1298 }
1299 Ok(number)
1300 }
1301
1302 fn parse_minute(&self) -> Result<i8, Error> {
1310 let number = self
1311 .parse_number_with_exactly_n_digits(2)
1312 .map_err(|e| err!("invalid minute digits: {e}"))?;
1313 let number = i8::try_from(number).map_err(|_| {
1314 err!(
1315 "minute `{number}` in POSIX time zone \
1316 does not fit into 8-bit integer"
1317 )
1318 })?;
1319 if !(0 <= number && number <= 59) {
1320 return Err(err!(
1321 "parsed minute `{number}`, but minute in \
1322 POSIX time zone must be in range `0..=59`",
1323 ));
1324 }
1325 Ok(number)
1326 }
1327
1328 fn parse_second(&self) -> Result<i8, Error> {
1336 let number = self
1337 .parse_number_with_exactly_n_digits(2)
1338 .map_err(|e| err!("invalid second digits: {e}"))?;
1339 let number = i8::try_from(number).map_err(|_| {
1340 err!(
1341 "second `{number}` in POSIX time zone \
1342 does not fit into 8-bit integer"
1343 )
1344 })?;
1345 if !(0 <= number && number <= 59) {
1346 return Err(err!(
1347 "parsed second `{number}`, but second in \
1348 POSIX time zone must be in range `0..=59`",
1349 ));
1350 }
1351 Ok(number)
1352 }
1353
1354 fn parse_number_with_exactly_n_digits(
1363 &self,
1364 n: usize,
1365 ) -> Result<i32, Error> {
1366 assert!(n >= 1, "numbers must have at least 1 digit");
1367 let start = self.pos();
1368 let mut number: i32 = 0;
1369 for i in 0..n {
1370 if self.is_done() {
1371 return Err(err!("expected {n} digits, but found {i}"));
1372 }
1373 let byte = self.byte();
1374 let digit = match byte.checked_sub(b'0') {
1375 None => {
1376 return Err(err!(
1377 "invalid digit, expected 0-9 but got {}",
1378 Byte(byte),
1379 ));
1380 }
1381 Some(digit) if digit > 9 => {
1382 return Err(err!(
1383 "invalid digit, expected 0-9 but got {}",
1384 Byte(byte),
1385 ))
1386 }
1387 Some(digit) => {
1388 debug_assert!((0..=9).contains(&digit));
1389 i32::from(digit)
1390 }
1391 };
1392 number = number
1393 .checked_mul(10)
1394 .and_then(|n| n.checked_add(digit))
1395 .ok_or_else(|| {
1396 err!(
1397 "number `{}` too big to parse into 64-bit integer",
1398 Bytes(&self.tz[start..i]),
1399 )
1400 })?;
1401 self.bump();
1402 }
1403 Ok(number)
1404 }
1405
1406 fn parse_number_with_upto_n_digits(&self, n: usize) -> Result<i32, Error> {
1413 assert!(n >= 1, "numbers must have at least 1 digit");
1414 let start = self.pos();
1415 let mut number: i32 = 0;
1416 for i in 0..n {
1417 if self.is_done() || !self.byte().is_ascii_digit() {
1418 if i == 0 {
1419 return Err(err!("invalid number, no digits found"));
1420 }
1421 break;
1422 }
1423 let digit = i32::from(self.byte() - b'0');
1424 number = number
1425 .checked_mul(10)
1426 .and_then(|n| n.checked_add(digit))
1427 .ok_or_else(|| {
1428 err!(
1429 "number `{}` too big to parse into 64-bit integer",
1430 Bytes(&self.tz[start..i]),
1431 )
1432 })?;
1433 self.bump();
1434 }
1435 Ok(number)
1436 }
1437
1438 fn parse_optional_sign(&self) -> Result<Option<i8>, Error> {
1446 if self.is_done() {
1447 return Ok(None);
1448 }
1449 Ok(match self.byte() {
1450 b'-' => {
1451 if !self.bump() {
1452 return Err(err!(
1453 "expected digit after '-' sign, \
1454 but got end of input",
1455 ));
1456 }
1457 Some(-1)
1458 }
1459 b'+' => {
1460 if !self.bump() {
1461 return Err(err!(
1462 "expected digit after '+' sign, \
1463 but got end of input",
1464 ));
1465 }
1466 Some(1)
1467 }
1468 _ => None,
1469 })
1470 }
1471}
1472
1473impl<'s> Parser<'s> {
1475 fn bump(&self) -> bool {
1479 if self.is_done() {
1480 return false;
1481 }
1482 self.pos.set(
1483 self.pos().checked_add(1).expect("pos cannot overflow usize"),
1484 );
1485 !self.is_done()
1486 }
1487
1488 fn is_done(&self) -> bool {
1490 self.pos() == self.tz.len()
1491 }
1492
1493 fn byte(&self) -> u8 {
1498 self.tz[self.pos()]
1499 }
1500
1501 fn maybe_byte(&self) -> Option<u8> {
1504 self.tz.get(self.pos()).copied()
1505 }
1506
1507 fn pos(&self) -> usize {
1511 self.pos.get()
1512 }
1513
1514 fn remaining(&self) -> &'s [u8] {
1518 &self.tz[self.pos()..]
1519 }
1520}
1521
1522#[cfg(feature = "alloc")]
1524#[cfg(test)]
1525mod tests {
1526 use alloc::string::ToString;
1527
1528 use super::*;
1529
1530 fn posix_time_zone(
1531 input: impl AsRef<[u8]>,
1532 ) -> PosixTimeZone<Abbreviation> {
1533 let input = input.as_ref();
1534 let tz = PosixTimeZone::parse(input).unwrap();
1535 let reparsed =
1547 PosixTimeZone::parse(tz.to_string().as_bytes()).unwrap();
1548 assert_eq!(tz, reparsed);
1549 assert_eq!(tz.to_string(), reparsed.to_string());
1550 tz
1551 }
1552
1553 fn parser(s: &str) -> Parser<'_> {
1554 Parser::new(s.as_bytes())
1555 }
1556
1557 fn date(year: i16, month: i8, day: i8) -> IDate {
1558 IDate { year, month, day }
1559 }
1560
1561 #[test]
1562 fn parse() {
1563 let p = parser("NZST-12NZDT,J60,J300");
1564 assert_eq!(
1565 p.parse().unwrap(),
1566 PosixTimeZone {
1567 std_abbrev: "NZST".into(),
1568 std_offset: PosixOffset { second: 12 * 60 * 60 },
1569 dst: Some(PosixDst {
1570 abbrev: "NZDT".into(),
1571 offset: PosixOffset { second: 13 * 60 * 60 },
1572 rule: PosixRule {
1573 start: PosixDayTime {
1574 date: PosixDay::JulianOne(60),
1575 time: PosixTime { second: 2 * 60 * 60 },
1576 },
1577 end: PosixDayTime {
1578 date: PosixDay::JulianOne(300),
1579 time: PosixTime { second: 2 * 60 * 60 },
1580 },
1581 },
1582 }),
1583 },
1584 );
1585
1586 let p = Parser::new("NZST-12NZDT,J60,J300WAT");
1587 assert!(p.parse().is_err());
1588 }
1589
1590 #[test]
1591 fn parse_posix_time_zone() {
1592 let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3");
1593 assert_eq!(
1594 p.parse_posix_time_zone().unwrap(),
1595 PosixTimeZone {
1596 std_abbrev: "NZST".into(),
1597 std_offset: PosixOffset { second: 12 * 60 * 60 },
1598 dst: Some(PosixDst {
1599 abbrev: "NZDT".into(),
1600 offset: PosixOffset { second: 13 * 60 * 60 },
1601 rule: PosixRule {
1602 start: PosixDayTime {
1603 date: PosixDay::WeekdayOfMonth {
1604 month: 9,
1605 week: 5,
1606 weekday: 0,
1607 },
1608 time: PosixTime { second: 2 * 60 * 60 },
1609 },
1610 end: PosixDayTime {
1611 date: PosixDay::WeekdayOfMonth {
1612 month: 4,
1613 week: 1,
1614 weekday: 0,
1615 },
1616 time: PosixTime { second: 3 * 60 * 60 },
1617 },
1618 },
1619 }),
1620 },
1621 );
1622
1623 let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3WAT");
1624 assert_eq!(
1625 p.parse_posix_time_zone().unwrap(),
1626 PosixTimeZone {
1627 std_abbrev: "NZST".into(),
1628 std_offset: PosixOffset { second: 12 * 60 * 60 },
1629 dst: Some(PosixDst {
1630 abbrev: "NZDT".into(),
1631 offset: PosixOffset { second: 13 * 60 * 60 },
1632 rule: PosixRule {
1633 start: PosixDayTime {
1634 date: PosixDay::WeekdayOfMonth {
1635 month: 9,
1636 week: 5,
1637 weekday: 0,
1638 },
1639 time: PosixTime { second: 2 * 60 * 60 },
1640 },
1641 end: PosixDayTime {
1642 date: PosixDay::WeekdayOfMonth {
1643 month: 4,
1644 week: 1,
1645 weekday: 0,
1646 },
1647 time: PosixTime { second: 3 * 60 * 60 },
1648 },
1649 },
1650 }),
1651 },
1652 );
1653
1654 let p = Parser::new("NZST-12NZDT,J60,J300");
1655 assert_eq!(
1656 p.parse_posix_time_zone().unwrap(),
1657 PosixTimeZone {
1658 std_abbrev: "NZST".into(),
1659 std_offset: PosixOffset { second: 12 * 60 * 60 },
1660 dst: Some(PosixDst {
1661 abbrev: "NZDT".into(),
1662 offset: PosixOffset { second: 13 * 60 * 60 },
1663 rule: PosixRule {
1664 start: PosixDayTime {
1665 date: PosixDay::JulianOne(60),
1666 time: PosixTime { second: 2 * 60 * 60 },
1667 },
1668 end: PosixDayTime {
1669 date: PosixDay::JulianOne(300),
1670 time: PosixTime { second: 2 * 60 * 60 },
1671 },
1672 },
1673 }),
1674 },
1675 );
1676
1677 let p = Parser::new("NZST-12NZDT,J60,J300WAT");
1678 assert_eq!(
1679 p.parse_posix_time_zone().unwrap(),
1680 PosixTimeZone {
1681 std_abbrev: "NZST".into(),
1682 std_offset: PosixOffset { second: 12 * 60 * 60 },
1683 dst: Some(PosixDst {
1684 abbrev: "NZDT".into(),
1685 offset: PosixOffset { second: 13 * 60 * 60 },
1686 rule: PosixRule {
1687 start: PosixDayTime {
1688 date: PosixDay::JulianOne(60),
1689 time: PosixTime { second: 2 * 60 * 60 },
1690 },
1691 end: PosixDayTime {
1692 date: PosixDay::JulianOne(300),
1693 time: PosixTime { second: 2 * 60 * 60 },
1694 },
1695 },
1696 }),
1697 },
1698 );
1699 }
1700
1701 #[test]
1702 fn parse_posix_dst() {
1703 let p = Parser::new("NZDT,M9.5.0,M4.1.0/3");
1704 assert_eq!(
1705 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
1706 PosixDst {
1707 abbrev: "NZDT".into(),
1708 offset: PosixOffset { second: 13 * 60 * 60 },
1709 rule: PosixRule {
1710 start: PosixDayTime {
1711 date: PosixDay::WeekdayOfMonth {
1712 month: 9,
1713 week: 5,
1714 weekday: 0,
1715 },
1716 time: PosixTime { second: 2 * 60 * 60 },
1717 },
1718 end: PosixDayTime {
1719 date: PosixDay::WeekdayOfMonth {
1720 month: 4,
1721 week: 1,
1722 weekday: 0,
1723 },
1724 time: PosixTime { second: 3 * 60 * 60 },
1725 },
1726 },
1727 },
1728 );
1729
1730 let p = Parser::new("NZDT,J60,J300");
1731 assert_eq!(
1732 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
1733 PosixDst {
1734 abbrev: "NZDT".into(),
1735 offset: PosixOffset { second: 13 * 60 * 60 },
1736 rule: PosixRule {
1737 start: PosixDayTime {
1738 date: PosixDay::JulianOne(60),
1739 time: PosixTime { second: 2 * 60 * 60 },
1740 },
1741 end: PosixDayTime {
1742 date: PosixDay::JulianOne(300),
1743 time: PosixTime { second: 2 * 60 * 60 },
1744 },
1745 },
1746 },
1747 );
1748
1749 let p = Parser::new("NZDT-7,J60,J300");
1750 assert_eq!(
1751 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
1752 PosixDst {
1753 abbrev: "NZDT".into(),
1754 offset: PosixOffset { second: 7 * 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 let p = Parser::new("NZDT+7,J60,J300");
1769 assert_eq!(
1770 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
1771 PosixDst {
1772 abbrev: "NZDT".into(),
1773 offset: PosixOffset { second: -7 * 60 * 60 },
1774 rule: PosixRule {
1775 start: PosixDayTime {
1776 date: PosixDay::JulianOne(60),
1777 time: PosixTime { second: 2 * 60 * 60 },
1778 },
1779 end: PosixDayTime {
1780 date: PosixDay::JulianOne(300),
1781 time: PosixTime { second: 2 * 60 * 60 },
1782 },
1783 },
1784 },
1785 );
1786
1787 let p = Parser::new("NZDT7,J60,J300");
1788 assert_eq!(
1789 p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
1790 PosixDst {
1791 abbrev: "NZDT".into(),
1792 offset: PosixOffset { second: -7 * 60 * 60 },
1793 rule: PosixRule {
1794 start: PosixDayTime {
1795 date: PosixDay::JulianOne(60),
1796 time: PosixTime { second: 2 * 60 * 60 },
1797 },
1798 end: PosixDayTime {
1799 date: PosixDay::JulianOne(300),
1800 time: PosixTime { second: 2 * 60 * 60 },
1801 },
1802 },
1803 },
1804 );
1805
1806 let p = Parser::new("NZDT7,");
1807 assert!(p
1808 .parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 })
1809 .is_err());
1810
1811 let p = Parser::new("NZDT7!");
1812 assert!(p
1813 .parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 })
1814 .is_err());
1815 }
1816
1817 #[test]
1818 fn parse_abbreviation() {
1819 let p = Parser::new("ABC");
1820 assert_eq!(p.parse_abbreviation().unwrap(), "ABC");
1821
1822 let p = Parser::new("<ABC>");
1823 assert_eq!(p.parse_abbreviation().unwrap(), "ABC");
1824
1825 let p = Parser::new("<+09>");
1826 assert_eq!(p.parse_abbreviation().unwrap(), "+09");
1827
1828 let p = Parser::new("+09");
1829 assert!(p.parse_abbreviation().is_err());
1830 }
1831
1832 #[test]
1833 fn parse_unquoted_abbreviation() {
1834 let p = Parser::new("ABC");
1835 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC");
1836
1837 let p = Parser::new("ABCXYZ");
1838 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABCXYZ");
1839
1840 let p = Parser::new("ABC123");
1841 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC");
1842
1843 let tz = "a".repeat(30);
1844 let p = Parser::new(&tz);
1845 assert_eq!(p.parse_unquoted_abbreviation().unwrap(), &*tz);
1846
1847 let p = Parser::new("a");
1848 assert!(p.parse_unquoted_abbreviation().is_err());
1849
1850 let p = Parser::new("ab");
1851 assert!(p.parse_unquoted_abbreviation().is_err());
1852
1853 let p = Parser::new("ab1");
1854 assert!(p.parse_unquoted_abbreviation().is_err());
1855
1856 let tz = "a".repeat(31);
1857 let p = Parser::new(&tz);
1858 assert!(p.parse_unquoted_abbreviation().is_err());
1859
1860 let p = Parser::new(b"ab\xFFcd");
1861 assert!(p.parse_unquoted_abbreviation().is_err());
1862 }
1863
1864 #[test]
1865 fn parse_quoted_abbreviation() {
1866 let p = Parser::new("ABC>");
1871 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC");
1872
1873 let p = Parser::new("ABCXYZ>");
1874 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABCXYZ");
1875
1876 let p = Parser::new("ABC>123");
1877 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC");
1878
1879 let p = Parser::new("ABC123>");
1880 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC123");
1881
1882 let p = Parser::new("ab1>");
1883 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ab1");
1884
1885 let p = Parser::new("+09>");
1886 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "+09");
1887
1888 let p = Parser::new("-09>");
1889 assert_eq!(p.parse_quoted_abbreviation().unwrap(), "-09");
1890
1891 let tz = alloc::format!("{}>", "a".repeat(30));
1892 let p = Parser::new(&tz);
1893 assert_eq!(
1894 p.parse_quoted_abbreviation().unwrap(),
1895 tz.trim_end_matches(">")
1896 );
1897
1898 let p = Parser::new("a>");
1899 assert!(p.parse_quoted_abbreviation().is_err());
1900
1901 let p = Parser::new("ab>");
1902 assert!(p.parse_quoted_abbreviation().is_err());
1903
1904 let tz = alloc::format!("{}>", "a".repeat(31));
1905 let p = Parser::new(&tz);
1906 assert!(p.parse_quoted_abbreviation().is_err());
1907
1908 let p = Parser::new(b"ab\xFFcd>");
1909 assert!(p.parse_quoted_abbreviation().is_err());
1910
1911 let p = Parser::new("ABC");
1912 assert!(p.parse_quoted_abbreviation().is_err());
1913
1914 let p = Parser::new("ABC!>");
1915 assert!(p.parse_quoted_abbreviation().is_err());
1916 }
1917
1918 #[test]
1919 fn parse_posix_offset() {
1920 let p = Parser::new("5");
1921 assert_eq!(p.parse_posix_offset().unwrap().second, -5 * 60 * 60);
1922
1923 let p = Parser::new("+5");
1924 assert_eq!(p.parse_posix_offset().unwrap().second, -5 * 60 * 60);
1925
1926 let p = Parser::new("-5");
1927 assert_eq!(p.parse_posix_offset().unwrap().second, 5 * 60 * 60);
1928
1929 let p = Parser::new("-12:34:56");
1930 assert_eq!(
1931 p.parse_posix_offset().unwrap().second,
1932 12 * 60 * 60 + 34 * 60 + 56,
1933 );
1934
1935 let p = Parser::new("a");
1936 assert!(p.parse_posix_offset().is_err());
1937
1938 let p = Parser::new("-");
1939 assert!(p.parse_posix_offset().is_err());
1940
1941 let p = Parser::new("+");
1942 assert!(p.parse_posix_offset().is_err());
1943
1944 let p = Parser::new("-a");
1945 assert!(p.parse_posix_offset().is_err());
1946
1947 let p = Parser::new("+a");
1948 assert!(p.parse_posix_offset().is_err());
1949
1950 let p = Parser::new("-25");
1951 assert!(p.parse_posix_offset().is_err());
1952
1953 let p = Parser::new("+25");
1954 assert!(p.parse_posix_offset().is_err());
1955
1956 let p = Parser { ianav3plus: true, ..Parser::new("25") };
1964 assert!(p.parse_posix_offset().is_err());
1965 let p = Parser { ianav3plus: true, ..Parser::new("+25") };
1966 assert!(p.parse_posix_offset().is_err());
1967 let p = Parser { ianav3plus: true, ..Parser::new("-25") };
1968 assert!(p.parse_posix_offset().is_err());
1969 }
1970
1971 #[test]
1972 fn parse_rule() {
1973 let p = Parser::new("M9.5.0,M4.1.0/3");
1974 assert_eq!(
1975 p.parse_rule().unwrap(),
1976 PosixRule {
1977 start: PosixDayTime {
1978 date: PosixDay::WeekdayOfMonth {
1979 month: 9,
1980 week: 5,
1981 weekday: 0,
1982 },
1983 time: PosixTime { second: 2 * 60 * 60 },
1984 },
1985 end: PosixDayTime {
1986 date: PosixDay::WeekdayOfMonth {
1987 month: 4,
1988 week: 1,
1989 weekday: 0,
1990 },
1991 time: PosixTime { second: 3 * 60 * 60 },
1992 },
1993 },
1994 );
1995
1996 let p = Parser::new("M9.5.0");
1997 assert!(p.parse_rule().is_err());
1998
1999 let p = Parser::new(",M9.5.0,M4.1.0/3");
2000 assert!(p.parse_rule().is_err());
2001
2002 let p = Parser::new("M9.5.0/");
2003 assert!(p.parse_rule().is_err());
2004
2005 let p = Parser::new("M9.5.0,M4.1.0/");
2006 assert!(p.parse_rule().is_err());
2007 }
2008
2009 #[test]
2010 fn parse_posix_datetime() {
2011 let p = Parser::new("J1");
2012 assert_eq!(
2013 p.parse_posix_datetime().unwrap(),
2014 PosixDayTime {
2015 date: PosixDay::JulianOne(1),
2016 time: PosixTime { second: 2 * 60 * 60 }
2017 },
2018 );
2019
2020 let p = Parser::new("J1/3");
2021 assert_eq!(
2022 p.parse_posix_datetime().unwrap(),
2023 PosixDayTime {
2024 date: PosixDay::JulianOne(1),
2025 time: PosixTime { second: 3 * 60 * 60 }
2026 },
2027 );
2028
2029 let p = Parser::new("M4.1.0/3");
2030 assert_eq!(
2031 p.parse_posix_datetime().unwrap(),
2032 PosixDayTime {
2033 date: PosixDay::WeekdayOfMonth {
2034 month: 4,
2035 week: 1,
2036 weekday: 0,
2037 },
2038 time: PosixTime { second: 3 * 60 * 60 },
2039 },
2040 );
2041
2042 let p = Parser::new("1/3:45:05");
2043 assert_eq!(
2044 p.parse_posix_datetime().unwrap(),
2045 PosixDayTime {
2046 date: PosixDay::JulianZero(1),
2047 time: PosixTime { second: 3 * 60 * 60 + 45 * 60 + 5 },
2048 },
2049 );
2050
2051 let p = Parser::new("a");
2052 assert!(p.parse_posix_datetime().is_err());
2053
2054 let p = Parser::new("J1/");
2055 assert!(p.parse_posix_datetime().is_err());
2056
2057 let p = Parser::new("1/");
2058 assert!(p.parse_posix_datetime().is_err());
2059
2060 let p = Parser::new("M4.1.0/");
2061 assert!(p.parse_posix_datetime().is_err());
2062 }
2063
2064 #[test]
2065 fn parse_posix_date() {
2066 let p = Parser::new("J1");
2067 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianOne(1));
2068 let p = Parser::new("J365");
2069 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianOne(365));
2070
2071 let p = Parser::new("0");
2072 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(0));
2073 let p = Parser::new("1");
2074 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(1));
2075 let p = Parser::new("365");
2076 assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(365));
2077
2078 let p = Parser::new("M9.5.0");
2079 assert_eq!(
2080 p.parse_posix_date().unwrap(),
2081 PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 0 },
2082 );
2083 let p = Parser::new("M9.5.6");
2084 assert_eq!(
2085 p.parse_posix_date().unwrap(),
2086 PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 6 },
2087 );
2088 let p = Parser::new("M09.5.6");
2089 assert_eq!(
2090 p.parse_posix_date().unwrap(),
2091 PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 6 },
2092 );
2093 let p = Parser::new("M12.1.1");
2094 assert_eq!(
2095 p.parse_posix_date().unwrap(),
2096 PosixDay::WeekdayOfMonth { month: 12, week: 1, weekday: 1 },
2097 );
2098
2099 let p = Parser::new("a");
2100 assert!(p.parse_posix_date().is_err());
2101
2102 let p = Parser::new("j");
2103 assert!(p.parse_posix_date().is_err());
2104
2105 let p = Parser::new("m");
2106 assert!(p.parse_posix_date().is_err());
2107
2108 let p = Parser::new("n");
2109 assert!(p.parse_posix_date().is_err());
2110
2111 let p = Parser::new("J366");
2112 assert!(p.parse_posix_date().is_err());
2113
2114 let p = Parser::new("366");
2115 assert!(p.parse_posix_date().is_err());
2116 }
2117
2118 #[test]
2119 fn parse_posix_julian_day_no_leap() {
2120 let p = Parser::new("1");
2121 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1);
2122
2123 let p = Parser::new("001");
2124 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1);
2125
2126 let p = Parser::new("365");
2127 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365);
2128
2129 let p = Parser::new("3655");
2130 assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365);
2131
2132 let p = Parser::new("0");
2133 assert!(p.parse_posix_julian_day_no_leap().is_err());
2134
2135 let p = Parser::new("366");
2136 assert!(p.parse_posix_julian_day_no_leap().is_err());
2137 }
2138
2139 #[test]
2140 fn parse_posix_julian_day_with_leap() {
2141 let p = Parser::new("0");
2142 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 0);
2143
2144 let p = Parser::new("1");
2145 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1);
2146
2147 let p = Parser::new("001");
2148 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1);
2149
2150 let p = Parser::new("365");
2151 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365);
2152
2153 let p = Parser::new("3655");
2154 assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365);
2155
2156 let p = Parser::new("366");
2157 assert!(p.parse_posix_julian_day_with_leap().is_err());
2158 }
2159
2160 #[test]
2161 fn parse_weekday_of_month() {
2162 let p = Parser::new("9.5.0");
2163 assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 5, 0));
2164
2165 let p = Parser::new("9.1.6");
2166 assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 1, 6));
2167
2168 let p = Parser::new("09.1.6");
2169 assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 1, 6));
2170
2171 let p = Parser::new("9");
2172 assert!(p.parse_weekday_of_month().is_err());
2173
2174 let p = Parser::new("9.");
2175 assert!(p.parse_weekday_of_month().is_err());
2176
2177 let p = Parser::new("9.5");
2178 assert!(p.parse_weekday_of_month().is_err());
2179
2180 let p = Parser::new("9.5.");
2181 assert!(p.parse_weekday_of_month().is_err());
2182
2183 let p = Parser::new("0.5.0");
2184 assert!(p.parse_weekday_of_month().is_err());
2185
2186 let p = Parser::new("13.5.0");
2187 assert!(p.parse_weekday_of_month().is_err());
2188
2189 let p = Parser::new("9.0.0");
2190 assert!(p.parse_weekday_of_month().is_err());
2191
2192 let p = Parser::new("9.6.0");
2193 assert!(p.parse_weekday_of_month().is_err());
2194
2195 let p = Parser::new("9.5.7");
2196 assert!(p.parse_weekday_of_month().is_err());
2197 }
2198
2199 #[test]
2200 fn parse_posix_time() {
2201 let p = Parser::new("5");
2202 assert_eq!(p.parse_posix_time().unwrap().second, 5 * 60 * 60);
2203
2204 let p = Parser::new("22");
2205 assert_eq!(p.parse_posix_time().unwrap().second, 22 * 60 * 60);
2206
2207 let p = Parser::new("02");
2208 assert_eq!(p.parse_posix_time().unwrap().second, 2 * 60 * 60);
2209
2210 let p = Parser::new("5:45");
2211 assert_eq!(
2212 p.parse_posix_time().unwrap().second,
2213 5 * 60 * 60 + 45 * 60
2214 );
2215
2216 let p = Parser::new("5:45:12");
2217 assert_eq!(
2218 p.parse_posix_time().unwrap().second,
2219 5 * 60 * 60 + 45 * 60 + 12
2220 );
2221
2222 let p = Parser::new("5:45:129");
2223 assert_eq!(
2224 p.parse_posix_time().unwrap().second,
2225 5 * 60 * 60 + 45 * 60 + 12
2226 );
2227
2228 let p = Parser::new("5:45:12:");
2229 assert_eq!(
2230 p.parse_posix_time().unwrap().second,
2231 5 * 60 * 60 + 45 * 60 + 12
2232 );
2233
2234 let p = Parser { ianav3plus: true, ..Parser::new("+5:45:12") };
2235 assert_eq!(
2236 p.parse_posix_time().unwrap().second,
2237 5 * 60 * 60 + 45 * 60 + 12
2238 );
2239
2240 let p = Parser { ianav3plus: true, ..Parser::new("-5:45:12") };
2241 assert_eq!(
2242 p.parse_posix_time().unwrap().second,
2243 -(5 * 60 * 60 + 45 * 60 + 12)
2244 );
2245
2246 let p = Parser { ianav3plus: true, ..Parser::new("-167:45:12") };
2247 assert_eq!(
2248 p.parse_posix_time().unwrap().second,
2249 -(167 * 60 * 60 + 45 * 60 + 12),
2250 );
2251
2252 let p = Parser::new("25");
2253 assert!(p.parse_posix_time().is_err());
2254
2255 let p = Parser::new("12:2");
2256 assert!(p.parse_posix_time().is_err());
2257
2258 let p = Parser::new("12:");
2259 assert!(p.parse_posix_time().is_err());
2260
2261 let p = Parser::new("12:23:5");
2262 assert!(p.parse_posix_time().is_err());
2263
2264 let p = Parser::new("12:23:");
2265 assert!(p.parse_posix_time().is_err());
2266
2267 let p = Parser { ianav3plus: true, ..Parser::new("168") };
2268 assert!(p.parse_posix_time().is_err());
2269
2270 let p = Parser { ianav3plus: true, ..Parser::new("-168") };
2271 assert!(p.parse_posix_time().is_err());
2272
2273 let p = Parser { ianav3plus: true, ..Parser::new("+168") };
2274 assert!(p.parse_posix_time().is_err());
2275 }
2276
2277 #[test]
2278 fn parse_month() {
2279 let p = Parser::new("1");
2280 assert_eq!(p.parse_month().unwrap(), 1);
2281
2282 let p = Parser::new("01");
2287 assert_eq!(p.parse_month().unwrap(), 1);
2288
2289 let p = Parser::new("12");
2290 assert_eq!(p.parse_month().unwrap(), 12);
2291
2292 let p = Parser::new("0");
2293 assert!(p.parse_month().is_err());
2294
2295 let p = Parser::new("00");
2296 assert!(p.parse_month().is_err());
2297
2298 let p = Parser::new("001");
2299 assert!(p.parse_month().is_err());
2300
2301 let p = Parser::new("13");
2302 assert!(p.parse_month().is_err());
2303 }
2304
2305 #[test]
2306 fn parse_week() {
2307 let p = Parser::new("1");
2308 assert_eq!(p.parse_week().unwrap(), 1);
2309
2310 let p = Parser::new("5");
2311 assert_eq!(p.parse_week().unwrap(), 5);
2312
2313 let p = Parser::new("55");
2314 assert_eq!(p.parse_week().unwrap(), 5);
2315
2316 let p = Parser::new("0");
2317 assert!(p.parse_week().is_err());
2318
2319 let p = Parser::new("6");
2320 assert!(p.parse_week().is_err());
2321
2322 let p = Parser::new("00");
2323 assert!(p.parse_week().is_err());
2324
2325 let p = Parser::new("01");
2326 assert!(p.parse_week().is_err());
2327
2328 let p = Parser::new("05");
2329 assert!(p.parse_week().is_err());
2330 }
2331
2332 #[test]
2333 fn parse_weekday() {
2334 let p = Parser::new("0");
2335 assert_eq!(p.parse_weekday().unwrap(), 0);
2336
2337 let p = Parser::new("1");
2338 assert_eq!(p.parse_weekday().unwrap(), 1);
2339
2340 let p = Parser::new("6");
2341 assert_eq!(p.parse_weekday().unwrap(), 6);
2342
2343 let p = Parser::new("00");
2344 assert_eq!(p.parse_weekday().unwrap(), 0);
2345
2346 let p = Parser::new("06");
2347 assert_eq!(p.parse_weekday().unwrap(), 0);
2348
2349 let p = Parser::new("60");
2350 assert_eq!(p.parse_weekday().unwrap(), 6);
2351
2352 let p = Parser::new("7");
2353 assert!(p.parse_weekday().is_err());
2354 }
2355
2356 #[test]
2357 fn parse_hour_posix() {
2358 let p = Parser::new("5");
2359 assert_eq!(p.parse_hour_posix().unwrap(), 5);
2360
2361 let p = Parser::new("0");
2362 assert_eq!(p.parse_hour_posix().unwrap(), 0);
2363
2364 let p = Parser::new("00");
2365 assert_eq!(p.parse_hour_posix().unwrap(), 0);
2366
2367 let p = Parser::new("24");
2368 assert_eq!(p.parse_hour_posix().unwrap(), 24);
2369
2370 let p = Parser::new("100");
2371 assert_eq!(p.parse_hour_posix().unwrap(), 10);
2372
2373 let p = Parser::new("25");
2374 assert!(p.parse_hour_posix().is_err());
2375
2376 let p = Parser::new("99");
2377 assert!(p.parse_hour_posix().is_err());
2378 }
2379
2380 #[test]
2381 fn parse_hour_ianav3plus() {
2382 let new = |input| Parser { ianav3plus: true, ..Parser::new(input) };
2383
2384 let p = new("5");
2385 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 5);
2386
2387 let p = new("0");
2388 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
2389
2390 let p = new("00");
2391 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
2392
2393 let p = new("000");
2394 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
2395
2396 let p = new("24");
2397 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 24);
2398
2399 let p = new("100");
2400 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100);
2401
2402 let p = new("1000");
2403 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100);
2404
2405 let p = new("167");
2406 assert_eq!(p.parse_hour_ianav3plus().unwrap(), 167);
2407
2408 let p = new("168");
2409 assert!(p.parse_hour_ianav3plus().is_err());
2410
2411 let p = new("999");
2412 assert!(p.parse_hour_ianav3plus().is_err());
2413 }
2414
2415 #[test]
2416 fn parse_minute() {
2417 let p = Parser::new("00");
2418 assert_eq!(p.parse_minute().unwrap(), 0);
2419
2420 let p = Parser::new("24");
2421 assert_eq!(p.parse_minute().unwrap(), 24);
2422
2423 let p = Parser::new("59");
2424 assert_eq!(p.parse_minute().unwrap(), 59);
2425
2426 let p = Parser::new("599");
2427 assert_eq!(p.parse_minute().unwrap(), 59);
2428
2429 let p = Parser::new("0");
2430 assert!(p.parse_minute().is_err());
2431
2432 let p = Parser::new("1");
2433 assert!(p.parse_minute().is_err());
2434
2435 let p = Parser::new("9");
2436 assert!(p.parse_minute().is_err());
2437
2438 let p = Parser::new("60");
2439 assert!(p.parse_minute().is_err());
2440 }
2441
2442 #[test]
2443 fn parse_second() {
2444 let p = Parser::new("00");
2445 assert_eq!(p.parse_second().unwrap(), 0);
2446
2447 let p = Parser::new("24");
2448 assert_eq!(p.parse_second().unwrap(), 24);
2449
2450 let p = Parser::new("59");
2451 assert_eq!(p.parse_second().unwrap(), 59);
2452
2453 let p = Parser::new("599");
2454 assert_eq!(p.parse_second().unwrap(), 59);
2455
2456 let p = Parser::new("0");
2457 assert!(p.parse_second().is_err());
2458
2459 let p = Parser::new("1");
2460 assert!(p.parse_second().is_err());
2461
2462 let p = Parser::new("9");
2463 assert!(p.parse_second().is_err());
2464
2465 let p = Parser::new("60");
2466 assert!(p.parse_second().is_err());
2467 }
2468
2469 #[test]
2470 fn parse_number_with_exactly_n_digits() {
2471 let p = Parser::new("1");
2472 assert_eq!(p.parse_number_with_exactly_n_digits(1).unwrap(), 1);
2473
2474 let p = Parser::new("12");
2475 assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12);
2476
2477 let p = Parser::new("123");
2478 assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12);
2479
2480 let p = Parser::new("");
2481 assert!(p.parse_number_with_exactly_n_digits(1).is_err());
2482
2483 let p = Parser::new("1");
2484 assert!(p.parse_number_with_exactly_n_digits(2).is_err());
2485
2486 let p = Parser::new("12");
2487 assert!(p.parse_number_with_exactly_n_digits(3).is_err());
2488 }
2489
2490 #[test]
2491 fn parse_number_with_upto_n_digits() {
2492 let p = Parser::new("1");
2493 assert_eq!(p.parse_number_with_upto_n_digits(1).unwrap(), 1);
2494
2495 let p = Parser::new("1");
2496 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 1);
2497
2498 let p = Parser::new("12");
2499 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12);
2500
2501 let p = Parser::new("12");
2502 assert_eq!(p.parse_number_with_upto_n_digits(3).unwrap(), 12);
2503
2504 let p = Parser::new("123");
2505 assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12);
2506
2507 let p = Parser::new("");
2508 assert!(p.parse_number_with_upto_n_digits(1).is_err());
2509
2510 let p = Parser::new("a");
2511 assert!(p.parse_number_with_upto_n_digits(1).is_err());
2512 }
2513
2514 #[test]
2515 fn to_dst_civil_datetime_utc_range() {
2516 let tz = posix_time_zone("WART4WARST,J1/-3,J365/20");
2517 let dst_info = DstInfo {
2518 dst: tz.dst.as_ref().unwrap(),
2522 start: date(2024, 1, 1).at(1, 0, 0, 0),
2523 end: date(2024, 12, 31).at(23, 0, 0, 0),
2524 };
2525 assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
2526
2527 let tz = posix_time_zone("WART4WARST,J1/-4,J365/21");
2528 let dst_info = DstInfo {
2529 dst: tz.dst.as_ref().unwrap(),
2530 start: date(2024, 1, 1).at(0, 0, 0, 0),
2531 end: date(2024, 12, 31).at(23, 59, 59, 999_999_999),
2532 };
2533 assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
2534
2535 let tz = posix_time_zone("EST5EDT,M3.2.0,M11.1.0");
2536 let dst_info = DstInfo {
2537 dst: tz.dst.as_ref().unwrap(),
2538 start: date(2024, 3, 10).at(7, 0, 0, 0),
2539 end: date(2024, 11, 3).at(6, 0, 0, 0),
2540 };
2541 assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
2542 }
2543
2544 #[test]
2545 fn reasonable() {
2546 assert!(PosixTimeZone::parse(b"EST5").is_ok());
2547 assert!(PosixTimeZone::parse(b"EST5EDT").is_err());
2548 assert!(PosixTimeZone::parse(b"EST5EDT,J1,J365").is_ok());
2549
2550 let tz = posix_time_zone("EST24EDT,J1,J365");
2551 assert_eq!(
2552 tz,
2553 PosixTimeZone {
2554 std_abbrev: "EST".into(),
2555 std_offset: PosixOffset { second: -24 * 60 * 60 },
2556 dst: Some(PosixDst {
2557 abbrev: "EDT".into(),
2558 offset: PosixOffset { second: -23 * 60 * 60 },
2559 rule: PosixRule {
2560 start: PosixDayTime {
2561 date: PosixDay::JulianOne(1),
2562 time: PosixTime::DEFAULT,
2563 },
2564 end: PosixDayTime {
2565 date: PosixDay::JulianOne(365),
2566 time: PosixTime::DEFAULT,
2567 },
2568 },
2569 }),
2570 },
2571 );
2572
2573 let tz = posix_time_zone("EST-24EDT,J1,J365");
2574 assert_eq!(
2575 tz,
2576 PosixTimeZone {
2577 std_abbrev: "EST".into(),
2578 std_offset: PosixOffset { second: 24 * 60 * 60 },
2579 dst: Some(PosixDst {
2580 abbrev: "EDT".into(),
2581 offset: PosixOffset { second: 25 * 60 * 60 },
2582 rule: PosixRule {
2583 start: PosixDayTime {
2584 date: PosixDay::JulianOne(1),
2585 time: PosixTime::DEFAULT,
2586 },
2587 end: PosixDayTime {
2588 date: PosixDay::JulianOne(365),
2589 time: PosixTime::DEFAULT,
2590 },
2591 },
2592 }),
2593 },
2594 );
2595 }
2596
2597 #[test]
2598 fn posix_date_time_spec_to_datetime() {
2599 let to_datetime = |daytime: &PosixDayTime, year: i16| {
2602 daytime.to_datetime(year, IOffset::UTC)
2603 };
2604
2605 let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
2606 assert_eq!(
2607 to_datetime(&tz.rule().start, 2023),
2608 date(2023, 1, 1).at(2, 0, 0, 0),
2609 );
2610 assert_eq!(
2611 to_datetime(&tz.rule().end, 2023),
2612 date(2023, 12, 31).at(5, 12, 34, 0),
2613 );
2614
2615 let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2");
2616 assert_eq!(
2617 to_datetime(&tz.rule().start, 2024),
2618 date(2024, 3, 10).at(2, 0, 0, 0),
2619 );
2620 assert_eq!(
2621 to_datetime(&tz.rule().end, 2024),
2622 date(2024, 11, 3).at(2, 0, 0, 0),
2623 );
2624
2625 let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2");
2626 assert_eq!(
2627 to_datetime(&tz.rule().start, 2024),
2628 date(2024, 1, 1).at(2, 0, 0, 0),
2629 );
2630 assert_eq!(
2631 to_datetime(&tz.rule().end, 2024),
2632 date(2024, 12, 31).at(2, 0, 0, 0),
2633 );
2634
2635 let tz = posix_time_zone("EST5EDT,0/0,J365/25");
2636 assert_eq!(
2637 to_datetime(&tz.rule().start, 2024),
2638 date(2024, 1, 1).at(0, 0, 0, 0),
2639 );
2640 assert_eq!(
2641 to_datetime(&tz.rule().end, 2024),
2642 date(2024, 12, 31).at(23, 59, 59, 999_999_999),
2643 );
2644
2645 let tz = posix_time_zone("XXX3EDT4,0/0,J365/23");
2646 assert_eq!(
2647 to_datetime(&tz.rule().start, 2024),
2648 date(2024, 1, 1).at(0, 0, 0, 0),
2649 );
2650 assert_eq!(
2651 to_datetime(&tz.rule().end, 2024),
2652 date(2024, 12, 31).at(23, 0, 0, 0),
2653 );
2654
2655 let tz = posix_time_zone("XXX3EDT4,0/0,365");
2656 assert_eq!(
2657 to_datetime(&tz.rule().end, 2023),
2658 date(2023, 12, 31).at(23, 59, 59, 999_999_999),
2659 );
2660 assert_eq!(
2661 to_datetime(&tz.rule().end, 2024),
2662 date(2024, 12, 31).at(2, 0, 0, 0),
2663 );
2664
2665 let tz = posix_time_zone("XXX3EDT4,J1/-167:59:59,J365/167:59:59");
2666 assert_eq!(
2667 to_datetime(&tz.rule().start, 2024),
2668 date(2024, 1, 1).at(0, 0, 0, 0),
2669 );
2670 assert_eq!(
2671 to_datetime(&tz.rule().end, 2024),
2672 date(2024, 12, 31).at(23, 59, 59, 999_999_999),
2673 );
2674 }
2675
2676 #[test]
2677 fn posix_date_time_spec_time() {
2678 let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
2679 assert_eq!(tz.rule().start.time, PosixTime::DEFAULT);
2680 assert_eq!(
2681 tz.rule().end.time,
2682 PosixTime { second: 5 * 60 * 60 + 12 * 60 + 34 },
2683 );
2684 }
2685
2686 #[test]
2687 fn posix_date_spec_to_date() {
2688 let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2");
2689 let start = tz.rule().start.date.to_date(2023);
2690 assert_eq!(start, Some(date(2023, 3, 12)));
2691 let end = tz.rule().end.date.to_date(2023);
2692 assert_eq!(end, Some(date(2023, 11, 5)));
2693 let start = tz.rule().start.date.to_date(2024);
2694 assert_eq!(start, Some(date(2024, 3, 10)));
2695 let end = tz.rule().end.date.to_date(2024);
2696 assert_eq!(end, Some(date(2024, 11, 3)));
2697
2698 let tz = posix_time_zone("EST+5EDT,J60,J365");
2699 let start = tz.rule().start.date.to_date(2023);
2700 assert_eq!(start, Some(date(2023, 3, 1)));
2701 let end = tz.rule().end.date.to_date(2023);
2702 assert_eq!(end, Some(date(2023, 12, 31)));
2703 let start = tz.rule().start.date.to_date(2024);
2704 assert_eq!(start, Some(date(2024, 3, 1)));
2705 let end = tz.rule().end.date.to_date(2024);
2706 assert_eq!(end, Some(date(2024, 12, 31)));
2707
2708 let tz = posix_time_zone("EST+5EDT,59,365");
2709 let start = tz.rule().start.date.to_date(2023);
2710 assert_eq!(start, Some(date(2023, 3, 1)));
2711 let end = tz.rule().end.date.to_date(2023);
2712 assert_eq!(end, None);
2713 let start = tz.rule().start.date.to_date(2024);
2714 assert_eq!(start, Some(date(2024, 2, 29)));
2715 let end = tz.rule().end.date.to_date(2024);
2716 assert_eq!(end, Some(date(2024, 12, 31)));
2717
2718 let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2");
2719 let start = tz.rule().start.date.to_date(2024);
2720 assert_eq!(start, Some(date(2024, 1, 1)));
2721 let end = tz.rule().end.date.to_date(2024);
2722 assert_eq!(end, Some(date(2024, 12, 31)));
2723 }
2724
2725 #[test]
2726 fn posix_time_spec_to_civil_time() {
2727 let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
2728 assert_eq!(
2729 tz.dst.as_ref().unwrap().rule.start.time.second,
2730 2 * 60 * 60,
2731 );
2732 assert_eq!(
2733 tz.dst.as_ref().unwrap().rule.end.time.second,
2734 5 * 60 * 60 + 12 * 60 + 34,
2735 );
2736
2737 let tz = posix_time_zone("EST5EDT,J1/23:59:59,J365/24:00:00");
2738 assert_eq!(
2739 tz.dst.as_ref().unwrap().rule.start.time.second,
2740 23 * 60 * 60 + 59 * 60 + 59,
2741 );
2742 assert_eq!(
2743 tz.dst.as_ref().unwrap().rule.end.time.second,
2744 24 * 60 * 60,
2745 );
2746
2747 let tz = posix_time_zone("EST5EDT,J1/-1,J365/167:00:00");
2748 assert_eq!(
2749 tz.dst.as_ref().unwrap().rule.start.time.second,
2750 -1 * 60 * 60,
2751 );
2752 assert_eq!(
2753 tz.dst.as_ref().unwrap().rule.end.time.second,
2754 167 * 60 * 60,
2755 );
2756 }
2757
2758 #[test]
2759 fn parse_iana() {
2760 let p = PosixTimeZone::parse(b"CRAZY5SHORT,M12.5.0/50,0/2").unwrap();
2762 assert_eq!(
2763 p,
2764 PosixTimeZone {
2765 std_abbrev: "CRAZY".into(),
2766 std_offset: PosixOffset { second: -5 * 60 * 60 },
2767 dst: Some(PosixDst {
2768 abbrev: "SHORT".into(),
2769 offset: PosixOffset { second: -4 * 60 * 60 },
2770 rule: PosixRule {
2771 start: PosixDayTime {
2772 date: PosixDay::WeekdayOfMonth {
2773 month: 12,
2774 week: 5,
2775 weekday: 0,
2776 },
2777 time: PosixTime { second: 50 * 60 * 60 },
2778 },
2779 end: PosixDayTime {
2780 date: PosixDay::JulianZero(0),
2781 time: PosixTime { second: 2 * 60 * 60 },
2782 },
2783 },
2784 }),
2785 },
2786 );
2787
2788 assert!(PosixTimeZone::parse(b"America/New_York").is_err());
2789 assert!(PosixTimeZone::parse(b":America/New_York").is_err());
2790 }
2791}