jiff/shared/
posix.rs

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    /// Parse a POSIX `TZ` environment variable, assuming it's a rule and not
17    /// an implementation defined value, from the given bytes.
18    #[cfg(feature = "alloc")]
19    pub fn parse(bytes: &[u8]) -> Result<PosixTimeZone<Abbreviation>, Error> {
20        // We enable the IANA v3+ extensions here. (Namely, that the time
21        // specification hour value has the range `-167..=167` instead of
22        // `0..=24`.) Requiring strict POSIX rules doesn't seem necessary
23        // since the extension is a strict superset. Plus, GNU tooling
24        // seems to accept the extension.
25        let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
26        parser.parse()
27    }
28
29    // only-jiff-start
30    /// Like parse, but parses a prefix of the input given and returns whatever
31    /// is remaining.
32    #[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    // only-jiff-end
40}
41
42impl<ABBREV: AsRef<str>> PosixTimeZone<ABBREV> {
43    /// Returns the appropriate time zone offset to use for the given
44    /// timestamp.
45    ///
46    /// If you need information like whether the offset is in DST or not, or
47    /// the time zone abbreviation, then use `PosixTimeZone::to_offset_info`.
48    /// But that API may be more expensive to use, so only use it if you need
49    /// the additional data.
50    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    /// Returns the appropriate time zone offset to use for the given
64    /// timestamp.
65    ///
66    /// This also includes whether the offset returned should be considered
67    /// to be "DST" or not, along with the time zone abbreviation (e.g., EST
68    /// for standard time in New York, and EDT for DST in New York).
69    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    /// Returns a possibly ambiguous timestamp for the given civil datetime.
92    ///
93    /// The given datetime should correspond to the "wall" clock time of what
94    /// humans use to tell time for this time zone.
95    ///
96    /// Note that "ambiguous timestamp" is represented by the possible
97    /// selection of offsets that could be applied to the given datetime. In
98    /// general, it is only ambiguous around transitions to-and-from DST. The
99    /// ambiguity can arise as a "fold" (when a particular wall clock time is
100    /// repeated) or as a "gap" (when a particular wall clock time is skipped
101    /// entirely).
102    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        // When the difference between DST and standard is positive, that means
111        // STD->DST results in a gap while DST->STD results in a fold. However,
112        // when the difference is negative, that means STD->DST results in a
113        // fold while DST->STD results in a gap. The former is by far the most
114        // common. The latter is a bit weird, but real cases do exist. For
115        // example, Dublin has DST in winter (UTC+01) and STD in the summer
116        // (UTC+00).
117        //
118        // When the difference is zero, then we have a weird POSIX time zone
119        // where a DST transition rule was specified, but was set to explicitly
120        // be the same as STD. In this case, there can be no ambiguity. (The
121        // zero case is strictly redundant. Both the diff < 0 and diff > 0
122        // cases handle the zero case correctly. But we write it out for
123        // clarity.)
124        if diff == 0 {
125            debug_assert_eq!(std_offset, dst_offset);
126            IAmbiguousOffset::Unambiguous { offset: std_offset }
127        } else if diff.is_negative() {
128            // For DST transitions that always move behind one hour, ambiguous
129            // timestamps only occur when the given civil datetime falls in the
130            // standard time range.
131            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            // For DST transitions that always move ahead one hour, ambiguous
153            // timestamps only occur when the given civil datetime falls in the
154            // DST range.
155            if !dst_info.in_dst(dt) {
156                IAmbiguousOffset::Unambiguous { offset: std_offset }
157            } else {
158                // PERF: I wonder if it makes sense to pre-compute these?
159                // Probably not, because we have to do it based on year of
160                // datetime given. But if we ever add a "caching" layer for
161                // POSIX time zones, then it might be worth adding these to it.
162                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    /// Returns the timestamp of the most recent time zone transition prior
183    /// to the timestamp given. If one doesn't exist, `None` is returned.
184    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    /// Returns the timestamp of the soonest time zone transition after the
213    /// timestamp given. If one doesn't exist, `None` is returned.
214    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    /// Returns the range in which DST occurs.
243    ///
244    /// The civil datetimes returned are in UTC. This is useful for determining
245    /// whether a timestamp is in DST or not.
246    fn dst_info_utc(&self, year: i16) -> Option<DstInfo<'_, ABBREV>> {
247        let dst = self.dst.as_ref()?;
248        // DST time starts with respect to standard time, so offset it by the
249        // standard offset.
250        let start =
251            dst.rule.start.to_datetime(year, self.std_offset.to_ioffset());
252        // DST time ends with respect to DST time, so offset it by the DST
253        // offset.
254        let end = dst.rule.end.to_datetime(year, dst.offset.to_ioffset());
255        Some(DstInfo { dst, start, end })
256    }
257
258    /// Returns the range in which DST occurs.
259    ///
260    /// The civil datetimes returned are in "wall clock time." That is, they
261    /// represent the transitions as they are seen from humans reading a clock
262    /// within the geographic location of that time zone.
263    fn dst_info_wall(&self, year: i16) -> Option<DstInfo<'_, ABBREV>> {
264        let dst = self.dst.as_ref()?;
265        // POSIX time zones express their DST transitions in terms of wall
266        // clock time. Since this method specifically is returning wall
267        // clock times, we don't want to offset our datetimes at all.
268        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    /// Returns the DST transition rule. This panics if this time zone doesn't
274    /// have DST.
275    #[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        // The overwhelming common case is that DST is exactly one hour ahead
304        // of standard time. So common that this is the default. So don't write
305        // the offset if we don't need to.
306        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    /// Turns this POSIX datetime spec into a civil datetime in the year given
323    /// with the given offset. The datetimes returned are offset by the given
324    /// offset. For wall clock time, an offset of `0` should be given. For
325    /// UTC time, the offset (standard or DST) corresponding to this time
326    /// spec should be given.
327    ///
328    /// The datetime returned is guaranteed to have a year component equal
329    /// to the year given. This guarantee is upheld even when the datetime
330    /// specification (combined with the offset) would extend past the end of
331    /// the year (or before the start of the year). In this case, the maximal
332    /// (or minimal) datetime for the given year is returned.
333    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        // The range on `self.time` is `-604799..=604799`, and the range
344        // on `offset.second` is `-93599..=93599`. Therefore, subtracting
345        // them can never overflow an `i32`.
346        let offset = self.time.second - offset.second;
347        // If the time goes negative or above 86400, then we might have
348        // to adjust our date.
349        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        // This is the default time, so don't write it if we
370        // don't need to.
371        if self.time != PosixTime::DEFAULT {
372            write!(f, "/{}", self.time)?;
373        }
374        Ok(())
375    }
376}
377
378impl PosixDay {
379    /// Convert this date specification to a civil date in the year given.
380    ///
381    /// If this date specification couldn't be turned into a date in the year
382    /// given, then `None` is returned. This happens when `366` is given as
383    /// a day, but the year given is not a leap year. In this case, callers may
384    /// want to assume a datetime that is maximal for the year given.
385    fn to_date(&self, year: i16) -> Option<IDate> {
386        match *self {
387            PosixDay::JulianOne(day) => {
388                // Parsing validates that our day is 1-365 which will always
389                // succeed for all possible year values. That is, every valid
390                // year has a December 31.
391                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                // OK because our value for `day` is validated to be `0..=365`,
398                // and since it is an `i16`, it is always valid to add 1.
399                //
400                // Also, while `day+1` is guaranteed to be in `1..=366`, it is
401                // possible that `366` is invalid, for when `year` is not a
402                // leap year. In this case, we throw our hands up, and ask the
403                // caller to make a decision for how to deal with it. Why does
404                // POSIX go out of its way to specifically not specify behavior
405                // in error cases?
406                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                // This is maybe non-obvious, but this will always succeed
414                // because it can only fail when the week number is one of
415                // {-5, 0, 5}. Since we've validated that 'week' is in 1..=5,
416                // we know it can't be 0. Moreover, because of the conditional
417                // above and since `5` actually means "last weekday of month,"
418                // that case will always translate to `-1`.
419                //
420                // Also, I looked at how other libraries deal with this case,
421                // and almost all of them just do a bunch of inline hairy
422                // arithmetic here. I suppose I could be reduced to such
423                // things if perf called for it, but we have a nice civil date
424                // abstraction. So use it, god damn it. (Well, we did, and now
425                // we have a lower level IDate abstraction. But it's still
426                // an abstraction!)
427                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            // The default is positive, so when
458            // positive, we write nothing.
459        }
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        // Yes, this is backwards. Blame POSIX.
484        // N.B. `+` is the default, so we don't
485        // need to write that out.
486        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/// A helper type for formatting a time zone abbreviation.
505///
506/// Basically, this will write the `<` and `>` quotes if necessary, and
507/// otherwise write out the abbreviation in its unquoted form.
508#[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/// The daylight saving time (DST) info for a POSIX time zone in a particular
523/// year.
524#[derive(Debug, Eq, PartialEq)]
525struct DstInfo<'a, ABBREV> {
526    /// The DST transition rule that generated this info.
527    dst: &'a PosixDst<ABBREV>,
528    /// The start time (inclusive) that DST begins.
529    ///
530    /// Note that this may be greater than `end`. This tends to happen in the
531    /// southern hemisphere.
532    ///
533    /// Note also that this may be in UTC or in wall clock civil
534    /// time. It depends on whether `PosixTimeZone::dst_info_utc` or
535    /// `PosixTimeZone::dst_info_wall` was used.
536    start: IDateTime,
537    /// The end time (exclusive) that DST ends.
538    ///
539    /// Note that this may be less than `start`. This tends to happen in the
540    /// southern hemisphere.
541    ///
542    /// Note also that this may be in UTC or in wall clock civil
543    /// time. It depends on whether `PosixTimeZone::dst_info_utc` or
544    /// `PosixTimeZone::dst_info_wall` was used.
545    end: IDateTime,
546}
547
548impl<'a, ABBREV> DstInfo<'a, ABBREV> {
549    /// Returns true if and only if the given civil datetime ought to be
550    /// considered in DST.
551    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    /// Returns the earlier and later times for this DST info.
560    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    /// Returns the DST offset.
569    fn offset(&self) -> &PosixOffset {
570        &self.dst.offset
571    }
572}
573
574/// A parser for POSIX time zones.
575#[derive(Debug)]
576struct Parser<'s> {
577    /// The `TZ` string that we're parsing.
578    tz: &'s [u8],
579    /// The parser's current position in `tz`.
580    pos: core::cell::Cell<usize>,
581    /// Whether to use IANA rules, i.e., when parsing a TZ string in a TZif
582    /// file of version 3 or greater. From `tzfile(5)`:
583    ///
584    /// > First, the hours part of its transition times may be signed and range
585    /// > from `-167` through `167` instead of the POSIX-required unsigned
586    /// > values from `0` through `24`. Second, DST is in effect all year if
587    /// > it starts January 1 at 00:00 and ends December 31 at 24:00 plus the
588    /// > difference between daylight saving and standard time.
589    ///
590    /// At time of writing, I don't think I understand the significance of
591    /// the second part above. (RFC 8536 elaborates that it is meant to be an
592    /// explicit clarification of something that POSIX itself implies.) But the
593    /// first part is clear: it permits the hours to be a bigger range.
594    ianav3plus: bool,
595}
596
597impl<'s> Parser<'s> {
598    /// Create a new parser for extracting a POSIX time zone from the given
599    /// bytes.
600    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    /// Parses a POSIX time zone from the current position of the parser and
609    /// ensures that the entire TZ string corresponds to a single valid POSIX
610    /// time zone.
611    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    /// Parses a POSIX time zone from the current position of the parser and
625    /// returns the remaining input.
626    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    /// Parse a POSIX time zone from the current position of the parser.
634    ///
635    /// Upon success, the parser will be positioned immediately following the
636    /// TZ string.
637    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    /// Parse a DST zone with an optional explicit transition rule.
656    ///
657    /// This assumes the parser is positioned at the first byte of the DST
658    /// abbreviation.
659    ///
660    /// Upon success, the parser will be positioned immediately after the end
661    /// of the DST transition rule (which might just be the abbreviation, but
662    /// might also include explicit start/end datetime specifications).
663    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        // This is the default: one hour ahead of standard time. We may
678        // override this if the DST portion specifies an offset. (But it
679        // usually doesn't.)
680        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    /// Parse a time zone abbreviation.
713    ///
714    /// This assumes the parser is positioned at the first byte of
715    /// the abbreviation. This is either the first character in the
716    /// abbreviation, or the opening quote of a quoted abbreviation.
717    ///
718    /// Upon success, the parser will be positioned immediately following
719    /// the abbreviation name.
720    ///
721    /// The string returned is guaranteed to be no more than 30 bytes.
722    /// (This restriction is somewhat arbitrary, but it's so we can put
723    /// the abbreviation in a fixed capacity array.)
724    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    /// Parses an unquoted time zone abbreviation.
740    ///
741    /// This assumes the parser is position at the first byte in the
742    /// abbreviation.
743    ///
744    /// Upon success, the parser will be positioned immediately after the
745    /// last byte in the abbreviation.
746    ///
747    /// The string returned is guaranteed to be no more than 30 bytes.
748    /// (This restriction is somewhat arbitrary, but it's so we can put
749    /// the abbreviation in a fixed capacity array.)
750    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                // NOTE: I believe this error is technically impossible
772                // since the loop above restricts letters in an
773                // abbreviation to ASCII. So everything from `start` to
774                // `end` is ASCII and thus should be UTF-8. But it doesn't
775                // cost us anything to report an error here in case the
776                // code above evolves somehow.
777                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 because we verified above that the abbreviation
791        // does not exceed `Abbreviation::capacity`.
792        Ok(Abbreviation::new(abbrev).unwrap())
793    }
794
795    /// Parses a quoted time zone abbreviation.
796    ///
797    /// This assumes the parser is positioned immediately after the opening
798    /// `<` quote. That is, at the first byte in the abbreviation.
799    ///
800    /// Upon success, the parser will be positioned immediately after the
801    /// closing `>` quote.
802    ///
803    /// The string returned is guaranteed to be no more than 30 bytes.
804    /// (This restriction is somewhat arbitrary, but it's so we can put
805    /// the abbreviation in a fixed capacity array.)
806    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                // NOTE: I believe this error is technically impossible
831                // since the loop above restricts letters in an
832                // abbreviation to ASCII. So everything from `start` to
833                // `end` is ASCII and thus should be UTF-8. But it doesn't
834                // cost us anything to report an error here in case the
835                // code above evolves somehow.
836                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 because we verified above that the abbreviation
865        // does not exceed `Abbreviation::capacity`.
866        Ok(Abbreviation::new(abbrev).unwrap())
867    }
868
869    /// Parse a POSIX time offset.
870    ///
871    /// This assumes the parser is positioned at the first byte of the
872    /// offset. This can either be a digit (for a positive offset) or the
873    /// sign of the offset (which must be either `-` or `+`).
874    ///
875    /// Upon success, the parser will be positioned immediately after the
876    /// end of the offset.
877    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        // Yes, we flip the sign, because POSIX is backwards.
909        // For example, `EST5` corresponds to `-05:00`.
910        offset.second *= i32::from(-sign);
911        // Must be true because the parsing routines for hours, minutes
912        // and seconds enforce they are in the ranges -24..=24, 0..=59
913        // and 0..=59, respectively.
914        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    /// Parses a POSIX DST transition rule.
923    ///
924    /// This assumes the parser is positioned at the first byte in the
925    /// rule. That is, it comes immediately after the DST abbreviation or
926    /// its optional offset.
927    ///
928    /// Upon success, the parser will be positioned immediately after the
929    /// DST transition rule. In typical cases, this corresponds to the end
930    /// of the TZ string.
931    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    /// Parses a POSIX datetime specification.
948    ///
949    /// This assumes the parser is position at the first byte where a
950    /// datetime specification is expected to occur.
951    ///
952    /// Upon success, the parser will be positioned after the datetime
953    /// specification. This will either be immediately after the date, or
954    /// if it's present, the time part of the specification.
955    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    /// Parses a POSIX date specification.
974    ///
975    /// This assumes the parser is positioned at the first byte of the date
976    /// specification. This can be `J` (for one based Julian day without
977    /// leap days), `M` (for "weekday of month") or a digit starting the
978    /// zero based Julian day with leap days. This routine will validate
979    /// that the position points to one of these possible values. That is,
980    /// the caller doesn't need to parse the `M` or the `J` or the leading
981    /// digit. The caller should just call this routine when it *expect* a
982    /// date specification to follow.
983    ///
984    /// Upon success, the parser will be positioned immediately after the
985    /// date specification.
986    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    /// Parses a POSIX Julian day that does not include leap days
1024    /// (`1 <= n <= 365`).
1025    ///
1026    /// This assumes the parser is positioned just after the `J` and at the
1027    /// first digit of the Julian day. Upon success, the parser will be
1028    /// positioned immediately following the day number.
1029    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    /// Parses a POSIX Julian day that includes leap days (`0 <= n <=
1050    /// 365`).
1051    ///
1052    /// This assumes the parser is positioned at the first digit of the
1053    /// Julian day. Upon success, the parser will be positioned immediately
1054    /// following the day number.
1055    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    /// Parses a POSIX "weekday of month" specification.
1076    ///
1077    /// This assumes the parser is positioned just after the `M` byte and
1078    /// at the first digit of the month. Upon success, the parser will be
1079    /// positioned immediately following the "weekday of the month" that
1080    /// was parsed.
1081    ///
1082    /// The tuple returned is month (1..=12), week (1..=5) and weekday
1083    /// (0..=6 with 0=Sunday).
1084    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    /// This parses a POSIX time specification in the format
1115    /// `[+/-]hh?[:mm[:ss]]`.
1116    ///
1117    /// This assumes the parser is positioned at the first `h` (or the
1118    /// sign, if present). Upon success, the parser will be positioned
1119    /// immediately following the end of the time specification.
1120    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        // Must be true because the parsing routines for hours, minutes
1160        // and seconds enforce they are in the ranges -167..=167, 0..=59
1161        // and 0..=59, respectively.
1162        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    /// Parses a month.
1171    ///
1172    /// This is expected to be positioned at the first digit. Upon success,
1173    /// the parser will be positioned after the month (which may contain
1174    /// two digits).
1175    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    /// Parses a week-of-month number.
1193    ///
1194    /// This is expected to be positioned at the first digit. Upon success,
1195    /// the parser will be positioned after the week digit.
1196    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    /// Parses a weekday number.
1214    ///
1215    /// This is expected to be positioned at the first digit. Upon success,
1216    /// the parser will be positioned after the week digit.
1217    ///
1218    /// The weekday returned is guaranteed to be in the range `0..=6`, with
1219    /// `0` corresponding to Sunday.
1220    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    /// Parses an hour from a POSIX time specification with the IANA
1239    /// v3+ extension. That is, the hour may be in the range `0..=167`.
1240    /// (Callers should parse an optional sign preceding the hour digits
1241    /// when IANA V3+ parsing is enabled.)
1242    ///
1243    /// The hour is allowed to be a single digit (unlike minutes or
1244    /// seconds).
1245    ///
1246    /// This assumes the parser is positioned at the position where the
1247    /// first hour digit should occur. Upon success, the parser will be
1248    /// positioned immediately after the last hour digit.
1249    fn parse_hour_ianav3plus(&self) -> Result<i16, Error> {
1250        // Callers should only be using this method when IANA v3+ parsing
1251        // is enabled.
1252        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            // The error message says -167 but the check above uses 0.
1264            // This is because the caller is responsible for parsing
1265            // the sign.
1266            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    /// Parses an hour from a POSIX time specification, with the allowed
1275    /// range being `0..=24`.
1276    ///
1277    /// The hour is allowed to be a single digit (unlike minutes or
1278    /// seconds).
1279    ///
1280    /// This assumes the parser is positioned at the position where the
1281    /// first hour digit should occur. Upon success, the parser will be
1282    /// positioned immediately after the last hour digit.
1283    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    /// Parses a minute from a POSIX time specification.
1303    ///
1304    /// The minute must be exactly two digits.
1305    ///
1306    /// This assumes the parser is positioned at the position where the
1307    /// first minute digit should occur. Upon success, the parser will be
1308    /// positioned immediately after the second minute digit.
1309    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    /// Parses a second from a POSIX time specification.
1329    ///
1330    /// The second must be exactly two digits.
1331    ///
1332    /// This assumes the parser is positioned at the position where the
1333    /// first second digit should occur. Upon success, the parser will be
1334    /// positioned immediately after the second second digit.
1335    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    /// Parses a signed 64-bit integer expressed in exactly `n` digits.
1355    ///
1356    /// If `n` digits could not be found (or if the `TZ` string ends before
1357    /// `n` digits could be found), then this returns an error.
1358    ///
1359    /// This assumes that `n >= 1` and that the parser is positioned at the
1360    /// first digit. Upon success, the parser is positioned immediately
1361    /// after the `n`th digit.
1362    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    /// Parses a signed 64-bit integer expressed with up to `n` digits and
1407    /// at least 1 digit.
1408    ///
1409    /// This assumes that `n >= 1` and that the parser is positioned at the
1410    /// first digit. Upon success, the parser is position immediately after
1411    /// the last digit (which can be at most `n`).
1412    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    /// Parses an optional sign.
1439    ///
1440    /// This assumes the parser is positioned at the position where a
1441    /// positive or negative sign is permitted. If one exists, then it
1442    /// is consumed and returned. Moreover, if one exists, then this
1443    /// guarantees that it is not the last byte in the input. That is, upon
1444    /// success, it is valid to call `self.byte()`.
1445    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
1473/// Helper routines for parsing a POSIX `TZ` string.
1474impl<'s> Parser<'s> {
1475    /// Bump the parser to the next byte.
1476    ///
1477    /// If the end of the input has been reached, then `false` is returned.
1478    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    /// Returns true if the next call to `bump` would return false.
1489    fn is_done(&self) -> bool {
1490        self.pos() == self.tz.len()
1491    }
1492
1493    /// Return the byte at the current position of the parser.
1494    ///
1495    /// This panics if the parser is positioned at the end of the TZ
1496    /// string.
1497    fn byte(&self) -> u8 {
1498        self.tz[self.pos()]
1499    }
1500
1501    /// Return the byte at the current position of the parser. If the TZ
1502    /// string has been exhausted, then this returns `None`.
1503    fn maybe_byte(&self) -> Option<u8> {
1504        self.tz.get(self.pos()).copied()
1505    }
1506
1507    /// Return the current byte offset of the parser.
1508    ///
1509    /// The offset starts at `0` from the beginning of the TZ string.
1510    fn pos(&self) -> usize {
1511        self.pos.get()
1512    }
1513
1514    /// Returns the remaining bytes of the TZ string.
1515    ///
1516    /// This includes `self.byte()`. It may be empty.
1517    fn remaining(&self) -> &'s [u8] {
1518        &self.tz[self.pos()..]
1519    }
1520}
1521
1522// Tests require parsing, and parsing requires alloc.
1523#[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        // While we're here, assert that converting the TZ back
1536        // to a string matches what we got. In the original version
1537        // of the POSIX TZ parser, we were very meticulous about
1538        // capturing the exact AST of the time zone. But I've
1539        // since simplified the data structure considerably such
1540        // that it is lossy in terms of what was actually parsed
1541        // (but of course, not lossy in terms of the semantic
1542        // meaning of the time zone).
1543        //
1544        // So to account for this, we serialize to a string and
1545        // then parse it back. We should get what we started with.
1546        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        // The inputs look a little funny here, but that's because
1867        // 'parse_quoted_abbreviation' starts after the opening quote
1868        // has been parsed.
1869
1870        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        // This checks that we don't accidentally permit IANA rules for
1957        // offset parsing. Namely, the IANA tzfile v3+ extension only applies
1958        // to transition times. But since POSIX says that the "time" for the
1959        // offset and transition is the same format, it would be an easy
1960        // implementation mistake to implement the more flexible rule for
1961        // IANA and have it accidentally also apply to the offset. So we check
1962        // that it doesn't here.
1963        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        // Should this be allowed? POSIX spec is unclear.
2283        // We allow it because our parse does stop at 2
2284        // digits, so this seems harmless. Namely, '001'
2285        // results in an error.
2286        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            // We test this in other places. It's too annoying to write this
2519            // out here, and I didn't adopt snapshot testing until I had
2520            // written out these tests by hand. ¯\_(ツ)_/¯
2521            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        // For this test, we just keep the offset to zero to simplify things
2600        // a bit. We get coverage for non-zero offsets in higher level tests.
2601        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        // Ref: https://github.com/chronotope/chrono/issues/1153
2761        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}