Skip to main content

jiff/shared/
posix.rs

1use core::fmt::Debug;
2
3use super::{
4    util::{
5        array_str::Abbreviation,
6        itime::{
7            IAmbiguousOffset, IDate, IDateTime, IOffset, ITime, ITimeSecond,
8            ITimestamp, IWeekday,
9        },
10    },
11    PosixDay, PosixDayTime, PosixDst, PosixOffset, PosixRule, PosixTime,
12    PosixTimeZone,
13};
14
15impl PosixTimeZone<Abbreviation> {
16    /// Parse a POSIX `TZ` environment variable, assuming it's a rule and not
17    /// an implementation defined value, from the given bytes.
18    pub fn parse(
19        bytes: &[u8],
20    ) -> Result<PosixTimeZone<Abbreviation>, PosixTimeZoneError> {
21        // We enable the IANA v3+ extensions here. (Namely, that the time
22        // specification hour value has the range `-167..=167` instead of
23        // `0..=24`.) Requiring strict POSIX rules doesn't seem necessary
24        // since the extension is a strict superset. Plus, GNU tooling
25        // seems to accept the extension.
26        let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
27        parser.parse()
28    }
29
30    // only-jiff-start
31    /// Like parse, but parses a prefix of the input given and returns whatever
32    /// is remaining.
33    pub fn parse_prefix<'b>(
34        bytes: &'b [u8],
35    ) -> Result<(PosixTimeZone<Abbreviation>, &'b [u8]), PosixTimeZoneError>
36    {
37        let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
38        parser.parse_prefix()
39    }
40    // only-jiff-end
41}
42
43impl<ABBREV: AsRef<str> + Debug> PosixTimeZone<ABBREV> {
44    /// Returns the appropriate time zone offset to use for the given
45    /// timestamp.
46    ///
47    /// If you need information like whether the offset is in DST or not, or
48    /// the time zone abbreviation, then use `PosixTimeZone::to_offset_info`.
49    /// But that API may be more expensive to use, so only use it if you need
50    /// the additional data.
51    pub(crate) fn to_offset(&self, timestamp: ITimestamp) -> IOffset {
52        let std_offset = self.std_offset.to_ioffset();
53        if self.dst.is_none() {
54            return std_offset;
55        }
56
57        let dt = timestamp.to_datetime(IOffset::UTC);
58        self.dst_info_utc(dt.date.year)
59            .filter(|dst_info| dst_info.in_dst(dt))
60            .map(|dst_info| dst_info.offset().to_ioffset())
61            .unwrap_or_else(|| std_offset)
62    }
63
64    /// Returns the appropriate time zone offset to use for the given
65    /// timestamp.
66    ///
67    /// This also includes whether the offset returned should be considered
68    /// to be "DST" or not, along with the time zone abbreviation (e.g., EST
69    /// for standard time in New York, and EDT for DST in New York).
70    pub(crate) fn to_offset_info(
71        &self,
72        timestamp: ITimestamp,
73    ) -> (IOffset, &'_ str, bool) {
74        let std_offset = self.std_offset.to_ioffset();
75        if self.dst.is_none() {
76            return (std_offset, self.std_abbrev.as_ref(), false);
77        }
78
79        let dt = timestamp.to_datetime(IOffset::UTC);
80        self.dst_info_utc(dt.date.year)
81            .filter(|dst_info| dst_info.in_dst(dt))
82            .map(|dst_info| {
83                (
84                    dst_info.offset().to_ioffset(),
85                    dst_info.dst.abbrev.as_ref(),
86                    true,
87                )
88            })
89            .unwrap_or_else(|| (std_offset, self.std_abbrev.as_ref(), false))
90    }
91
92    /// Returns a possibly ambiguous timestamp for the given civil datetime.
93    ///
94    /// The given datetime should correspond to the "wall" clock time of what
95    /// humans use to tell time for this time zone.
96    ///
97    /// Note that "ambiguous timestamp" is represented by the possible
98    /// selection of offsets that could be applied to the given datetime. In
99    /// general, it is only ambiguous around transitions to-and-from DST. The
100    /// ambiguity can arise as a "fold" (when a particular wall clock time is
101    /// repeated) or as a "gap" (when a particular wall clock time is skipped
102    /// entirely).
103    pub(crate) fn to_ambiguous_kind(&self, dt: IDateTime) -> IAmbiguousOffset {
104        let year = dt.date.year;
105        let std_offset = self.std_offset.to_ioffset();
106        let Some(dst_info) = self.dst_info_wall(year) else {
107            return IAmbiguousOffset::Unambiguous { offset: std_offset };
108        };
109        let dst_offset = dst_info.offset().to_ioffset();
110        let diff = dst_offset.second - std_offset.second;
111        // When the difference between DST and standard is positive, that means
112        // STD->DST results in a gap while DST->STD results in a fold. However,
113        // when the difference is negative, that means STD->DST results in a
114        // fold while DST->STD results in a gap. The former is by far the most
115        // common. The latter is a bit weird, but real cases do exist. For
116        // example, Dublin has DST in winter (UTC+01) and STD in the summer
117        // (UTC+00).
118        //
119        // When the difference is zero, then we have a weird POSIX time zone
120        // where a DST transition rule was specified, but was set to explicitly
121        // be the same as STD. In this case, there can be no ambiguity. (The
122        // zero case is strictly redundant. Both the diff < 0 and diff > 0
123        // cases handle the zero case correctly. But we write it out for
124        // clarity.)
125        if diff == 0 {
126            debug_assert_eq!(std_offset, dst_offset);
127            IAmbiguousOffset::Unambiguous { offset: std_offset }
128        } else if diff.is_negative() {
129            // For DST transitions that always move behind one hour, ambiguous
130            // timestamps only occur when the given civil datetime falls in the
131            // standard time range.
132            if dst_info.in_dst(dt) {
133                IAmbiguousOffset::Unambiguous { offset: dst_offset }
134            } else {
135                let fold_start = dst_info.start.saturating_add_seconds(diff);
136                let gap_end =
137                    dst_info.end.saturating_add_seconds(diff.saturating_neg());
138                if fold_start <= dt && dt < dst_info.start {
139                    IAmbiguousOffset::Fold {
140                        before: std_offset,
141                        after: dst_offset,
142                    }
143                } else if dst_info.end <= dt && dt < gap_end {
144                    IAmbiguousOffset::Gap {
145                        before: dst_offset,
146                        after: std_offset,
147                    }
148                } else {
149                    IAmbiguousOffset::Unambiguous { offset: std_offset }
150                }
151            }
152        } else {
153            // For DST transitions that always move ahead one hour, ambiguous
154            // timestamps only occur when the given civil datetime falls in the
155            // DST range.
156            if !dst_info.in_dst(dt) {
157                IAmbiguousOffset::Unambiguous { offset: std_offset }
158            } else {
159                // PERF: I wonder if it makes sense to pre-compute these?
160                // Probably not, because we have to do it based on year of
161                // datetime given. But if we ever add a "caching" layer for
162                // POSIX time zones, then it might be worth adding these to it.
163                let gap_end = dst_info.start.saturating_add_seconds(diff);
164                let fold_start =
165                    dst_info.end.saturating_add_seconds(diff.saturating_neg());
166                if dst_info.start <= dt && dt < gap_end {
167                    IAmbiguousOffset::Gap {
168                        before: std_offset,
169                        after: dst_offset,
170                    }
171                } else if fold_start <= dt && dt < dst_info.end {
172                    IAmbiguousOffset::Fold {
173                        before: dst_offset,
174                        after: std_offset,
175                    }
176                } else {
177                    IAmbiguousOffset::Unambiguous { offset: dst_offset }
178                }
179            }
180        }
181    }
182
183    /// Returns the timestamp of the most recent time zone transition prior
184    /// to the timestamp given. If one doesn't exist, `None` is returned.
185    pub(crate) fn previous_transition(
186        &self,
187        timestamp: ITimestamp,
188    ) -> Option<(ITimestamp, IOffset, &'_ str, bool)> {
189        let dt = timestamp.to_datetime(IOffset::UTC);
190        let dst_info = self.dst_info_utc(dt.date.year)?;
191        let (earlier, later) = dst_info.ordered();
192        let (prev, dst_info) = if dt > later {
193            (later, dst_info)
194        } else if dt > earlier {
195            (earlier, dst_info)
196        } else {
197            let prev_year = dt.date.prev_year().ok()?;
198            let dst_info = self.dst_info_utc(prev_year)?;
199            let (_, later) = dst_info.ordered();
200            (later, dst_info)
201        };
202
203        let timestamp = prev.to_timestamp_checked(IOffset::UTC)?;
204        let dt = timestamp.to_datetime(IOffset::UTC);
205        let (offset, abbrev, dst) = if dst_info.in_dst(dt) {
206            (dst_info.offset(), dst_info.dst.abbrev.as_ref(), true)
207        } else {
208            (&self.std_offset, self.std_abbrev.as_ref(), false)
209        };
210        Some((timestamp, offset.to_ioffset(), abbrev, dst))
211    }
212
213    /// Returns the timestamp of the soonest time zone transition after the
214    /// timestamp given. If one doesn't exist, `None` is returned.
215    pub(crate) fn next_transition(
216        &self,
217        timestamp: ITimestamp,
218    ) -> Option<(ITimestamp, IOffset, &'_ str, bool)> {
219        let dt = timestamp.to_datetime(IOffset::UTC);
220        let dst_info = self.dst_info_utc(dt.date.year)?;
221        let (earlier, later) = dst_info.ordered();
222        let (next, dst_info) = if dt < earlier {
223            (earlier, dst_info)
224        } else if dt < later {
225            (later, dst_info)
226        } else {
227            let next_year = dt.date.next_year().ok()?;
228            let dst_info = self.dst_info_utc(next_year)?;
229            let (earlier, _) = dst_info.ordered();
230            (earlier, dst_info)
231        };
232
233        let timestamp = next.to_timestamp_checked(IOffset::UTC)?;
234        let dt = timestamp.to_datetime(IOffset::UTC);
235        let (offset, abbrev, dst) = if dst_info.in_dst(dt) {
236            (dst_info.offset(), dst_info.dst.abbrev.as_ref(), true)
237        } else {
238            (&self.std_offset, self.std_abbrev.as_ref(), false)
239        };
240        Some((timestamp, offset.to_ioffset(), abbrev, dst))
241    }
242
243    /// Returns the range in which DST occurs.
244    ///
245    /// The civil datetimes returned are in UTC. This is useful for determining
246    /// whether a timestamp is in DST or not.
247    fn dst_info_utc(&self, year: i16) -> Option<DstInfo<'_, ABBREV>> {
248        let dst = self.dst.as_ref()?;
249        // DST time starts with respect to standard time, so offset it by the
250        // standard offset.
251        let start =
252            dst.rule.start.to_datetime(year, self.std_offset.to_ioffset());
253        // DST time ends with respect to DST time, so offset it by the DST
254        // offset.
255        let mut end = dst.rule.end.to_datetime(year, dst.offset.to_ioffset());
256        // This is a whacky special case when DST is permanent, but the math
257        // using to calculate the start/end datetimes ends up leaving a gap
258        // for standard time to appear. In which case, it's possible for a
259        // timestamp at the end of a calendar year to get standard time when
260        // it really should be DST.
261        //
262        // We detect this case by re-interpreting the end of the boundary using
263        // the standard offset. If we get a datetime that is in a different
264        // year, then it follows that standard time is actually impossible to
265        // occur.
266        //
267        // These weird POSIX time zones can occur as the TZ strings in
268        // a TZif file compiled using rearguard semantics. For example,
269        // `Africa/Casablanca` has:
270        //
271        //     XXX-2<+01>-1,0/0,J365/23
272        //
273        // Notice here that DST is actually one hour *behind* (it is usually
274        // one hour *ahead*) _and_ it ends at 23:00:00 on the last day of the
275        // year. But if it ends at 23:00, then jumping to standard time moves
276        // the clocks *forward*. Which would bring us to 00:00:00 on the first
277        // of the next year... but that is when DST begins! Hence, DST is
278        // permanent.
279        //
280        // Ideally, this could just be handled by our math automatically. But
281        // I couldn't figure out how to make it work. In particular, in the
282        // above example for year 2087, we get
283        //
284        //     start == 2087-01-01T00:00:00Z
285        //     end == 2087-12-31T22:00:00Z
286        //
287        // Which leaves a two hour gap for a timestamp to get erroneously
288        // categorized as standard time.
289        //
290        // ... so we special case this. We could pre-compute whether a POSIX
291        // time zone is in permanent DST at construction time, but it's not
292        // obvious to me that it's worth it. Especially since this is an
293        // exceptionally rare case.
294        //
295        // Note that I did try to consult tzcode's (incredibly inscrutable)
296        // `localtime` implementation to figure out how they deal with it. At
297        // first, it looks like they don't have any special handling for this
298        // case. But looking more closely, they skip any time zone transitions
299        // generated by POSIX time zones whose rule spans more than 1 year:
300        //
301        //     https://github.com/eggert/tz/blob/8d65db9786753f3b263087e31c59d191561d63e3/localtime.c#L1717-L1735
302        //
303        // By just ignoring them, I think it achieves the desired effect of
304        // permanent DST. But I'm not 100% confident in my understanding of
305        // the code.
306        if start.date.month == 1
307            && start.date.day == 1
308            && start.time == ITime::MIN
309            // NOTE: This should come last because it is potentially expensive.
310            && year
311                != end.saturating_add_seconds(self.std_offset.second).date.year
312        {
313            end = IDateTime {
314                date: IDate { year, month: 12, day: 31 },
315                time: ITime::MAX,
316            };
317        }
318        Some(DstInfo { dst, start, end })
319    }
320
321    /// Returns the range in which DST occurs.
322    ///
323    /// The civil datetimes returned are in "wall clock time." That is, they
324    /// represent the transitions as they are seen from humans reading a clock
325    /// within the geographic location of that time zone.
326    fn dst_info_wall(&self, year: i16) -> Option<DstInfo<'_, ABBREV>> {
327        let dst = self.dst.as_ref()?;
328        // POSIX time zones express their DST transitions in terms of wall
329        // clock time. Since this method specifically is returning wall
330        // clock times, we don't want to offset our datetimes at all.
331        let start = dst.rule.start.to_datetime(year, IOffset::UTC);
332        let end = dst.rule.end.to_datetime(year, IOffset::UTC);
333        Some(DstInfo { dst, start, end })
334    }
335
336    /// Returns the DST transition rule. This panics if this time zone doesn't
337    /// have DST.
338    #[cfg(test)]
339    fn rule(&self) -> &PosixRule {
340        &self.dst.as_ref().unwrap().rule
341    }
342}
343
344impl<ABBREV: AsRef<str>> core::fmt::Display for PosixTimeZone<ABBREV> {
345    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
346        core::fmt::Display::fmt(
347            &AbbreviationDisplay(self.std_abbrev.as_ref()),
348            f,
349        )?;
350        core::fmt::Display::fmt(&self.std_offset, f)?;
351        if let Some(ref dst) = self.dst {
352            dst.display(&self.std_offset, f)?;
353        }
354        Ok(())
355    }
356}
357
358impl<ABBREV: AsRef<str>> PosixDst<ABBREV> {
359    fn display(
360        &self,
361        std_offset: &PosixOffset,
362        f: &mut core::fmt::Formatter,
363    ) -> core::fmt::Result {
364        core::fmt::Display::fmt(
365            &AbbreviationDisplay(self.abbrev.as_ref()),
366            f,
367        )?;
368        // The overwhelming common case is that DST is exactly one hour ahead
369        // of standard time. So common that this is the default. So don't write
370        // the offset if we don't need to.
371        let default = PosixOffset { second: std_offset.second + 3600 };
372        if self.offset != default {
373            core::fmt::Display::fmt(&self.offset, f)?;
374        }
375        f.write_str(",")?;
376        core::fmt::Display::fmt(&self.rule, f)?;
377        Ok(())
378    }
379}
380
381impl core::fmt::Display for PosixRule {
382    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
383        core::fmt::Display::fmt(&self.start, f)?;
384        f.write_str(",")?;
385        core::fmt::Display::fmt(&self.end, f)?;
386        Ok(())
387    }
388}
389
390impl PosixDayTime {
391    /// Turns this POSIX datetime spec into a civil datetime in the year given
392    /// with the given offset. The datetimes returned are offset by the given
393    /// offset. For wall clock time, an offset of `0` should be given. For
394    /// UTC time, the offset (standard or DST) corresponding to this time
395    /// spec should be given.
396    ///
397    /// The datetime returned is guaranteed to have a year component equal
398    /// to the year given. This guarantee is upheld even when the datetime
399    /// specification (combined with the offset) would extend past the end of
400    /// the year (or before the start of the year). In this case, the maximal
401    /// (or minimal) datetime for the given year is returned.
402    pub(crate) fn to_datetime(&self, year: i16, offset: IOffset) -> IDateTime {
403        let mkmin = || IDateTime {
404            date: IDate { year, month: 1, day: 1 },
405            time: ITime::MIN,
406        };
407        let mkmax = || IDateTime {
408            date: IDate { year, month: 12, day: 31 },
409            time: ITime::MAX,
410        };
411        let Some(date) = self.date.to_date(year) else { return mkmax() };
412        // The range on `self.time` is `-604799..=604799`, and the range
413        // on `offset.second` is `-93599..=93599`. Therefore, subtracting
414        // them can never overflow an `i32`.
415        let offset = self.time.second - offset.second;
416        // If the time goes negative or above 86400, then we might have
417        // to adjust our date.
418        let days = offset.div_euclid(86400);
419        let second = offset.rem_euclid(86400);
420
421        let Ok(date) = date.checked_add_days(days) else {
422            return if offset < 0 { mkmin() } else { mkmax() };
423        };
424        if date.year < year {
425            mkmin()
426        } else if date.year > year {
427            mkmax()
428        } else {
429            let time = ITimeSecond { second }.to_time();
430            IDateTime { date, time }
431        }
432    }
433}
434
435impl core::fmt::Display for PosixDayTime {
436    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
437        core::fmt::Display::fmt(&self.date, f)?;
438        // This is the default time, so don't write it if we
439        // don't need to.
440        if self.time != PosixTime::DEFAULT {
441            f.write_str("/")?;
442            core::fmt::Display::fmt(&self.time, f)?;
443        }
444        Ok(())
445    }
446}
447
448impl PosixDay {
449    /// Convert this date specification to a civil date in the year given.
450    ///
451    /// If this date specification couldn't be turned into a date in the year
452    /// given, then `None` is returned. This happens when `366` is given as
453    /// a day, but the year given is not a leap year. In this case, callers may
454    /// want to assume a datetime that is maximal for the year given.
455    fn to_date(&self, year: i16) -> Option<IDate> {
456        match *self {
457            PosixDay::JulianOne(day) => {
458                // Parsing validates that our day is 1-365 which will always
459                // succeed for all possible year values. That is, every valid
460                // year has a December 31.
461                Some(
462                    IDate::from_day_of_year_no_leap(year, day)
463                        .expect("Julian `J day` should be in bounds"),
464                )
465            }
466            PosixDay::JulianZero(day) => {
467                // OK because our value for `day` is validated to be `0..=365`,
468                // and since it is an `i16`, it is always valid to add 1.
469                //
470                // Also, while `day+1` is guaranteed to be in `1..=366`, it is
471                // possible that `366` is invalid, for when `year` is not a
472                // leap year. In this case, we throw our hands up, and ask the
473                // caller to make a decision for how to deal with it. Why does
474                // POSIX go out of its way to specifically not specify behavior
475                // in error cases?
476                IDate::from_day_of_year(year, day + 1).ok()
477            }
478            PosixDay::WeekdayOfMonth { month, week, weekday } => {
479                let weekday = IWeekday::from_sunday_zero_offset(weekday);
480                let first = IDate { year, month, day: 1 };
481                let week = if week == 5 { -1 } else { week };
482                debug_assert!(week == -1 || (1..=4).contains(&week));
483                // This is maybe non-obvious, but this will always succeed
484                // because it can only fail when the week number is one of
485                // {-5, 0, 5}. Since we've validated that 'week' is in 1..=5,
486                // we know it can't be 0. Moreover, because of the conditional
487                // above and since `5` actually means "last weekday of month,"
488                // that case will always translate to `-1`.
489                //
490                // Also, I looked at how other libraries deal with this case,
491                // and almost all of them just do a bunch of inline hairy
492                // arithmetic here. I suppose I could be reduced to such
493                // things if perf called for it, but we have a nice civil date
494                // abstraction. So use it, god damn it. (Well, we did, and now
495                // we have a lower level IDate abstraction. But it's still
496                // an abstraction!)
497                Some(
498                    first
499                        .nth_weekday_of_month(week, weekday)
500                        .expect("nth weekday always exists"),
501                )
502            }
503        }
504    }
505}
506
507impl core::fmt::Display for PosixDay {
508    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
509        match *self {
510            PosixDay::JulianOne(n) => {
511                f.write_str("J")?;
512                core::fmt::Display::fmt(&n, f)
513            }
514            PosixDay::JulianZero(n) => core::fmt::Display::fmt(&n, f),
515            PosixDay::WeekdayOfMonth { month, week, weekday } => {
516                f.write_str("M")?;
517                core::fmt::Display::fmt(&month, f)?;
518                f.write_str(".")?;
519                core::fmt::Display::fmt(&week, f)?;
520                f.write_str(".")?;
521                core::fmt::Display::fmt(&weekday, f)?;
522                Ok(())
523            }
524        }
525    }
526}
527
528impl PosixTime {
529    const DEFAULT: PosixTime = PosixTime { second: 2 * 60 * 60 };
530}
531
532impl core::fmt::Display for PosixTime {
533    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
534        if self.second.is_negative() {
535            f.write_str("-")?;
536            // The default is positive, so when
537            // positive, we write nothing.
538        }
539        let second = self.second.unsigned_abs();
540        let h = second / 3600;
541        let m = (second / 60) % 60;
542        let s = second % 60;
543        core::fmt::Display::fmt(&h, f)?;
544        if m != 0 || s != 0 {
545            write!(f, ":{m:02}")?;
546            if s != 0 {
547                write!(f, ":{s:02}")?;
548            }
549        }
550        Ok(())
551    }
552}
553
554impl PosixOffset {
555    fn to_ioffset(&self) -> IOffset {
556        IOffset { second: self.second }
557    }
558}
559
560impl core::fmt::Display for PosixOffset {
561    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
562        // Yes, this is backwards. Blame POSIX.
563        // N.B. `+` is the default, so we don't
564        // need to write that out.
565        if self.second > 0 {
566            f.write_str("-")?;
567        }
568        let second = self.second.unsigned_abs();
569        let h = second / 3600;
570        let m = (second / 60) % 60;
571        let s = second % 60;
572        core::fmt::Display::fmt(&h, f)?;
573        if m != 0 || s != 0 {
574            write!(f, ":{m:02}")?;
575            if s != 0 {
576                write!(f, ":{s:02}")?;
577            }
578        }
579        Ok(())
580    }
581}
582
583/// A helper type for formatting a time zone abbreviation.
584///
585/// Basically, this will write the `<` and `>` quotes if necessary, and
586/// otherwise write out the abbreviation in its unquoted form.
587#[derive(Debug)]
588struct AbbreviationDisplay<S>(S);
589
590impl<S: AsRef<str>> core::fmt::Display for AbbreviationDisplay<S> {
591    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
592        let s = self.0.as_ref();
593        if s.chars().any(|ch| ch == '+' || ch == '-') {
594            f.write_str("<")?;
595            core::fmt::Display::fmt(&s, f)?;
596            f.write_str(">")
597        } else {
598            core::fmt::Display::fmt(&s, f)
599        }
600    }
601}
602
603/// The daylight saving time (DST) info for a POSIX time zone in a particular
604/// year.
605#[derive(Debug, Eq, PartialEq)]
606struct DstInfo<'a, ABBREV> {
607    /// The DST transition rule that generated this info.
608    dst: &'a PosixDst<ABBREV>,
609    /// The start time (inclusive) that DST begins.
610    ///
611    /// Note that this may be greater than `end`. This tends to happen in the
612    /// southern hemisphere.
613    ///
614    /// Note also that this may be in UTC or in wall clock civil
615    /// time. It depends on whether `PosixTimeZone::dst_info_utc` or
616    /// `PosixTimeZone::dst_info_wall` was used.
617    start: IDateTime,
618    /// The end time (exclusive) that DST ends.
619    ///
620    /// Note that this may be less than `start`. This tends to happen in the
621    /// southern hemisphere.
622    ///
623    /// Note also that this may be in UTC or in wall clock civil
624    /// time. It depends on whether `PosixTimeZone::dst_info_utc` or
625    /// `PosixTimeZone::dst_info_wall` was used.
626    end: IDateTime,
627}
628
629impl<'a, ABBREV> DstInfo<'a, ABBREV> {
630    /// Returns true if and only if the given civil datetime ought to be
631    /// considered in DST.
632    fn in_dst(&self, utc_dt: IDateTime) -> bool {
633        if self.start <= self.end {
634            self.start <= utc_dt && utc_dt < self.end
635        } else {
636            !(self.end <= utc_dt && utc_dt < self.start)
637        }
638    }
639
640    /// Returns the earlier and later times for this DST info.
641    fn ordered(&self) -> (IDateTime, IDateTime) {
642        if self.start <= self.end {
643            (self.start, self.end)
644        } else {
645            (self.end, self.start)
646        }
647    }
648
649    /// Returns the DST offset.
650    fn offset(&self) -> &PosixOffset {
651        &self.dst.offset
652    }
653}
654
655/// A parser for POSIX time zones.
656#[derive(Debug)]
657struct Parser<'s> {
658    /// The `TZ` string that we're parsing.
659    tz: &'s [u8],
660    /// The parser's current position in `tz`.
661    pos: core::cell::Cell<usize>,
662    /// Whether to use IANA rules, i.e., when parsing a TZ string in a TZif
663    /// file of version 3 or greater. From `tzfile(5)`:
664    ///
665    /// > First, the hours part of its transition times may be signed and range
666    /// > from `-167` through `167` instead of the POSIX-required unsigned
667    /// > values from `0` through `24`. Second, DST is in effect all year if
668    /// > it starts January 1 at 00:00 and ends December 31 at 24:00 plus the
669    /// > difference between daylight saving and standard time.
670    ///
671    /// At time of writing, I don't think I understand the significance of
672    /// the second part above. (RFC 8536 elaborates that it is meant to be an
673    /// explicit clarification of something that POSIX itself implies.) But the
674    /// first part is clear: it permits the hours to be a bigger range.
675    ianav3plus: bool,
676}
677
678impl<'s> Parser<'s> {
679    /// Create a new parser for extracting a POSIX time zone from the given
680    /// bytes.
681    fn new<B: ?Sized + AsRef<[u8]>>(tz: &'s B) -> Parser<'s> {
682        Parser {
683            tz: tz.as_ref(),
684            pos: core::cell::Cell::new(0),
685            ianav3plus: false,
686        }
687    }
688
689    /// Parses a POSIX time zone from the current position of the parser and
690    /// ensures that the entire TZ string corresponds to a single valid POSIX
691    /// time zone.
692    fn parse(
693        &self,
694    ) -> Result<PosixTimeZone<Abbreviation>, PosixTimeZoneError> {
695        let (time_zone, remaining) = self.parse_prefix()?;
696        if !remaining.is_empty() {
697            return Err(ErrorKind::FoundRemaining.into());
698        }
699        Ok(time_zone)
700    }
701
702    /// Parses a POSIX time zone from the current position of the parser and
703    /// returns the remaining input.
704    fn parse_prefix(
705        &self,
706    ) -> Result<(PosixTimeZone<Abbreviation>, &'s [u8]), PosixTimeZoneError>
707    {
708        let time_zone = self.parse_posix_time_zone()?;
709        Ok((time_zone, self.remaining()))
710    }
711
712    /// Parse a POSIX time zone from the current position of the parser.
713    ///
714    /// Upon success, the parser will be positioned immediately following the
715    /// TZ string.
716    fn parse_posix_time_zone(
717        &self,
718    ) -> Result<PosixTimeZone<Abbreviation>, PosixTimeZoneError> {
719        if self.is_done() {
720            return Err(ErrorKind::Empty.into());
721        }
722        let std_abbrev =
723            self.parse_abbreviation().map_err(ErrorKind::AbbreviationStd)?;
724        let std_offset =
725            self.parse_posix_offset().map_err(ErrorKind::OffsetStd)?;
726        let mut dst = None;
727        if !self.is_done()
728            && (self.byte().is_ascii_alphabetic() || self.byte() == b'<')
729        {
730            dst = Some(self.parse_posix_dst(&std_offset)?);
731        }
732        Ok(PosixTimeZone { std_abbrev, std_offset, dst })
733    }
734
735    /// Parse a DST zone with an optional explicit transition rule.
736    ///
737    /// This assumes the parser is positioned at the first byte of the DST
738    /// abbreviation.
739    ///
740    /// Upon success, the parser will be positioned immediately after the end
741    /// of the DST transition rule (which might just be the abbreviation, but
742    /// might also include explicit start/end datetime specifications).
743    fn parse_posix_dst(
744        &self,
745        std_offset: &PosixOffset,
746    ) -> Result<PosixDst<Abbreviation>, PosixTimeZoneError> {
747        let abbrev =
748            self.parse_abbreviation().map_err(ErrorKind::AbbreviationDst)?;
749        if self.is_done() {
750            return Err(ErrorKind::FoundDstNoRule.into());
751        }
752        // This is the default: one hour ahead of standard time. We may
753        // override this if the DST portion specifies an offset. (But it
754        // usually doesn't.)
755        let mut offset = PosixOffset { second: std_offset.second + 3600 };
756        if self.byte() != b',' {
757            offset =
758                self.parse_posix_offset().map_err(ErrorKind::OffsetDst)?;
759            if self.is_done() {
760                return Err(ErrorKind::FoundDstNoRuleWithOffset.into());
761            }
762        }
763        if self.byte() != b',' {
764            return Err(ErrorKind::ExpectedCommaAfterDst.into());
765        }
766        if !self.bump() {
767            return Err(ErrorKind::FoundEndAfterComma.into());
768        }
769        let rule = self.parse_rule().map_err(ErrorKind::Rule)?;
770        Ok(PosixDst { abbrev, offset, rule })
771    }
772
773    /// Parse a time zone abbreviation.
774    ///
775    /// This assumes the parser is positioned at the first byte of
776    /// the abbreviation. This is either the first character in the
777    /// abbreviation, or the opening quote of a quoted abbreviation.
778    ///
779    /// Upon success, the parser will be positioned immediately following
780    /// the abbreviation name.
781    ///
782    /// The string returned is guaranteed to be no more than 30 bytes.
783    /// (This restriction is somewhat arbitrary, but it's so we can put
784    /// the abbreviation in a fixed capacity array.)
785    fn parse_abbreviation(&self) -> Result<Abbreviation, AbbreviationError> {
786        if self.byte() == b'<' {
787            if !self.bump() {
788                return Err(AbbreviationError::Quoted(
789                    QuotedAbbreviationError::UnexpectedEndAfterOpening,
790                ));
791            }
792            self.parse_quoted_abbreviation().map_err(AbbreviationError::Quoted)
793        } else {
794            self.parse_unquoted_abbreviation()
795                .map_err(AbbreviationError::Unquoted)
796        }
797    }
798
799    /// Parses an unquoted time zone abbreviation.
800    ///
801    /// This assumes the parser is position at the first byte in the
802    /// abbreviation.
803    ///
804    /// Upon success, the parser will be positioned immediately after the
805    /// last byte in the abbreviation.
806    ///
807    /// The string returned is guaranteed to be no more than 30 bytes.
808    /// (This restriction is somewhat arbitrary, but it's so we can put
809    /// the abbreviation in a fixed capacity array.)
810    fn parse_unquoted_abbreviation(
811        &self,
812    ) -> Result<Abbreviation, UnquotedAbbreviationError> {
813        let start = self.pos();
814        for i in 0.. {
815            if !self.byte().is_ascii_alphabetic() {
816                break;
817            }
818            if i >= Abbreviation::capacity() {
819                return Err(UnquotedAbbreviationError::TooLong);
820            }
821            if !self.bump() {
822                break;
823            }
824        }
825        let end = self.pos();
826        let abbrev =
827            core::str::from_utf8(&self.tz[start..end]).map_err(|_| {
828                // NOTE: I believe this error is technically impossible
829                // since the loop above restricts letters in an
830                // abbreviation to ASCII. So everything from `start` to
831                // `end` is ASCII and thus should be UTF-8. But it doesn't
832                // cost us anything to report an error here in case the
833                // code above evolves somehow.
834                UnquotedAbbreviationError::InvalidUtf8
835            })?;
836        if abbrev.len() < 3 {
837            return Err(UnquotedAbbreviationError::TooShort);
838        }
839        // OK because we verified above that the abbreviation
840        // does not exceed `Abbreviation::capacity`.
841        Ok(Abbreviation::new(abbrev).unwrap())
842    }
843
844    /// Parses a quoted time zone abbreviation.
845    ///
846    /// This assumes the parser is positioned immediately after the opening
847    /// `<` quote. That is, at the first byte in the abbreviation.
848    ///
849    /// Upon success, the parser will be positioned immediately after the
850    /// closing `>` quote.
851    ///
852    /// The string returned is guaranteed to be no more than 30 bytes.
853    /// (This restriction is somewhat arbitrary, but it's so we can put
854    /// the abbreviation in a fixed capacity array.)
855    fn parse_quoted_abbreviation(
856        &self,
857    ) -> Result<Abbreviation, QuotedAbbreviationError> {
858        let start = self.pos();
859        for i in 0.. {
860            if !self.byte().is_ascii_alphanumeric()
861                && self.byte() != b'+'
862                && self.byte() != b'-'
863            {
864                break;
865            }
866            if i >= Abbreviation::capacity() {
867                return Err(QuotedAbbreviationError::TooLong);
868            }
869            if !self.bump() {
870                break;
871            }
872        }
873        let end = self.pos();
874        let abbrev =
875            core::str::from_utf8(&self.tz[start..end]).map_err(|_| {
876                // NOTE: I believe this error is technically impossible
877                // since the loop above restricts letters in an
878                // abbreviation to ASCII. So everything from `start` to
879                // `end` is ASCII and thus should be UTF-8. But it doesn't
880                // cost us anything to report an error here in case the
881                // code above evolves somehow.
882                QuotedAbbreviationError::InvalidUtf8
883            })?;
884        if self.is_done() {
885            return Err(QuotedAbbreviationError::UnexpectedEnd);
886        }
887        if self.byte() != b'>' {
888            return Err(QuotedAbbreviationError::UnexpectedLastByte);
889        }
890        self.bump();
891        if abbrev.len() < 3 {
892            return Err(QuotedAbbreviationError::TooShort);
893        }
894        // OK because we verified above that the abbreviation
895        // does not exceed `Abbreviation::capacity`.
896        Ok(Abbreviation::new(abbrev).unwrap())
897    }
898
899    /// Parse a POSIX time offset.
900    ///
901    /// This assumes the parser is positioned at the first byte of the
902    /// offset. This can either be a digit (for a positive offset) or the
903    /// sign of the offset (which must be either `-` or `+`).
904    ///
905    /// Upon success, the parser will be positioned immediately after the
906    /// end of the offset.
907    fn parse_posix_offset(&self) -> Result<PosixOffset, PosixOffsetError> {
908        let sign = self.parse_optional_sign()?.unwrap_or(1);
909        let hour = self.parse_hour_posix()?;
910        let (mut minute, mut second) = (0, 0);
911        if self.maybe_byte() == Some(b':') {
912            if !self.bump() {
913                return Err(PosixOffsetError::IncompleteMinutes);
914            }
915            minute = self.parse_minute()?;
916            if self.maybe_byte() == Some(b':') {
917                if !self.bump() {
918                    return Err(PosixOffsetError::IncompleteSeconds);
919                }
920                second = self.parse_second()?;
921            }
922        }
923        let mut offset = PosixOffset { second: i32::from(hour) * 3600 };
924        offset.second += i32::from(minute) * 60;
925        offset.second += i32::from(second);
926        // Yes, we flip the sign, because POSIX is backwards.
927        // For example, `EST5` corresponds to `-05:00`.
928        offset.second *= i32::from(-sign);
929        // Must be true because the parsing routines for hours, minutes
930        // and seconds enforce they are in the ranges -24..=24, 0..=59
931        // and 0..=59, respectively.
932        assert!(
933            -89999 <= offset.second && offset.second <= 89999,
934            "POSIX offset seconds {} is out of range",
935            offset.second
936        );
937        Ok(offset)
938    }
939
940    /// Parses a POSIX DST transition rule.
941    ///
942    /// This assumes the parser is positioned at the first byte in the
943    /// rule. That is, it comes immediately after the DST abbreviation or
944    /// its optional offset.
945    ///
946    /// Upon success, the parser will be positioned immediately after the
947    /// DST transition rule. In typical cases, this corresponds to the end
948    /// of the TZ string.
949    fn parse_rule(&self) -> Result<PosixRule, PosixRuleError> {
950        let start = self
951            .parse_posix_datetime()
952            .map_err(PosixRuleError::DateTimeStart)?;
953        if self.maybe_byte() != Some(b',') || !self.bump() {
954            return Err(PosixRuleError::ExpectedEnd);
955        }
956        let end = self
957            .parse_posix_datetime()
958            .map_err(PosixRuleError::DateTimeEnd)?;
959        Ok(PosixRule { start, end })
960    }
961
962    /// Parses a POSIX datetime specification.
963    ///
964    /// This assumes the parser is position at the first byte where a
965    /// datetime specification is expected to occur.
966    ///
967    /// Upon success, the parser will be positioned after the datetime
968    /// specification. This will either be immediately after the date, or
969    /// if it's present, the time part of the specification.
970    fn parse_posix_datetime(
971        &self,
972    ) -> Result<PosixDayTime, PosixDateTimeError> {
973        let mut daytime = PosixDayTime {
974            date: self.parse_posix_date()?,
975            time: PosixTime::DEFAULT,
976        };
977        if self.maybe_byte() != Some(b'/') {
978            return Ok(daytime);
979        }
980        if !self.bump() {
981            return Err(PosixDateTimeError::ExpectedTime);
982        }
983        daytime.time = self.parse_posix_time()?;
984        Ok(daytime)
985    }
986
987    /// Parses a POSIX date specification.
988    ///
989    /// This assumes the parser is positioned at the first byte of the date
990    /// specification. This can be `J` (for one based Julian day without
991    /// leap days), `M` (for "weekday of month") or a digit starting the
992    /// zero based Julian day with leap days. This routine will validate
993    /// that the position points to one of these possible values. That is,
994    /// the caller doesn't need to parse the `M` or the `J` or the leading
995    /// digit. The caller should just call this routine when it *expect* a
996    /// date specification to follow.
997    ///
998    /// Upon success, the parser will be positioned immediately after the
999    /// date specification.
1000    fn parse_posix_date(&self) -> Result<PosixDay, PosixDateError> {
1001        match self.byte() {
1002            b'J' => {
1003                if !self.bump() {
1004                    return Err(PosixDateError::ExpectedJulianNoLeap);
1005                }
1006                Ok(PosixDay::JulianOne(self.parse_posix_julian_day_no_leap()?))
1007            }
1008            b'0'..=b'9' => Ok(PosixDay::JulianZero(
1009                self.parse_posix_julian_day_with_leap()?,
1010            )),
1011            b'M' => {
1012                if !self.bump() {
1013                    return Err(PosixDateError::ExpectedMonthWeekWeekday);
1014                }
1015                let (month, week, weekday) = self.parse_weekday_of_month()?;
1016                Ok(PosixDay::WeekdayOfMonth { month, week, weekday })
1017            }
1018            _ => Err(PosixDateError::UnexpectedByte),
1019        }
1020    }
1021
1022    /// Parses a POSIX Julian day that does not include leap days
1023    /// (`1 <= n <= 365`).
1024    ///
1025    /// This assumes the parser is positioned just after the `J` and at the
1026    /// first digit of the Julian day. Upon success, the parser will be
1027    /// positioned immediately following the day number.
1028    fn parse_posix_julian_day_no_leap(
1029        &self,
1030    ) -> Result<i16, PosixJulianNoLeapError> {
1031        let number = self
1032            .parse_number_with_upto_n_digits(3)
1033            .map_err(PosixJulianNoLeapError::Parse)?;
1034        let number = i16::try_from(number)
1035            .map_err(|_| PosixJulianNoLeapError::Range)?;
1036        if !(1 <= number && number <= 365) {
1037            return Err(PosixJulianNoLeapError::Range);
1038        }
1039        Ok(number)
1040    }
1041
1042    /// Parses a POSIX Julian day that includes leap days (`0 <= n <=
1043    /// 365`).
1044    ///
1045    /// This assumes the parser is positioned at the first digit of the
1046    /// Julian day. Upon success, the parser will be positioned immediately
1047    /// following the day number.
1048    fn parse_posix_julian_day_with_leap(
1049        &self,
1050    ) -> Result<i16, PosixJulianLeapError> {
1051        let number = self
1052            .parse_number_with_upto_n_digits(3)
1053            .map_err(PosixJulianLeapError::Parse)?;
1054        let number =
1055            i16::try_from(number).map_err(|_| PosixJulianLeapError::Range)?;
1056        if !(0 <= number && number <= 365) {
1057            return Err(PosixJulianLeapError::Range);
1058        }
1059        Ok(number)
1060    }
1061
1062    /// Parses a POSIX "weekday of month" specification.
1063    ///
1064    /// This assumes the parser is positioned just after the `M` byte and
1065    /// at the first digit of the month. Upon success, the parser will be
1066    /// positioned immediately following the "weekday of the month" that
1067    /// was parsed.
1068    ///
1069    /// The tuple returned is month (1..=12), week (1..=5) and weekday
1070    /// (0..=6 with 0=Sunday).
1071    fn parse_weekday_of_month(
1072        &self,
1073    ) -> Result<(i8, i8, i8), WeekdayOfMonthError> {
1074        let month = self.parse_month()?;
1075        if self.maybe_byte() != Some(b'.') {
1076            return Err(WeekdayOfMonthError::ExpectedDotAfterMonth);
1077        }
1078        if !self.bump() {
1079            return Err(WeekdayOfMonthError::ExpectedWeekAfterMonth);
1080        }
1081        let week = self.parse_week()?;
1082        if self.maybe_byte() != Some(b'.') {
1083            return Err(WeekdayOfMonthError::ExpectedDotAfterWeek);
1084        }
1085        if !self.bump() {
1086            return Err(WeekdayOfMonthError::ExpectedDayOfWeekAfterWeek);
1087        }
1088        let weekday = self.parse_weekday()?;
1089        Ok((month, week, weekday))
1090    }
1091
1092    /// This parses a POSIX time specification in the format
1093    /// `[+/-]hh?[:mm[:ss]]`.
1094    ///
1095    /// This assumes the parser is positioned at the first `h` (or the
1096    /// sign, if present). Upon success, the parser will be positioned
1097    /// immediately following the end of the time specification.
1098    fn parse_posix_time(&self) -> Result<PosixTime, PosixTimeError> {
1099        let (sign, hour) = if self.ianav3plus {
1100            let sign = self.parse_optional_sign()?.unwrap_or(1);
1101            let hour = self.parse_hour_ianav3plus()?;
1102            (sign, hour)
1103        } else {
1104            (1, i16::from(self.parse_hour_posix()?))
1105        };
1106        let (mut minute, mut second) = (0, 0);
1107        if self.maybe_byte() == Some(b':') {
1108            if !self.bump() {
1109                return Err(PosixTimeError::IncompleteMinutes);
1110            }
1111            minute = self.parse_minute()?;
1112            if self.maybe_byte() == Some(b':') {
1113                if !self.bump() {
1114                    return Err(PosixTimeError::IncompleteSeconds);
1115                }
1116                second = self.parse_second()?;
1117            }
1118        }
1119        let mut time = PosixTime { second: i32::from(hour) * 3600 };
1120        time.second += i32::from(minute) * 60;
1121        time.second += i32::from(second);
1122        time.second *= i32::from(sign);
1123        // Must be true because the parsing routines for hours, minutes
1124        // and seconds enforce they are in the ranges -167..=167, 0..=59
1125        // and 0..=59, respectively.
1126        assert!(
1127            -604799 <= time.second && time.second <= 604799,
1128            "POSIX time seconds {} is out of range",
1129            time.second
1130        );
1131        Ok(time)
1132    }
1133
1134    /// Parses a month.
1135    ///
1136    /// This is expected to be positioned at the first digit. Upon success,
1137    /// the parser will be positioned after the month (which may contain
1138    /// two digits).
1139    fn parse_month(&self) -> Result<i8, MonthError> {
1140        let number = self
1141            .parse_number_with_upto_n_digits(2)
1142            .map_err(MonthError::Parse)?;
1143        let number = i8::try_from(number).map_err(|_| MonthError::Range)?;
1144        if !(1 <= number && number <= 12) {
1145            return Err(MonthError::Range);
1146        }
1147        Ok(number)
1148    }
1149
1150    /// Parses a week-of-month number.
1151    ///
1152    /// This is expected to be positioned at the first digit. Upon success,
1153    /// the parser will be positioned after the week digit.
1154    fn parse_week(&self) -> Result<i8, WeekOfMonthError> {
1155        let number = self
1156            .parse_number_with_exactly_n_digits(1)
1157            .map_err(WeekOfMonthError::Parse)?;
1158        let number =
1159            i8::try_from(number).map_err(|_| WeekOfMonthError::Range)?;
1160        if !(1 <= number && number <= 5) {
1161            return Err(WeekOfMonthError::Range);
1162        }
1163        Ok(number)
1164    }
1165
1166    /// Parses a weekday number.
1167    ///
1168    /// This is expected to be positioned at the first digit. Upon success,
1169    /// the parser will be positioned after the week digit.
1170    ///
1171    /// The weekday returned is guaranteed to be in the range `0..=6`, with
1172    /// `0` corresponding to Sunday.
1173    fn parse_weekday(&self) -> Result<i8, WeekdayError> {
1174        let number = self
1175            .parse_number_with_exactly_n_digits(1)
1176            .map_err(WeekdayError::Parse)?;
1177        let number = i8::try_from(number).map_err(|_| WeekdayError::Range)?;
1178        if !(0 <= number && number <= 6) {
1179            return Err(WeekdayError::Range);
1180        }
1181        Ok(number)
1182    }
1183
1184    /// Parses an hour from a POSIX time specification with the IANA
1185    /// v3+ extension. That is, the hour may be in the range `0..=167`.
1186    /// (Callers should parse an optional sign preceding the hour digits
1187    /// when IANA V3+ parsing is enabled.)
1188    ///
1189    /// The hour is allowed to be a single digit (unlike minutes or
1190    /// seconds).
1191    ///
1192    /// This assumes the parser is positioned at the position where the
1193    /// first hour digit should occur. Upon success, the parser will be
1194    /// positioned immediately after the last hour digit.
1195    fn parse_hour_ianav3plus(&self) -> Result<i16, HourIanaError> {
1196        // Callers should only be using this method when IANA v3+ parsing
1197        // is enabled.
1198        assert!(self.ianav3plus);
1199        let number = self
1200            .parse_number_with_upto_n_digits(3)
1201            .map_err(HourIanaError::Parse)?;
1202        let number =
1203            i16::try_from(number).map_err(|_| HourIanaError::Range)?;
1204        if !(0 <= number && number <= 167) {
1205            // The error message says -167 but the check above uses 0.
1206            // This is because the caller is responsible for parsing
1207            // the sign.
1208            return Err(HourIanaError::Range);
1209        }
1210        Ok(number)
1211    }
1212
1213    /// Parses an hour from a POSIX time specification, with the allowed
1214    /// range being `0..=24`.
1215    ///
1216    /// The hour is allowed to be a single digit (unlike minutes or
1217    /// seconds).
1218    ///
1219    /// This assumes the parser is positioned at the position where the
1220    /// first hour digit should occur. Upon success, the parser will be
1221    /// positioned immediately after the last hour digit.
1222    fn parse_hour_posix(&self) -> Result<i8, HourPosixError> {
1223        let number = self
1224            .parse_number_with_upto_n_digits(2)
1225            .map_err(HourPosixError::Parse)?;
1226        let number =
1227            i8::try_from(number).map_err(|_| HourPosixError::Range)?;
1228        if !(0 <= number && number <= 24) {
1229            return Err(HourPosixError::Range);
1230        }
1231        Ok(number)
1232    }
1233
1234    /// Parses a minute from a POSIX time specification.
1235    ///
1236    /// The minute must be exactly two digits.
1237    ///
1238    /// This assumes the parser is positioned at the position where the
1239    /// first minute digit should occur. Upon success, the parser will be
1240    /// positioned immediately after the second minute digit.
1241    fn parse_minute(&self) -> Result<i8, MinuteError> {
1242        let number = self
1243            .parse_number_with_exactly_n_digits(2)
1244            .map_err(MinuteError::Parse)?;
1245        let number = i8::try_from(number).map_err(|_| MinuteError::Range)?;
1246        if !(0 <= number && number <= 59) {
1247            return Err(MinuteError::Range);
1248        }
1249        Ok(number)
1250    }
1251
1252    /// Parses a second from a POSIX time specification.
1253    ///
1254    /// The second must be exactly two digits.
1255    ///
1256    /// This assumes the parser is positioned at the position where the
1257    /// first second digit should occur. Upon success, the parser will be
1258    /// positioned immediately after the second second digit.
1259    fn parse_second(&self) -> Result<i8, SecondError> {
1260        let number = self
1261            .parse_number_with_exactly_n_digits(2)
1262            .map_err(SecondError::Parse)?;
1263        let number = i8::try_from(number).map_err(|_| SecondError::Range)?;
1264        if !(0 <= number && number <= 59) {
1265            return Err(SecondError::Range);
1266        }
1267        Ok(number)
1268    }
1269
1270    /// Parses a signed 64-bit integer expressed in exactly `n` digits.
1271    ///
1272    /// If `n` digits could not be found (or if the `TZ` string ends before
1273    /// `n` digits could be found), then this returns an error.
1274    ///
1275    /// This assumes that `n >= 1` and that the parser is positioned at the
1276    /// first digit. Upon success, the parser is positioned immediately
1277    /// after the `n`th digit.
1278    fn parse_number_with_exactly_n_digits(
1279        &self,
1280        n: usize,
1281    ) -> Result<i32, NumberError> {
1282        assert!(n >= 1, "numbers must have at least 1 digit");
1283        let mut number: i32 = 0;
1284        for _ in 0..n {
1285            if self.is_done() {
1286                return Err(NumberError::ExpectedLength);
1287            }
1288            let byte = self.byte();
1289            let digit = match byte.checked_sub(b'0') {
1290                None => {
1291                    return Err(NumberError::InvalidDigit);
1292                }
1293                Some(digit) if digit > 9 => {
1294                    return Err(NumberError::InvalidDigit);
1295                }
1296                Some(digit) => {
1297                    debug_assert!((0..=9).contains(&digit));
1298                    i32::from(digit)
1299                }
1300            };
1301            number = number
1302                .checked_mul(10)
1303                .and_then(|n| n.checked_add(digit))
1304                .ok_or(NumberError::TooBig)?;
1305            self.bump();
1306        }
1307        Ok(number)
1308    }
1309
1310    /// Parses a signed 64-bit integer expressed with up to `n` digits and
1311    /// at least 1 digit.
1312    ///
1313    /// This assumes that `n >= 1` and that the parser is positioned at the
1314    /// first digit. Upon success, the parser is position immediately after
1315    /// the last digit (which can be at most `n`).
1316    fn parse_number_with_upto_n_digits(
1317        &self,
1318        n: usize,
1319    ) -> Result<i32, NumberError> {
1320        assert!(n >= 1, "numbers must have at least 1 digit");
1321        let mut number: i32 = 0;
1322        for i in 0..n {
1323            if self.is_done() || !self.byte().is_ascii_digit() {
1324                if i == 0 {
1325                    return Err(NumberError::Empty);
1326                }
1327                break;
1328            }
1329            let digit = i32::from(self.byte() - b'0');
1330            number = number
1331                .checked_mul(10)
1332                .and_then(|n| n.checked_add(digit))
1333                .ok_or(NumberError::TooBig)?;
1334            self.bump();
1335        }
1336        Ok(number)
1337    }
1338
1339    /// Parses an optional sign.
1340    ///
1341    /// This assumes the parser is positioned at the position where a
1342    /// positive or negative sign is permitted. If one exists, then it
1343    /// is consumed and returned. Moreover, if one exists, then this
1344    /// guarantees that it is not the last byte in the input. That is, upon
1345    /// success, it is valid to call `self.byte()`.
1346    fn parse_optional_sign(&self) -> Result<Option<i8>, OptionalSignError> {
1347        if self.is_done() {
1348            return Ok(None);
1349        }
1350        Ok(match self.byte() {
1351            b'-' => {
1352                if !self.bump() {
1353                    return Err(OptionalSignError::ExpectedDigitAfterMinus);
1354                }
1355                Some(-1)
1356            }
1357            b'+' => {
1358                if !self.bump() {
1359                    return Err(OptionalSignError::ExpectedDigitAfterPlus);
1360                }
1361                Some(1)
1362            }
1363            _ => None,
1364        })
1365    }
1366}
1367
1368/// Helper routines for parsing a POSIX `TZ` string.
1369impl<'s> Parser<'s> {
1370    /// Bump the parser to the next byte.
1371    ///
1372    /// If the end of the input has been reached, then `false` is returned.
1373    fn bump(&self) -> bool {
1374        if self.is_done() {
1375            return false;
1376        }
1377        self.pos.set(
1378            self.pos().checked_add(1).expect("pos cannot overflow usize"),
1379        );
1380        !self.is_done()
1381    }
1382
1383    /// Returns true if the next call to `bump` would return false.
1384    fn is_done(&self) -> bool {
1385        self.pos() == self.tz.len()
1386    }
1387
1388    /// Return the byte at the current position of the parser.
1389    ///
1390    /// This panics if the parser is positioned at the end of the TZ
1391    /// string.
1392    fn byte(&self) -> u8 {
1393        self.tz[self.pos()]
1394    }
1395
1396    /// Return the byte at the current position of the parser. If the TZ
1397    /// string has been exhausted, then this returns `None`.
1398    fn maybe_byte(&self) -> Option<u8> {
1399        self.tz.get(self.pos()).copied()
1400    }
1401
1402    /// Return the current byte offset of the parser.
1403    ///
1404    /// The offset starts at `0` from the beginning of the TZ string.
1405    fn pos(&self) -> usize {
1406        self.pos.get()
1407    }
1408
1409    /// Returns the remaining bytes of the TZ string.
1410    ///
1411    /// This includes `self.byte()`. It may be empty.
1412    fn remaining(&self) -> &'s [u8] {
1413        &self.tz[self.pos()..]
1414    }
1415}
1416
1417#[derive(Clone, Debug, Eq, PartialEq)]
1418pub struct PosixTimeZoneError {
1419    kind: ErrorKind,
1420}
1421
1422#[derive(Clone, Debug, Eq, PartialEq)]
1423enum ErrorKind {
1424    AbbreviationDst(AbbreviationError),
1425    AbbreviationStd(AbbreviationError),
1426    Empty,
1427    ExpectedCommaAfterDst,
1428    FoundDstNoRule,
1429    FoundDstNoRuleWithOffset,
1430    FoundEndAfterComma,
1431    FoundRemaining,
1432    OffsetDst(PosixOffsetError),
1433    OffsetStd(PosixOffsetError),
1434    Rule(PosixRuleError),
1435}
1436
1437impl core::fmt::Display for PosixTimeZoneError {
1438    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1439        use self::ErrorKind::*;
1440        match self.kind {
1441            AbbreviationDst(ref err) => {
1442                f.write_str("failed to parse DST time zone abbreviation: ")?;
1443                core::fmt::Display::fmt(err, f)
1444            }
1445            AbbreviationStd(ref err) => {
1446                f.write_str(
1447                    "failed to parse standard time zone abbreviation: ",
1448                )?;
1449                core::fmt::Display::fmt(err, f)
1450            }
1451            Empty => f.write_str(
1452                "an empty string is not a valid POSIX time zone \
1453                 transition rule",
1454            ),
1455            ExpectedCommaAfterDst => f.write_str(
1456                "expected `,` after parsing DST offset \
1457                 in POSIX time zone string",
1458            ),
1459            FoundDstNoRule => f.write_str(
1460                "found DST abbreviation in POSIX time zone string, \
1461                 but no transition rule \
1462                 (this is technically allowed by POSIX, but has \
1463                 unspecified behavior)",
1464            ),
1465            FoundDstNoRuleWithOffset => f.write_str(
1466                "found DST abbreviation and offset in POSIX time zone string, \
1467                 but no transition rule \
1468                 (this is technically allowed by POSIX, but has \
1469                 unspecified behavior)",
1470            ),
1471            FoundEndAfterComma => f.write_str(
1472                "after parsing DST offset in POSIX time zone string, \
1473                 found end of string after a trailing `,`",
1474            ),
1475            FoundRemaining => f.write_str(
1476                "expected entire POSIX TZ string to be a valid \
1477                 time zone transition rule, but found data after \
1478                 parsing a valid time zone transition rule",
1479            ),
1480            OffsetDst(ref err) => {
1481                f.write_str("failed to parse DST offset: ")?;
1482                core::fmt::Display::fmt(err, f)
1483            }
1484            OffsetStd(ref err) => {
1485                f.write_str("failed to parse standard offset: ")?;
1486                core::fmt::Display::fmt(err, f)
1487            }
1488            Rule(ref err) => core::fmt::Display::fmt(err, f),
1489        }
1490    }
1491}
1492
1493impl From<ErrorKind> for PosixTimeZoneError {
1494    fn from(kind: ErrorKind) -> PosixTimeZoneError {
1495        PosixTimeZoneError { kind }
1496    }
1497}
1498
1499#[derive(Clone, Debug, Eq, PartialEq)]
1500enum PosixOffsetError {
1501    HourPosix(HourPosixError),
1502    IncompleteMinutes,
1503    IncompleteSeconds,
1504    Minute(MinuteError),
1505    OptionalSign(OptionalSignError),
1506    Second(SecondError),
1507}
1508
1509impl core::fmt::Display for PosixOffsetError {
1510    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1511        use self::PosixOffsetError::*;
1512        match *self {
1513            HourPosix(ref err) => core::fmt::Display::fmt(err, f),
1514            IncompleteMinutes => f.write_str(
1515                "incomplete time in \
1516                 POSIX time zone string (missing minutes)",
1517            ),
1518            IncompleteSeconds => f.write_str(
1519                "incomplete time in \
1520                 POSIX time zone string (missing seconds)",
1521            ),
1522            Minute(ref err) => core::fmt::Display::fmt(err, f),
1523            Second(ref err) => core::fmt::Display::fmt(err, f),
1524            OptionalSign(ref err) => {
1525                f.write_str(
1526                    "failed to parse sign for time offset \
1527                     POSIX time zone string",
1528                )?;
1529                core::fmt::Display::fmt(err, f)
1530            }
1531        }
1532    }
1533}
1534
1535impl From<HourPosixError> for PosixOffsetError {
1536    fn from(err: HourPosixError) -> PosixOffsetError {
1537        PosixOffsetError::HourPosix(err)
1538    }
1539}
1540
1541impl From<MinuteError> for PosixOffsetError {
1542    fn from(err: MinuteError) -> PosixOffsetError {
1543        PosixOffsetError::Minute(err)
1544    }
1545}
1546
1547impl From<OptionalSignError> for PosixOffsetError {
1548    fn from(err: OptionalSignError) -> PosixOffsetError {
1549        PosixOffsetError::OptionalSign(err)
1550    }
1551}
1552
1553impl From<SecondError> for PosixOffsetError {
1554    fn from(err: SecondError) -> PosixOffsetError {
1555        PosixOffsetError::Second(err)
1556    }
1557}
1558
1559#[derive(Clone, Debug, Eq, PartialEq)]
1560enum PosixRuleError {
1561    DateTimeEnd(PosixDateTimeError),
1562    DateTimeStart(PosixDateTimeError),
1563    ExpectedEnd,
1564}
1565
1566impl core::fmt::Display for PosixRuleError {
1567    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1568        use self::PosixRuleError::*;
1569        match *self {
1570            DateTimeEnd(ref err) => {
1571                f.write_str("failed to parse end of DST transition rule: ")?;
1572                core::fmt::Display::fmt(err, f)
1573            }
1574            DateTimeStart(ref err) => {
1575                f.write_str("failed to parse start of DST transition rule: ")?;
1576                core::fmt::Display::fmt(err, f)
1577            }
1578            ExpectedEnd => f.write_str(
1579                "expected end of DST rule after parsing the start \
1580                 of the DST rule",
1581            ),
1582        }
1583    }
1584}
1585
1586#[derive(Clone, Debug, Eq, PartialEq)]
1587enum PosixDateTimeError {
1588    Date(PosixDateError),
1589    ExpectedTime,
1590    Time(PosixTimeError),
1591}
1592
1593impl core::fmt::Display for PosixDateTimeError {
1594    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1595        use self::PosixDateTimeError::*;
1596        match *self {
1597            Date(ref err) => core::fmt::Display::fmt(err, f),
1598            ExpectedTime => f.write_str(
1599                "expected time specification after `/` following a date
1600                 specification in a POSIX time zone DST transition rule",
1601            ),
1602            Time(ref err) => core::fmt::Display::fmt(err, f),
1603        }
1604    }
1605}
1606
1607impl From<PosixDateError> for PosixDateTimeError {
1608    fn from(err: PosixDateError) -> PosixDateTimeError {
1609        PosixDateTimeError::Date(err)
1610    }
1611}
1612
1613impl From<PosixTimeError> for PosixDateTimeError {
1614    fn from(err: PosixTimeError) -> PosixDateTimeError {
1615        PosixDateTimeError::Time(err)
1616    }
1617}
1618
1619#[derive(Clone, Debug, Eq, PartialEq)]
1620enum PosixDateError {
1621    ExpectedJulianNoLeap,
1622    ExpectedMonthWeekWeekday,
1623    JulianLeap(PosixJulianLeapError),
1624    JulianNoLeap(PosixJulianNoLeapError),
1625    UnexpectedByte,
1626    WeekdayOfMonth(WeekdayOfMonthError),
1627}
1628
1629impl core::fmt::Display for PosixDateError {
1630    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1631        use self::PosixDateError::*;
1632        match *self {
1633            ExpectedJulianNoLeap => f.write_str(
1634                "expected one-based Julian day after `J` in date \
1635                 specification of a POSIX time zone DST \
1636                 transition rule, but found the end of input",
1637            ),
1638            ExpectedMonthWeekWeekday => f.write_str(
1639                "expected month-week-weekday after `M` in date \
1640                 specification of a POSIX time zone DST \
1641                 transition rule, but found the end of input",
1642            ),
1643            JulianLeap(ref err) => core::fmt::Display::fmt(err, f),
1644            JulianNoLeap(ref err) => core::fmt::Display::fmt(err, f),
1645            UnexpectedByte => f.write_str(
1646                "expected `J`, a digit or `M` at the beginning of a date \
1647                 specification of a POSIX time zone DST transition rule",
1648            ),
1649            WeekdayOfMonth(ref err) => core::fmt::Display::fmt(err, f),
1650        }
1651    }
1652}
1653
1654impl From<PosixJulianLeapError> for PosixDateError {
1655    fn from(err: PosixJulianLeapError) -> PosixDateError {
1656        PosixDateError::JulianLeap(err)
1657    }
1658}
1659
1660impl From<PosixJulianNoLeapError> for PosixDateError {
1661    fn from(err: PosixJulianNoLeapError) -> PosixDateError {
1662        PosixDateError::JulianNoLeap(err)
1663    }
1664}
1665
1666impl From<WeekdayOfMonthError> for PosixDateError {
1667    fn from(err: WeekdayOfMonthError) -> PosixDateError {
1668        PosixDateError::WeekdayOfMonth(err)
1669    }
1670}
1671
1672#[derive(Clone, Debug, Eq, PartialEq)]
1673enum PosixJulianNoLeapError {
1674    Parse(NumberError),
1675    Range,
1676}
1677
1678impl core::fmt::Display for PosixJulianNoLeapError {
1679    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1680        use self::PosixJulianNoLeapError::*;
1681        match *self {
1682            Parse(ref err) => {
1683                f.write_str("invalid one-based Julian day digits: ")?;
1684                core::fmt::Display::fmt(err, f)
1685            }
1686            Range => f.write_str(
1687                "parsed one-based Julian day, but it's not in supported \
1688                 range of `1..=365`",
1689            ),
1690        }
1691    }
1692}
1693
1694#[derive(Clone, Debug, Eq, PartialEq)]
1695enum PosixJulianLeapError {
1696    Parse(NumberError),
1697    Range,
1698}
1699
1700impl core::fmt::Display for PosixJulianLeapError {
1701    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1702        use self::PosixJulianLeapError::*;
1703        match *self {
1704            Parse(ref err) => {
1705                f.write_str("invalid zero-based Julian day digits: ")?;
1706                core::fmt::Display::fmt(err, f)
1707            }
1708            Range => f.write_str(
1709                "parsed zero-based Julian day, but it's not in supported \
1710                 range of `0..=365`",
1711            ),
1712        }
1713    }
1714}
1715
1716#[derive(Clone, Debug, Eq, PartialEq)]
1717enum AbbreviationError {
1718    Quoted(QuotedAbbreviationError),
1719    Unquoted(UnquotedAbbreviationError),
1720}
1721
1722impl core::fmt::Display for AbbreviationError {
1723    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1724        use self::AbbreviationError::*;
1725        match *self {
1726            Quoted(ref err) => core::fmt::Display::fmt(err, f),
1727            Unquoted(ref err) => core::fmt::Display::fmt(err, f),
1728        }
1729    }
1730}
1731
1732#[derive(Clone, Debug, Eq, PartialEq)]
1733enum UnquotedAbbreviationError {
1734    InvalidUtf8,
1735    TooLong,
1736    TooShort,
1737}
1738
1739impl core::fmt::Display for UnquotedAbbreviationError {
1740    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1741        use self::UnquotedAbbreviationError::*;
1742        match *self {
1743            InvalidUtf8 => f.write_str(
1744                "unquoted time zone abbreviation must be valid UTF-8",
1745            ),
1746            TooLong => write!(
1747                f,
1748                "expected unquoted time zone abbreviation with at most \
1749                 {} bytes, but found an abbreviation that is longer",
1750                Abbreviation::capacity(),
1751            ),
1752            TooShort => f.write_str(
1753                "expected unquoted time zone abbreviation to have length of \
1754                 3 or more bytes, but an abbreviation that is shorter",
1755            ),
1756        }
1757    }
1758}
1759
1760#[derive(Clone, Debug, Eq, PartialEq)]
1761enum QuotedAbbreviationError {
1762    InvalidUtf8,
1763    TooLong,
1764    TooShort,
1765    UnexpectedEnd,
1766    UnexpectedEndAfterOpening,
1767    UnexpectedLastByte,
1768}
1769
1770impl core::fmt::Display for QuotedAbbreviationError {
1771    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1772        use self::QuotedAbbreviationError::*;
1773        match *self {
1774            InvalidUtf8 => f.write_str(
1775                "quoted time zone abbreviation must be valid UTF-8",
1776            ),
1777            TooLong => write!(
1778                f,
1779                "expected quoted time zone abbreviation with at most \
1780                 {} bytes, but found an abbreviation that is longer",
1781                Abbreviation::capacity(),
1782            ),
1783            TooShort => f.write_str(
1784                "expected quoted time zone abbreviation to have length of \
1785                 3 or more bytes, but an abbreviation that is shorter",
1786            ),
1787            UnexpectedEnd => f.write_str(
1788                "found non-empty quoted time zone abbreviation, but \
1789                 found end of input before an end-of-quoted abbreviation \
1790                 `>` character",
1791            ),
1792            UnexpectedEndAfterOpening => f.write_str(
1793                "found opening `<` quote for time zone abbreviation in \
1794                 POSIX time zone transition rule, and expected a name \
1795                 following it, but found the end of input instead",
1796            ),
1797            UnexpectedLastByte => f.write_str(
1798                "found non-empty quoted time zone abbreviation, but \
1799                 found did not find end-of-quoted abbreviation `>` \
1800                 character",
1801            ),
1802        }
1803    }
1804}
1805
1806#[derive(Clone, Debug, Eq, PartialEq)]
1807enum WeekdayOfMonthError {
1808    ExpectedDayOfWeekAfterWeek,
1809    ExpectedDotAfterMonth,
1810    ExpectedDotAfterWeek,
1811    ExpectedWeekAfterMonth,
1812    Month(MonthError),
1813    WeekOfMonth(WeekOfMonthError),
1814    Weekday(WeekdayError),
1815}
1816
1817impl core::fmt::Display for WeekdayOfMonthError {
1818    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1819        use self::WeekdayOfMonthError::*;
1820        match *self {
1821            ExpectedDayOfWeekAfterWeek => f.write_str(
1822                "expected day-of-week after week in POSIX time zone rule",
1823            ),
1824            ExpectedDotAfterMonth => {
1825                f.write_str("expected `.` after month in POSIX time zone rule")
1826            }
1827            ExpectedWeekAfterMonth => f.write_str(
1828                "expected week after month in POSIX time zone rule",
1829            ),
1830            ExpectedDotAfterWeek => {
1831                f.write_str("expected `.` after week in POSIX time zone rule")
1832            }
1833            Month(ref err) => core::fmt::Display::fmt(err, f),
1834            WeekOfMonth(ref err) => core::fmt::Display::fmt(err, f),
1835            Weekday(ref err) => core::fmt::Display::fmt(err, f),
1836        }
1837    }
1838}
1839
1840impl From<MonthError> for WeekdayOfMonthError {
1841    fn from(err: MonthError) -> WeekdayOfMonthError {
1842        WeekdayOfMonthError::Month(err)
1843    }
1844}
1845
1846impl From<WeekOfMonthError> for WeekdayOfMonthError {
1847    fn from(err: WeekOfMonthError) -> WeekdayOfMonthError {
1848        WeekdayOfMonthError::WeekOfMonth(err)
1849    }
1850}
1851
1852impl From<WeekdayError> for WeekdayOfMonthError {
1853    fn from(err: WeekdayError) -> WeekdayOfMonthError {
1854        WeekdayOfMonthError::Weekday(err)
1855    }
1856}
1857
1858#[derive(Clone, Debug, Eq, PartialEq)]
1859enum PosixTimeError {
1860    HourIana(HourIanaError),
1861    HourPosix(HourPosixError),
1862    IncompleteMinutes,
1863    IncompleteSeconds,
1864    Minute(MinuteError),
1865    OptionalSign(OptionalSignError),
1866    Second(SecondError),
1867}
1868
1869impl core::fmt::Display for PosixTimeError {
1870    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1871        use self::PosixTimeError::*;
1872        match *self {
1873            HourIana(ref err) => core::fmt::Display::fmt(err, f),
1874            HourPosix(ref err) => core::fmt::Display::fmt(err, f),
1875            IncompleteMinutes => f.write_str(
1876                "incomplete time zone transition time in \
1877                 POSIX time zone string (missing minutes)",
1878            ),
1879            IncompleteSeconds => f.write_str(
1880                "incomplete time zone transition time in \
1881                 POSIX time zone string (missing seconds)",
1882            ),
1883            Minute(ref err) => core::fmt::Display::fmt(err, f),
1884            Second(ref err) => core::fmt::Display::fmt(err, f),
1885            OptionalSign(ref err) => {
1886                f.write_str(
1887                    "failed to parse sign for time zone transition time",
1888                )?;
1889                core::fmt::Display::fmt(err, f)
1890            }
1891        }
1892    }
1893}
1894
1895impl From<HourIanaError> for PosixTimeError {
1896    fn from(err: HourIanaError) -> PosixTimeError {
1897        PosixTimeError::HourIana(err)
1898    }
1899}
1900
1901impl From<HourPosixError> for PosixTimeError {
1902    fn from(err: HourPosixError) -> PosixTimeError {
1903        PosixTimeError::HourPosix(err)
1904    }
1905}
1906
1907impl From<MinuteError> for PosixTimeError {
1908    fn from(err: MinuteError) -> PosixTimeError {
1909        PosixTimeError::Minute(err)
1910    }
1911}
1912
1913impl From<OptionalSignError> for PosixTimeError {
1914    fn from(err: OptionalSignError) -> PosixTimeError {
1915        PosixTimeError::OptionalSign(err)
1916    }
1917}
1918
1919impl From<SecondError> for PosixTimeError {
1920    fn from(err: SecondError) -> PosixTimeError {
1921        PosixTimeError::Second(err)
1922    }
1923}
1924
1925#[derive(Clone, Debug, Eq, PartialEq)]
1926enum MonthError {
1927    Parse(NumberError),
1928    Range,
1929}
1930
1931impl core::fmt::Display for MonthError {
1932    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1933        use self::MonthError::*;
1934        match *self {
1935            Parse(ref err) => {
1936                f.write_str("invalid month digits: ")?;
1937                core::fmt::Display::fmt(err, f)
1938            }
1939            Range => f.write_str(
1940                "parsed month, but it's not in supported \
1941                 range of `1..=12`",
1942            ),
1943        }
1944    }
1945}
1946
1947#[derive(Clone, Debug, Eq, PartialEq)]
1948enum WeekOfMonthError {
1949    Parse(NumberError),
1950    Range,
1951}
1952
1953impl core::fmt::Display for WeekOfMonthError {
1954    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1955        use self::WeekOfMonthError::*;
1956        match *self {
1957            Parse(ref err) => {
1958                f.write_str("invalid week-of-month digits: ")?;
1959                core::fmt::Display::fmt(err, f)
1960            }
1961            Range => f.write_str(
1962                "parsed week-of-month, but it's not in supported \
1963                 range of `1..=5`",
1964            ),
1965        }
1966    }
1967}
1968
1969#[derive(Clone, Debug, Eq, PartialEq)]
1970enum WeekdayError {
1971    Parse(NumberError),
1972    Range,
1973}
1974
1975impl core::fmt::Display for WeekdayError {
1976    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1977        use self::WeekdayError::*;
1978        match *self {
1979            Parse(ref err) => {
1980                f.write_str("invalid weekday digits: ")?;
1981                core::fmt::Display::fmt(err, f)
1982            }
1983            Range => f.write_str(
1984                "parsed weekday, but it's not in supported \
1985                 range of `0..=6` (with `0` corresponding to Sunday)",
1986            ),
1987        }
1988    }
1989}
1990
1991#[derive(Clone, Debug, Eq, PartialEq)]
1992enum HourIanaError {
1993    Parse(NumberError),
1994    Range,
1995}
1996
1997impl core::fmt::Display for HourIanaError {
1998    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1999        use self::HourIanaError::*;
2000        match *self {
2001            Parse(ref err) => {
2002                f.write_str("invalid hour digits: ")?;
2003                core::fmt::Display::fmt(err, f)
2004            }
2005            Range => f.write_str(
2006                "parsed hours, but it's not in supported \
2007                 range of `-167..=167`",
2008            ),
2009        }
2010    }
2011}
2012
2013#[derive(Clone, Debug, Eq, PartialEq)]
2014enum HourPosixError {
2015    Parse(NumberError),
2016    Range,
2017}
2018
2019impl core::fmt::Display for HourPosixError {
2020    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2021        use self::HourPosixError::*;
2022        match *self {
2023            Parse(ref err) => {
2024                f.write_str("invalid hour digits: ")?;
2025                core::fmt::Display::fmt(err, f)
2026            }
2027            Range => f.write_str(
2028                "parsed hours, but it's not in supported \
2029                 range of `0..=24`",
2030            ),
2031        }
2032    }
2033}
2034
2035#[derive(Clone, Debug, Eq, PartialEq)]
2036enum MinuteError {
2037    Parse(NumberError),
2038    Range,
2039}
2040
2041impl core::fmt::Display for MinuteError {
2042    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2043        use self::MinuteError::*;
2044        match *self {
2045            Parse(ref err) => {
2046                f.write_str("invalid minute digits: ")?;
2047                core::fmt::Display::fmt(err, f)
2048            }
2049            Range => f.write_str(
2050                "parsed minutes, but it's not in supported \
2051                 range of `0..=59`",
2052            ),
2053        }
2054    }
2055}
2056
2057#[derive(Clone, Debug, Eq, PartialEq)]
2058enum SecondError {
2059    Parse(NumberError),
2060    Range,
2061}
2062
2063impl core::fmt::Display for SecondError {
2064    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2065        use self::SecondError::*;
2066        match *self {
2067            Parse(ref err) => {
2068                f.write_str("invalid second digits: ")?;
2069                core::fmt::Display::fmt(err, f)
2070            }
2071            Range => f.write_str(
2072                "parsed seconds, but it's not in supported \
2073                 range of `0..=59`",
2074            ),
2075        }
2076    }
2077}
2078
2079#[derive(Clone, Debug, Eq, PartialEq)]
2080enum NumberError {
2081    Empty,
2082    ExpectedLength,
2083    InvalidDigit,
2084    TooBig,
2085}
2086
2087impl core::fmt::Display for NumberError {
2088    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2089        use self::NumberError::*;
2090        match *self {
2091            Empty => f.write_str("invalid number, no digits found"),
2092            ExpectedLength => f.write_str(
2093                "expected a fixed number of digits, \
2094                 but found incorrect number",
2095            ),
2096            InvalidDigit => f.write_str("expected digit in range `0..=9`"),
2097            TooBig => f.write_str(
2098                "parsed number too big to fit into a 32-bit signed integer",
2099            ),
2100        }
2101    }
2102}
2103
2104#[derive(Clone, Debug, Eq, PartialEq)]
2105enum OptionalSignError {
2106    ExpectedDigitAfterMinus,
2107    ExpectedDigitAfterPlus,
2108}
2109
2110impl core::fmt::Display for OptionalSignError {
2111    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2112        use self::OptionalSignError::*;
2113        match *self {
2114            ExpectedDigitAfterMinus => f.write_str(
2115                "expected digit after `-` sign, \
2116                 but got end of input",
2117            ),
2118            ExpectedDigitAfterPlus => f.write_str(
2119                "expected digit after `+` sign, \
2120                 but got end of input",
2121            ),
2122        }
2123    }
2124}
2125
2126#[cfg(test)]
2127mod tests {
2128    use super::*;
2129
2130    fn posix_time_zone(
2131        input: impl AsRef<[u8]>,
2132    ) -> PosixTimeZone<Abbreviation> {
2133        let input = input.as_ref();
2134        let tz = PosixTimeZone::parse(input).unwrap();
2135        #[cfg(feature = "alloc")]
2136        {
2137            use alloc::string::ToString;
2138
2139            // While we're here, assert that converting the TZ back
2140            // to a string matches what we got. In the original version
2141            // of the POSIX TZ parser, we were very meticulous about
2142            // capturing the exact AST of the time zone. But I've
2143            // since simplified the data structure considerably such
2144            // that it is lossy in terms of what was actually parsed
2145            // (but of course, not lossy in terms of the semantic
2146            // meaning of the time zone).
2147            //
2148            // So to account for this, we serialize to a string and
2149            // then parse it back. We should get what we started with.
2150            let reparsed =
2151                PosixTimeZone::parse(tz.to_string().as_bytes()).unwrap();
2152            assert_eq!(tz, reparsed);
2153            assert_eq!(tz.to_string(), reparsed.to_string());
2154        }
2155        tz
2156    }
2157
2158    fn parser(s: &str) -> Parser<'_> {
2159        Parser::new(s.as_bytes())
2160    }
2161
2162    fn date(year: i16, month: i8, day: i8) -> IDate {
2163        IDate { year, month, day }
2164    }
2165
2166    #[test]
2167    fn parse() {
2168        let p = parser("NZST-12NZDT,J60,J300");
2169        assert_eq!(
2170            p.parse().unwrap(),
2171            PosixTimeZone {
2172                std_abbrev: "NZST".into(),
2173                std_offset: PosixOffset { second: 12 * 60 * 60 },
2174                dst: Some(PosixDst {
2175                    abbrev: "NZDT".into(),
2176                    offset: PosixOffset { second: 13 * 60 * 60 },
2177                    rule: PosixRule {
2178                        start: PosixDayTime {
2179                            date: PosixDay::JulianOne(60),
2180                            time: PosixTime { second: 2 * 60 * 60 },
2181                        },
2182                        end: PosixDayTime {
2183                            date: PosixDay::JulianOne(300),
2184                            time: PosixTime { second: 2 * 60 * 60 },
2185                        },
2186                    },
2187                }),
2188            },
2189        );
2190
2191        let p = Parser::new("NZST-12NZDT,J60,J300WAT");
2192        assert!(p.parse().is_err());
2193    }
2194
2195    #[test]
2196    fn parse_posix_time_zone() {
2197        let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3");
2198        assert_eq!(
2199            p.parse_posix_time_zone().unwrap(),
2200            PosixTimeZone {
2201                std_abbrev: "NZST".into(),
2202                std_offset: PosixOffset { second: 12 * 60 * 60 },
2203                dst: Some(PosixDst {
2204                    abbrev: "NZDT".into(),
2205                    offset: PosixOffset { second: 13 * 60 * 60 },
2206                    rule: PosixRule {
2207                        start: PosixDayTime {
2208                            date: PosixDay::WeekdayOfMonth {
2209                                month: 9,
2210                                week: 5,
2211                                weekday: 0,
2212                            },
2213                            time: PosixTime { second: 2 * 60 * 60 },
2214                        },
2215                        end: PosixDayTime {
2216                            date: PosixDay::WeekdayOfMonth {
2217                                month: 4,
2218                                week: 1,
2219                                weekday: 0,
2220                            },
2221                            time: PosixTime { second: 3 * 60 * 60 },
2222                        },
2223                    },
2224                }),
2225            },
2226        );
2227
2228        let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3WAT");
2229        assert_eq!(
2230            p.parse_posix_time_zone().unwrap(),
2231            PosixTimeZone {
2232                std_abbrev: "NZST".into(),
2233                std_offset: PosixOffset { second: 12 * 60 * 60 },
2234                dst: Some(PosixDst {
2235                    abbrev: "NZDT".into(),
2236                    offset: PosixOffset { second: 13 * 60 * 60 },
2237                    rule: PosixRule {
2238                        start: PosixDayTime {
2239                            date: PosixDay::WeekdayOfMonth {
2240                                month: 9,
2241                                week: 5,
2242                                weekday: 0,
2243                            },
2244                            time: PosixTime { second: 2 * 60 * 60 },
2245                        },
2246                        end: PosixDayTime {
2247                            date: PosixDay::WeekdayOfMonth {
2248                                month: 4,
2249                                week: 1,
2250                                weekday: 0,
2251                            },
2252                            time: PosixTime { second: 3 * 60 * 60 },
2253                        },
2254                    },
2255                }),
2256            },
2257        );
2258
2259        let p = Parser::new("NZST-12NZDT,J60,J300");
2260        assert_eq!(
2261            p.parse_posix_time_zone().unwrap(),
2262            PosixTimeZone {
2263                std_abbrev: "NZST".into(),
2264                std_offset: PosixOffset { second: 12 * 60 * 60 },
2265                dst: Some(PosixDst {
2266                    abbrev: "NZDT".into(),
2267                    offset: PosixOffset { second: 13 * 60 * 60 },
2268                    rule: PosixRule {
2269                        start: PosixDayTime {
2270                            date: PosixDay::JulianOne(60),
2271                            time: PosixTime { second: 2 * 60 * 60 },
2272                        },
2273                        end: PosixDayTime {
2274                            date: PosixDay::JulianOne(300),
2275                            time: PosixTime { second: 2 * 60 * 60 },
2276                        },
2277                    },
2278                }),
2279            },
2280        );
2281
2282        let p = Parser::new("NZST-12NZDT,J60,J300WAT");
2283        assert_eq!(
2284            p.parse_posix_time_zone().unwrap(),
2285            PosixTimeZone {
2286                std_abbrev: "NZST".into(),
2287                std_offset: PosixOffset { second: 12 * 60 * 60 },
2288                dst: Some(PosixDst {
2289                    abbrev: "NZDT".into(),
2290                    offset: PosixOffset { second: 13 * 60 * 60 },
2291                    rule: PosixRule {
2292                        start: PosixDayTime {
2293                            date: PosixDay::JulianOne(60),
2294                            time: PosixTime { second: 2 * 60 * 60 },
2295                        },
2296                        end: PosixDayTime {
2297                            date: PosixDay::JulianOne(300),
2298                            time: PosixTime { second: 2 * 60 * 60 },
2299                        },
2300                    },
2301                }),
2302            },
2303        );
2304    }
2305
2306    #[test]
2307    fn parse_posix_dst() {
2308        let p = Parser::new("NZDT,M9.5.0,M4.1.0/3");
2309        assert_eq!(
2310            p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
2311            PosixDst {
2312                abbrev: "NZDT".into(),
2313                offset: PosixOffset { second: 13 * 60 * 60 },
2314                rule: PosixRule {
2315                    start: PosixDayTime {
2316                        date: PosixDay::WeekdayOfMonth {
2317                            month: 9,
2318                            week: 5,
2319                            weekday: 0,
2320                        },
2321                        time: PosixTime { second: 2 * 60 * 60 },
2322                    },
2323                    end: PosixDayTime {
2324                        date: PosixDay::WeekdayOfMonth {
2325                            month: 4,
2326                            week: 1,
2327                            weekday: 0,
2328                        },
2329                        time: PosixTime { second: 3 * 60 * 60 },
2330                    },
2331                },
2332            },
2333        );
2334
2335        let p = Parser::new("NZDT,J60,J300");
2336        assert_eq!(
2337            p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
2338            PosixDst {
2339                abbrev: "NZDT".into(),
2340                offset: PosixOffset { second: 13 * 60 * 60 },
2341                rule: PosixRule {
2342                    start: PosixDayTime {
2343                        date: PosixDay::JulianOne(60),
2344                        time: PosixTime { second: 2 * 60 * 60 },
2345                    },
2346                    end: PosixDayTime {
2347                        date: PosixDay::JulianOne(300),
2348                        time: PosixTime { second: 2 * 60 * 60 },
2349                    },
2350                },
2351            },
2352        );
2353
2354        let p = Parser::new("NZDT-7,J60,J300");
2355        assert_eq!(
2356            p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
2357            PosixDst {
2358                abbrev: "NZDT".into(),
2359                offset: PosixOffset { second: 7 * 60 * 60 },
2360                rule: PosixRule {
2361                    start: PosixDayTime {
2362                        date: PosixDay::JulianOne(60),
2363                        time: PosixTime { second: 2 * 60 * 60 },
2364                    },
2365                    end: PosixDayTime {
2366                        date: PosixDay::JulianOne(300),
2367                        time: PosixTime { second: 2 * 60 * 60 },
2368                    },
2369                },
2370            },
2371        );
2372
2373        let p = Parser::new("NZDT+7,J60,J300");
2374        assert_eq!(
2375            p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
2376            PosixDst {
2377                abbrev: "NZDT".into(),
2378                offset: PosixOffset { second: -7 * 60 * 60 },
2379                rule: PosixRule {
2380                    start: PosixDayTime {
2381                        date: PosixDay::JulianOne(60),
2382                        time: PosixTime { second: 2 * 60 * 60 },
2383                    },
2384                    end: PosixDayTime {
2385                        date: PosixDay::JulianOne(300),
2386                        time: PosixTime { second: 2 * 60 * 60 },
2387                    },
2388                },
2389            },
2390        );
2391
2392        let p = Parser::new("NZDT7,J60,J300");
2393        assert_eq!(
2394            p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
2395            PosixDst {
2396                abbrev: "NZDT".into(),
2397                offset: PosixOffset { second: -7 * 60 * 60 },
2398                rule: PosixRule {
2399                    start: PosixDayTime {
2400                        date: PosixDay::JulianOne(60),
2401                        time: PosixTime { second: 2 * 60 * 60 },
2402                    },
2403                    end: PosixDayTime {
2404                        date: PosixDay::JulianOne(300),
2405                        time: PosixTime { second: 2 * 60 * 60 },
2406                    },
2407                },
2408            },
2409        );
2410
2411        let p = Parser::new("NZDT7,");
2412        assert!(p
2413            .parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 })
2414            .is_err());
2415
2416        let p = Parser::new("NZDT7!");
2417        assert!(p
2418            .parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 })
2419            .is_err());
2420    }
2421
2422    #[test]
2423    fn parse_abbreviation() {
2424        let p = Parser::new("ABC");
2425        assert_eq!(p.parse_abbreviation().unwrap(), "ABC");
2426
2427        let p = Parser::new("<ABC>");
2428        assert_eq!(p.parse_abbreviation().unwrap(), "ABC");
2429
2430        let p = Parser::new("<+09>");
2431        assert_eq!(p.parse_abbreviation().unwrap(), "+09");
2432
2433        let p = Parser::new("+09");
2434        assert!(p.parse_abbreviation().is_err());
2435    }
2436
2437    #[test]
2438    fn parse_unquoted_abbreviation() {
2439        let p = Parser::new("ABC");
2440        assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC");
2441
2442        let p = Parser::new("ABCXYZ");
2443        assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABCXYZ");
2444
2445        let p = Parser::new("ABC123");
2446        assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC");
2447
2448        let tz = "a".repeat(30);
2449        let p = Parser::new(&tz);
2450        assert_eq!(p.parse_unquoted_abbreviation().unwrap(), &*tz);
2451
2452        let p = Parser::new("a");
2453        assert!(p.parse_unquoted_abbreviation().is_err());
2454
2455        let p = Parser::new("ab");
2456        assert!(p.parse_unquoted_abbreviation().is_err());
2457
2458        let p = Parser::new("ab1");
2459        assert!(p.parse_unquoted_abbreviation().is_err());
2460
2461        let tz = "a".repeat(31);
2462        let p = Parser::new(&tz);
2463        assert!(p.parse_unquoted_abbreviation().is_err());
2464
2465        let p = Parser::new(b"ab\xFFcd");
2466        assert!(p.parse_unquoted_abbreviation().is_err());
2467    }
2468
2469    #[test]
2470    fn parse_quoted_abbreviation() {
2471        // The inputs look a little funny here, but that's because
2472        // 'parse_quoted_abbreviation' starts after the opening quote
2473        // has been parsed.
2474
2475        let p = Parser::new("ABC>");
2476        assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC");
2477
2478        let p = Parser::new("ABCXYZ>");
2479        assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABCXYZ");
2480
2481        let p = Parser::new("ABC>123");
2482        assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC");
2483
2484        let p = Parser::new("ABC123>");
2485        assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC123");
2486
2487        let p = Parser::new("ab1>");
2488        assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ab1");
2489
2490        let p = Parser::new("+09>");
2491        assert_eq!(p.parse_quoted_abbreviation().unwrap(), "+09");
2492
2493        let p = Parser::new("-09>");
2494        assert_eq!(p.parse_quoted_abbreviation().unwrap(), "-09");
2495
2496        let tz = alloc::format!("{}>", "a".repeat(30));
2497        let p = Parser::new(&tz);
2498        assert_eq!(
2499            p.parse_quoted_abbreviation().unwrap(),
2500            tz.trim_end_matches(">")
2501        );
2502
2503        let p = Parser::new("a>");
2504        assert!(p.parse_quoted_abbreviation().is_err());
2505
2506        let p = Parser::new("ab>");
2507        assert!(p.parse_quoted_abbreviation().is_err());
2508
2509        let tz = alloc::format!("{}>", "a".repeat(31));
2510        let p = Parser::new(&tz);
2511        assert!(p.parse_quoted_abbreviation().is_err());
2512
2513        let p = Parser::new(b"ab\xFFcd>");
2514        assert!(p.parse_quoted_abbreviation().is_err());
2515
2516        let p = Parser::new("ABC");
2517        assert!(p.parse_quoted_abbreviation().is_err());
2518
2519        let p = Parser::new("ABC!>");
2520        assert!(p.parse_quoted_abbreviation().is_err());
2521    }
2522
2523    #[test]
2524    fn parse_posix_offset() {
2525        let p = Parser::new("5");
2526        assert_eq!(p.parse_posix_offset().unwrap().second, -5 * 60 * 60);
2527
2528        let p = Parser::new("+5");
2529        assert_eq!(p.parse_posix_offset().unwrap().second, -5 * 60 * 60);
2530
2531        let p = Parser::new("-5");
2532        assert_eq!(p.parse_posix_offset().unwrap().second, 5 * 60 * 60);
2533
2534        let p = Parser::new("-12:34:56");
2535        assert_eq!(
2536            p.parse_posix_offset().unwrap().second,
2537            12 * 60 * 60 + 34 * 60 + 56,
2538        );
2539
2540        let p = Parser::new("a");
2541        assert!(p.parse_posix_offset().is_err());
2542
2543        let p = Parser::new("-");
2544        assert!(p.parse_posix_offset().is_err());
2545
2546        let p = Parser::new("+");
2547        assert!(p.parse_posix_offset().is_err());
2548
2549        let p = Parser::new("-a");
2550        assert!(p.parse_posix_offset().is_err());
2551
2552        let p = Parser::new("+a");
2553        assert!(p.parse_posix_offset().is_err());
2554
2555        let p = Parser::new("-25");
2556        assert!(p.parse_posix_offset().is_err());
2557
2558        let p = Parser::new("+25");
2559        assert!(p.parse_posix_offset().is_err());
2560
2561        // This checks that we don't accidentally permit IANA rules for
2562        // offset parsing. Namely, the IANA tzfile v3+ extension only applies
2563        // to transition times. But since POSIX says that the "time" for the
2564        // offset and transition is the same format, it would be an easy
2565        // implementation mistake to implement the more flexible rule for
2566        // IANA and have it accidentally also apply to the offset. So we check
2567        // that it doesn't here.
2568        let p = Parser { ianav3plus: true, ..Parser::new("25") };
2569        assert!(p.parse_posix_offset().is_err());
2570        let p = Parser { ianav3plus: true, ..Parser::new("+25") };
2571        assert!(p.parse_posix_offset().is_err());
2572        let p = Parser { ianav3plus: true, ..Parser::new("-25") };
2573        assert!(p.parse_posix_offset().is_err());
2574    }
2575
2576    #[test]
2577    fn parse_rule() {
2578        let p = Parser::new("M9.5.0,M4.1.0/3");
2579        assert_eq!(
2580            p.parse_rule().unwrap(),
2581            PosixRule {
2582                start: PosixDayTime {
2583                    date: PosixDay::WeekdayOfMonth {
2584                        month: 9,
2585                        week: 5,
2586                        weekday: 0,
2587                    },
2588                    time: PosixTime { second: 2 * 60 * 60 },
2589                },
2590                end: PosixDayTime {
2591                    date: PosixDay::WeekdayOfMonth {
2592                        month: 4,
2593                        week: 1,
2594                        weekday: 0,
2595                    },
2596                    time: PosixTime { second: 3 * 60 * 60 },
2597                },
2598            },
2599        );
2600
2601        let p = Parser::new("M9.5.0");
2602        assert!(p.parse_rule().is_err());
2603
2604        let p = Parser::new(",M9.5.0,M4.1.0/3");
2605        assert!(p.parse_rule().is_err());
2606
2607        let p = Parser::new("M9.5.0/");
2608        assert!(p.parse_rule().is_err());
2609
2610        let p = Parser::new("M9.5.0,M4.1.0/");
2611        assert!(p.parse_rule().is_err());
2612    }
2613
2614    #[test]
2615    fn parse_posix_datetime() {
2616        let p = Parser::new("J1");
2617        assert_eq!(
2618            p.parse_posix_datetime().unwrap(),
2619            PosixDayTime {
2620                date: PosixDay::JulianOne(1),
2621                time: PosixTime { second: 2 * 60 * 60 }
2622            },
2623        );
2624
2625        let p = Parser::new("J1/3");
2626        assert_eq!(
2627            p.parse_posix_datetime().unwrap(),
2628            PosixDayTime {
2629                date: PosixDay::JulianOne(1),
2630                time: PosixTime { second: 3 * 60 * 60 }
2631            },
2632        );
2633
2634        let p = Parser::new("M4.1.0/3");
2635        assert_eq!(
2636            p.parse_posix_datetime().unwrap(),
2637            PosixDayTime {
2638                date: PosixDay::WeekdayOfMonth {
2639                    month: 4,
2640                    week: 1,
2641                    weekday: 0,
2642                },
2643                time: PosixTime { second: 3 * 60 * 60 },
2644            },
2645        );
2646
2647        let p = Parser::new("1/3:45:05");
2648        assert_eq!(
2649            p.parse_posix_datetime().unwrap(),
2650            PosixDayTime {
2651                date: PosixDay::JulianZero(1),
2652                time: PosixTime { second: 3 * 60 * 60 + 45 * 60 + 5 },
2653            },
2654        );
2655
2656        let p = Parser::new("a");
2657        assert!(p.parse_posix_datetime().is_err());
2658
2659        let p = Parser::new("J1/");
2660        assert!(p.parse_posix_datetime().is_err());
2661
2662        let p = Parser::new("1/");
2663        assert!(p.parse_posix_datetime().is_err());
2664
2665        let p = Parser::new("M4.1.0/");
2666        assert!(p.parse_posix_datetime().is_err());
2667    }
2668
2669    #[test]
2670    fn parse_posix_date() {
2671        let p = Parser::new("J1");
2672        assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianOne(1));
2673        let p = Parser::new("J365");
2674        assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianOne(365));
2675
2676        let p = Parser::new("0");
2677        assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(0));
2678        let p = Parser::new("1");
2679        assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(1));
2680        let p = Parser::new("365");
2681        assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(365));
2682
2683        let p = Parser::new("M9.5.0");
2684        assert_eq!(
2685            p.parse_posix_date().unwrap(),
2686            PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 0 },
2687        );
2688        let p = Parser::new("M9.5.6");
2689        assert_eq!(
2690            p.parse_posix_date().unwrap(),
2691            PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 6 },
2692        );
2693        let p = Parser::new("M09.5.6");
2694        assert_eq!(
2695            p.parse_posix_date().unwrap(),
2696            PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 6 },
2697        );
2698        let p = Parser::new("M12.1.1");
2699        assert_eq!(
2700            p.parse_posix_date().unwrap(),
2701            PosixDay::WeekdayOfMonth { month: 12, week: 1, weekday: 1 },
2702        );
2703
2704        let p = Parser::new("a");
2705        assert!(p.parse_posix_date().is_err());
2706
2707        let p = Parser::new("j");
2708        assert!(p.parse_posix_date().is_err());
2709
2710        let p = Parser::new("m");
2711        assert!(p.parse_posix_date().is_err());
2712
2713        let p = Parser::new("n");
2714        assert!(p.parse_posix_date().is_err());
2715
2716        let p = Parser::new("J366");
2717        assert!(p.parse_posix_date().is_err());
2718
2719        let p = Parser::new("366");
2720        assert!(p.parse_posix_date().is_err());
2721    }
2722
2723    #[test]
2724    fn parse_posix_julian_day_no_leap() {
2725        let p = Parser::new("1");
2726        assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1);
2727
2728        let p = Parser::new("001");
2729        assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1);
2730
2731        let p = Parser::new("365");
2732        assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365);
2733
2734        let p = Parser::new("3655");
2735        assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365);
2736
2737        let p = Parser::new("0");
2738        assert!(p.parse_posix_julian_day_no_leap().is_err());
2739
2740        let p = Parser::new("366");
2741        assert!(p.parse_posix_julian_day_no_leap().is_err());
2742    }
2743
2744    #[test]
2745    fn parse_posix_julian_day_with_leap() {
2746        let p = Parser::new("0");
2747        assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 0);
2748
2749        let p = Parser::new("1");
2750        assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1);
2751
2752        let p = Parser::new("001");
2753        assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1);
2754
2755        let p = Parser::new("365");
2756        assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365);
2757
2758        let p = Parser::new("3655");
2759        assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365);
2760
2761        let p = Parser::new("366");
2762        assert!(p.parse_posix_julian_day_with_leap().is_err());
2763    }
2764
2765    #[test]
2766    fn parse_weekday_of_month() {
2767        let p = Parser::new("9.5.0");
2768        assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 5, 0));
2769
2770        let p = Parser::new("9.1.6");
2771        assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 1, 6));
2772
2773        let p = Parser::new("09.1.6");
2774        assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 1, 6));
2775
2776        let p = Parser::new("9");
2777        assert!(p.parse_weekday_of_month().is_err());
2778
2779        let p = Parser::new("9.");
2780        assert!(p.parse_weekday_of_month().is_err());
2781
2782        let p = Parser::new("9.5");
2783        assert!(p.parse_weekday_of_month().is_err());
2784
2785        let p = Parser::new("9.5.");
2786        assert!(p.parse_weekday_of_month().is_err());
2787
2788        let p = Parser::new("0.5.0");
2789        assert!(p.parse_weekday_of_month().is_err());
2790
2791        let p = Parser::new("13.5.0");
2792        assert!(p.parse_weekday_of_month().is_err());
2793
2794        let p = Parser::new("9.0.0");
2795        assert!(p.parse_weekday_of_month().is_err());
2796
2797        let p = Parser::new("9.6.0");
2798        assert!(p.parse_weekday_of_month().is_err());
2799
2800        let p = Parser::new("9.5.7");
2801        assert!(p.parse_weekday_of_month().is_err());
2802    }
2803
2804    #[test]
2805    fn parse_posix_time() {
2806        let p = Parser::new("5");
2807        assert_eq!(p.parse_posix_time().unwrap().second, 5 * 60 * 60);
2808
2809        let p = Parser::new("22");
2810        assert_eq!(p.parse_posix_time().unwrap().second, 22 * 60 * 60);
2811
2812        let p = Parser::new("02");
2813        assert_eq!(p.parse_posix_time().unwrap().second, 2 * 60 * 60);
2814
2815        let p = Parser::new("5:45");
2816        assert_eq!(
2817            p.parse_posix_time().unwrap().second,
2818            5 * 60 * 60 + 45 * 60
2819        );
2820
2821        let p = Parser::new("5:45:12");
2822        assert_eq!(
2823            p.parse_posix_time().unwrap().second,
2824            5 * 60 * 60 + 45 * 60 + 12
2825        );
2826
2827        let p = Parser::new("5:45:129");
2828        assert_eq!(
2829            p.parse_posix_time().unwrap().second,
2830            5 * 60 * 60 + 45 * 60 + 12
2831        );
2832
2833        let p = Parser::new("5:45:12:");
2834        assert_eq!(
2835            p.parse_posix_time().unwrap().second,
2836            5 * 60 * 60 + 45 * 60 + 12
2837        );
2838
2839        let p = Parser { ianav3plus: true, ..Parser::new("+5:45:12") };
2840        assert_eq!(
2841            p.parse_posix_time().unwrap().second,
2842            5 * 60 * 60 + 45 * 60 + 12
2843        );
2844
2845        let p = Parser { ianav3plus: true, ..Parser::new("-5:45:12") };
2846        assert_eq!(
2847            p.parse_posix_time().unwrap().second,
2848            -(5 * 60 * 60 + 45 * 60 + 12)
2849        );
2850
2851        let p = Parser { ianav3plus: true, ..Parser::new("-167:45:12") };
2852        assert_eq!(
2853            p.parse_posix_time().unwrap().second,
2854            -(167 * 60 * 60 + 45 * 60 + 12),
2855        );
2856
2857        let p = Parser::new("25");
2858        assert!(p.parse_posix_time().is_err());
2859
2860        let p = Parser::new("12:2");
2861        assert!(p.parse_posix_time().is_err());
2862
2863        let p = Parser::new("12:");
2864        assert!(p.parse_posix_time().is_err());
2865
2866        let p = Parser::new("12:23:5");
2867        assert!(p.parse_posix_time().is_err());
2868
2869        let p = Parser::new("12:23:");
2870        assert!(p.parse_posix_time().is_err());
2871
2872        let p = Parser { ianav3plus: true, ..Parser::new("168") };
2873        assert!(p.parse_posix_time().is_err());
2874
2875        let p = Parser { ianav3plus: true, ..Parser::new("-168") };
2876        assert!(p.parse_posix_time().is_err());
2877
2878        let p = Parser { ianav3plus: true, ..Parser::new("+168") };
2879        assert!(p.parse_posix_time().is_err());
2880    }
2881
2882    #[test]
2883    fn parse_month() {
2884        let p = Parser::new("1");
2885        assert_eq!(p.parse_month().unwrap(), 1);
2886
2887        // Should this be allowed? POSIX spec is unclear.
2888        // We allow it because our parse does stop at 2
2889        // digits, so this seems harmless. Namely, '001'
2890        // results in an error.
2891        let p = Parser::new("01");
2892        assert_eq!(p.parse_month().unwrap(), 1);
2893
2894        let p = Parser::new("12");
2895        assert_eq!(p.parse_month().unwrap(), 12);
2896
2897        let p = Parser::new("0");
2898        assert!(p.parse_month().is_err());
2899
2900        let p = Parser::new("00");
2901        assert!(p.parse_month().is_err());
2902
2903        let p = Parser::new("001");
2904        assert!(p.parse_month().is_err());
2905
2906        let p = Parser::new("13");
2907        assert!(p.parse_month().is_err());
2908    }
2909
2910    #[test]
2911    fn parse_week() {
2912        let p = Parser::new("1");
2913        assert_eq!(p.parse_week().unwrap(), 1);
2914
2915        let p = Parser::new("5");
2916        assert_eq!(p.parse_week().unwrap(), 5);
2917
2918        let p = Parser::new("55");
2919        assert_eq!(p.parse_week().unwrap(), 5);
2920
2921        let p = Parser::new("0");
2922        assert!(p.parse_week().is_err());
2923
2924        let p = Parser::new("6");
2925        assert!(p.parse_week().is_err());
2926
2927        let p = Parser::new("00");
2928        assert!(p.parse_week().is_err());
2929
2930        let p = Parser::new("01");
2931        assert!(p.parse_week().is_err());
2932
2933        let p = Parser::new("05");
2934        assert!(p.parse_week().is_err());
2935    }
2936
2937    #[test]
2938    fn parse_weekday() {
2939        let p = Parser::new("0");
2940        assert_eq!(p.parse_weekday().unwrap(), 0);
2941
2942        let p = Parser::new("1");
2943        assert_eq!(p.parse_weekday().unwrap(), 1);
2944
2945        let p = Parser::new("6");
2946        assert_eq!(p.parse_weekday().unwrap(), 6);
2947
2948        let p = Parser::new("00");
2949        assert_eq!(p.parse_weekday().unwrap(), 0);
2950
2951        let p = Parser::new("06");
2952        assert_eq!(p.parse_weekday().unwrap(), 0);
2953
2954        let p = Parser::new("60");
2955        assert_eq!(p.parse_weekday().unwrap(), 6);
2956
2957        let p = Parser::new("7");
2958        assert!(p.parse_weekday().is_err());
2959    }
2960
2961    #[test]
2962    fn parse_hour_posix() {
2963        let p = Parser::new("5");
2964        assert_eq!(p.parse_hour_posix().unwrap(), 5);
2965
2966        let p = Parser::new("0");
2967        assert_eq!(p.parse_hour_posix().unwrap(), 0);
2968
2969        let p = Parser::new("00");
2970        assert_eq!(p.parse_hour_posix().unwrap(), 0);
2971
2972        let p = Parser::new("24");
2973        assert_eq!(p.parse_hour_posix().unwrap(), 24);
2974
2975        let p = Parser::new("100");
2976        assert_eq!(p.parse_hour_posix().unwrap(), 10);
2977
2978        let p = Parser::new("25");
2979        assert!(p.parse_hour_posix().is_err());
2980
2981        let p = Parser::new("99");
2982        assert!(p.parse_hour_posix().is_err());
2983    }
2984
2985    #[test]
2986    fn parse_hour_ianav3plus() {
2987        let new = |input| Parser { ianav3plus: true, ..Parser::new(input) };
2988
2989        let p = new("5");
2990        assert_eq!(p.parse_hour_ianav3plus().unwrap(), 5);
2991
2992        let p = new("0");
2993        assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
2994
2995        let p = new("00");
2996        assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
2997
2998        let p = new("000");
2999        assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
3000
3001        let p = new("24");
3002        assert_eq!(p.parse_hour_ianav3plus().unwrap(), 24);
3003
3004        let p = new("100");
3005        assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100);
3006
3007        let p = new("1000");
3008        assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100);
3009
3010        let p = new("167");
3011        assert_eq!(p.parse_hour_ianav3plus().unwrap(), 167);
3012
3013        let p = new("168");
3014        assert!(p.parse_hour_ianav3plus().is_err());
3015
3016        let p = new("999");
3017        assert!(p.parse_hour_ianav3plus().is_err());
3018    }
3019
3020    #[test]
3021    fn parse_minute() {
3022        let p = Parser::new("00");
3023        assert_eq!(p.parse_minute().unwrap(), 0);
3024
3025        let p = Parser::new("24");
3026        assert_eq!(p.parse_minute().unwrap(), 24);
3027
3028        let p = Parser::new("59");
3029        assert_eq!(p.parse_minute().unwrap(), 59);
3030
3031        let p = Parser::new("599");
3032        assert_eq!(p.parse_minute().unwrap(), 59);
3033
3034        let p = Parser::new("0");
3035        assert!(p.parse_minute().is_err());
3036
3037        let p = Parser::new("1");
3038        assert!(p.parse_minute().is_err());
3039
3040        let p = Parser::new("9");
3041        assert!(p.parse_minute().is_err());
3042
3043        let p = Parser::new("60");
3044        assert!(p.parse_minute().is_err());
3045    }
3046
3047    #[test]
3048    fn parse_second() {
3049        let p = Parser::new("00");
3050        assert_eq!(p.parse_second().unwrap(), 0);
3051
3052        let p = Parser::new("24");
3053        assert_eq!(p.parse_second().unwrap(), 24);
3054
3055        let p = Parser::new("59");
3056        assert_eq!(p.parse_second().unwrap(), 59);
3057
3058        let p = Parser::new("599");
3059        assert_eq!(p.parse_second().unwrap(), 59);
3060
3061        let p = Parser::new("0");
3062        assert!(p.parse_second().is_err());
3063
3064        let p = Parser::new("1");
3065        assert!(p.parse_second().is_err());
3066
3067        let p = Parser::new("9");
3068        assert!(p.parse_second().is_err());
3069
3070        let p = Parser::new("60");
3071        assert!(p.parse_second().is_err());
3072    }
3073
3074    #[test]
3075    fn parse_number_with_exactly_n_digits() {
3076        let p = Parser::new("1");
3077        assert_eq!(p.parse_number_with_exactly_n_digits(1).unwrap(), 1);
3078
3079        let p = Parser::new("12");
3080        assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12);
3081
3082        let p = Parser::new("123");
3083        assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12);
3084
3085        let p = Parser::new("");
3086        assert!(p.parse_number_with_exactly_n_digits(1).is_err());
3087
3088        let p = Parser::new("1");
3089        assert!(p.parse_number_with_exactly_n_digits(2).is_err());
3090
3091        let p = Parser::new("12");
3092        assert!(p.parse_number_with_exactly_n_digits(3).is_err());
3093    }
3094
3095    #[test]
3096    fn parse_number_with_upto_n_digits() {
3097        let p = Parser::new("1");
3098        assert_eq!(p.parse_number_with_upto_n_digits(1).unwrap(), 1);
3099
3100        let p = Parser::new("1");
3101        assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 1);
3102
3103        let p = Parser::new("12");
3104        assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12);
3105
3106        let p = Parser::new("12");
3107        assert_eq!(p.parse_number_with_upto_n_digits(3).unwrap(), 12);
3108
3109        let p = Parser::new("123");
3110        assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12);
3111
3112        let p = Parser::new("");
3113        assert!(p.parse_number_with_upto_n_digits(1).is_err());
3114
3115        let p = Parser::new("a");
3116        assert!(p.parse_number_with_upto_n_digits(1).is_err());
3117    }
3118
3119    #[test]
3120    fn to_dst_civil_datetime_utc_range() {
3121        let tz = posix_time_zone("WART4WARST,J1/-3,J365/20");
3122        let dst_info = DstInfo {
3123            // We test this in other places. It's too annoying to write this
3124            // out here, and I didn't adopt snapshot testing until I had
3125            // written out these tests by hand. ¯\_(ツ)_/¯
3126            dst: tz.dst.as_ref().unwrap(),
3127            start: date(2024, 1, 1).at(1, 0, 0, 0),
3128            end: date(2024, 12, 31).at(23, 0, 0, 0),
3129        };
3130        assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
3131
3132        let tz = posix_time_zone("WART4WARST,J1/-4,J365/21");
3133        let dst_info = DstInfo {
3134            dst: tz.dst.as_ref().unwrap(),
3135            start: date(2024, 1, 1).at(0, 0, 0, 0),
3136            end: date(2024, 12, 31).at(23, 59, 59, 999_999_999),
3137        };
3138        assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
3139
3140        let tz = posix_time_zone("EST5EDT,M3.2.0,M11.1.0");
3141        let dst_info = DstInfo {
3142            dst: tz.dst.as_ref().unwrap(),
3143            start: date(2024, 3, 10).at(7, 0, 0, 0),
3144            end: date(2024, 11, 3).at(6, 0, 0, 0),
3145        };
3146        assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
3147    }
3148
3149    // See: https://github.com/BurntSushi/jiff/issues/386
3150    #[test]
3151    fn regression_permanent_dst() {
3152        let tz = posix_time_zone("XXX-2<+01>-1,0/0,J365/23");
3153        let dst_info = DstInfo {
3154            dst: tz.dst.as_ref().unwrap(),
3155            start: date(2087, 1, 1).at(0, 0, 0, 0),
3156            end: date(2087, 12, 31).at(23, 59, 59, 999_999_999),
3157        };
3158        assert_eq!(tz.dst_info_utc(2087), Some(dst_info));
3159    }
3160
3161    #[test]
3162    fn reasonable() {
3163        assert!(PosixTimeZone::parse(b"EST5").is_ok());
3164        assert!(PosixTimeZone::parse(b"EST5EDT").is_err());
3165        assert!(PosixTimeZone::parse(b"EST5EDT,J1,J365").is_ok());
3166
3167        let tz = posix_time_zone("EST24EDT,J1,J365");
3168        assert_eq!(
3169            tz,
3170            PosixTimeZone {
3171                std_abbrev: "EST".into(),
3172                std_offset: PosixOffset { second: -24 * 60 * 60 },
3173                dst: Some(PosixDst {
3174                    abbrev: "EDT".into(),
3175                    offset: PosixOffset { second: -23 * 60 * 60 },
3176                    rule: PosixRule {
3177                        start: PosixDayTime {
3178                            date: PosixDay::JulianOne(1),
3179                            time: PosixTime::DEFAULT,
3180                        },
3181                        end: PosixDayTime {
3182                            date: PosixDay::JulianOne(365),
3183                            time: PosixTime::DEFAULT,
3184                        },
3185                    },
3186                }),
3187            },
3188        );
3189
3190        let tz = posix_time_zone("EST-24EDT,J1,J365");
3191        assert_eq!(
3192            tz,
3193            PosixTimeZone {
3194                std_abbrev: "EST".into(),
3195                std_offset: PosixOffset { second: 24 * 60 * 60 },
3196                dst: Some(PosixDst {
3197                    abbrev: "EDT".into(),
3198                    offset: PosixOffset { second: 25 * 60 * 60 },
3199                    rule: PosixRule {
3200                        start: PosixDayTime {
3201                            date: PosixDay::JulianOne(1),
3202                            time: PosixTime::DEFAULT,
3203                        },
3204                        end: PosixDayTime {
3205                            date: PosixDay::JulianOne(365),
3206                            time: PosixTime::DEFAULT,
3207                        },
3208                    },
3209                }),
3210            },
3211        );
3212    }
3213
3214    #[test]
3215    fn posix_date_time_spec_to_datetime() {
3216        // For this test, we just keep the offset to zero to simplify things
3217        // a bit. We get coverage for non-zero offsets in higher level tests.
3218        let to_datetime = |daytime: &PosixDayTime, year: i16| {
3219            daytime.to_datetime(year, IOffset::UTC)
3220        };
3221
3222        let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
3223        assert_eq!(
3224            to_datetime(&tz.rule().start, 2023),
3225            date(2023, 1, 1).at(2, 0, 0, 0),
3226        );
3227        assert_eq!(
3228            to_datetime(&tz.rule().end, 2023),
3229            date(2023, 12, 31).at(5, 12, 34, 0),
3230        );
3231
3232        let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2");
3233        assert_eq!(
3234            to_datetime(&tz.rule().start, 2024),
3235            date(2024, 3, 10).at(2, 0, 0, 0),
3236        );
3237        assert_eq!(
3238            to_datetime(&tz.rule().end, 2024),
3239            date(2024, 11, 3).at(2, 0, 0, 0),
3240        );
3241
3242        let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2");
3243        assert_eq!(
3244            to_datetime(&tz.rule().start, 2024),
3245            date(2024, 1, 1).at(2, 0, 0, 0),
3246        );
3247        assert_eq!(
3248            to_datetime(&tz.rule().end, 2024),
3249            date(2024, 12, 31).at(2, 0, 0, 0),
3250        );
3251
3252        let tz = posix_time_zone("EST5EDT,0/0,J365/25");
3253        assert_eq!(
3254            to_datetime(&tz.rule().start, 2024),
3255            date(2024, 1, 1).at(0, 0, 0, 0),
3256        );
3257        assert_eq!(
3258            to_datetime(&tz.rule().end, 2024),
3259            date(2024, 12, 31).at(23, 59, 59, 999_999_999),
3260        );
3261
3262        let tz = posix_time_zone("XXX3EDT4,0/0,J365/23");
3263        assert_eq!(
3264            to_datetime(&tz.rule().start, 2024),
3265            date(2024, 1, 1).at(0, 0, 0, 0),
3266        );
3267        assert_eq!(
3268            to_datetime(&tz.rule().end, 2024),
3269            date(2024, 12, 31).at(23, 0, 0, 0),
3270        );
3271
3272        let tz = posix_time_zone("XXX3EDT4,0/0,365");
3273        assert_eq!(
3274            to_datetime(&tz.rule().end, 2023),
3275            date(2023, 12, 31).at(23, 59, 59, 999_999_999),
3276        );
3277        assert_eq!(
3278            to_datetime(&tz.rule().end, 2024),
3279            date(2024, 12, 31).at(2, 0, 0, 0),
3280        );
3281
3282        let tz = posix_time_zone("XXX3EDT4,J1/-167:59:59,J365/167:59:59");
3283        assert_eq!(
3284            to_datetime(&tz.rule().start, 2024),
3285            date(2024, 1, 1).at(0, 0, 0, 0),
3286        );
3287        assert_eq!(
3288            to_datetime(&tz.rule().end, 2024),
3289            date(2024, 12, 31).at(23, 59, 59, 999_999_999),
3290        );
3291    }
3292
3293    #[test]
3294    fn posix_date_time_spec_time() {
3295        let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
3296        assert_eq!(tz.rule().start.time, PosixTime::DEFAULT);
3297        assert_eq!(
3298            tz.rule().end.time,
3299            PosixTime { second: 5 * 60 * 60 + 12 * 60 + 34 },
3300        );
3301    }
3302
3303    #[test]
3304    fn posix_date_spec_to_date() {
3305        let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2");
3306        let start = tz.rule().start.date.to_date(2023);
3307        assert_eq!(start, Some(date(2023, 3, 12)));
3308        let end = tz.rule().end.date.to_date(2023);
3309        assert_eq!(end, Some(date(2023, 11, 5)));
3310        let start = tz.rule().start.date.to_date(2024);
3311        assert_eq!(start, Some(date(2024, 3, 10)));
3312        let end = tz.rule().end.date.to_date(2024);
3313        assert_eq!(end, Some(date(2024, 11, 3)));
3314
3315        let tz = posix_time_zone("EST+5EDT,J60,J365");
3316        let start = tz.rule().start.date.to_date(2023);
3317        assert_eq!(start, Some(date(2023, 3, 1)));
3318        let end = tz.rule().end.date.to_date(2023);
3319        assert_eq!(end, Some(date(2023, 12, 31)));
3320        let start = tz.rule().start.date.to_date(2024);
3321        assert_eq!(start, Some(date(2024, 3, 1)));
3322        let end = tz.rule().end.date.to_date(2024);
3323        assert_eq!(end, Some(date(2024, 12, 31)));
3324
3325        let tz = posix_time_zone("EST+5EDT,59,365");
3326        let start = tz.rule().start.date.to_date(2023);
3327        assert_eq!(start, Some(date(2023, 3, 1)));
3328        let end = tz.rule().end.date.to_date(2023);
3329        assert_eq!(end, None);
3330        let start = tz.rule().start.date.to_date(2024);
3331        assert_eq!(start, Some(date(2024, 2, 29)));
3332        let end = tz.rule().end.date.to_date(2024);
3333        assert_eq!(end, Some(date(2024, 12, 31)));
3334
3335        let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2");
3336        let start = tz.rule().start.date.to_date(2024);
3337        assert_eq!(start, Some(date(2024, 1, 1)));
3338        let end = tz.rule().end.date.to_date(2024);
3339        assert_eq!(end, Some(date(2024, 12, 31)));
3340    }
3341
3342    #[test]
3343    fn posix_time_spec_to_civil_time() {
3344        let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
3345        assert_eq!(
3346            tz.dst.as_ref().unwrap().rule.start.time.second,
3347            2 * 60 * 60,
3348        );
3349        assert_eq!(
3350            tz.dst.as_ref().unwrap().rule.end.time.second,
3351            5 * 60 * 60 + 12 * 60 + 34,
3352        );
3353
3354        let tz = posix_time_zone("EST5EDT,J1/23:59:59,J365/24:00:00");
3355        assert_eq!(
3356            tz.dst.as_ref().unwrap().rule.start.time.second,
3357            23 * 60 * 60 + 59 * 60 + 59,
3358        );
3359        assert_eq!(
3360            tz.dst.as_ref().unwrap().rule.end.time.second,
3361            24 * 60 * 60,
3362        );
3363
3364        let tz = posix_time_zone("EST5EDT,J1/-1,J365/167:00:00");
3365        assert_eq!(
3366            tz.dst.as_ref().unwrap().rule.start.time.second,
3367            -1 * 60 * 60,
3368        );
3369        assert_eq!(
3370            tz.dst.as_ref().unwrap().rule.end.time.second,
3371            167 * 60 * 60,
3372        );
3373    }
3374
3375    #[test]
3376    fn parse_iana() {
3377        // Ref: https://github.com/chronotope/chrono/issues/1153
3378        let p = PosixTimeZone::parse(b"CRAZY5SHORT,M12.5.0/50,0/2").unwrap();
3379        assert_eq!(
3380            p,
3381            PosixTimeZone {
3382                std_abbrev: "CRAZY".into(),
3383                std_offset: PosixOffset { second: -5 * 60 * 60 },
3384                dst: Some(PosixDst {
3385                    abbrev: "SHORT".into(),
3386                    offset: PosixOffset { second: -4 * 60 * 60 },
3387                    rule: PosixRule {
3388                        start: PosixDayTime {
3389                            date: PosixDay::WeekdayOfMonth {
3390                                month: 12,
3391                                week: 5,
3392                                weekday: 0,
3393                            },
3394                            time: PosixTime { second: 50 * 60 * 60 },
3395                        },
3396                        end: PosixDayTime {
3397                            date: PosixDay::JulianZero(0),
3398                            time: PosixTime { second: 2 * 60 * 60 },
3399                        },
3400                    },
3401                }),
3402            },
3403        );
3404
3405        assert!(PosixTimeZone::parse(b"America/New_York").is_err());
3406        assert!(PosixTimeZone::parse(b":America/New_York").is_err());
3407    }
3408
3409    // See: https://github.com/BurntSushi/jiff/issues/407
3410    #[test]
3411    fn parse_empty_is_err() {
3412        assert!(PosixTimeZone::parse(b"").is_err());
3413    }
3414
3415    // See: https://github.com/BurntSushi/jiff/issues/407
3416    #[test]
3417    fn parse_weird_is_err() {
3418        let s =
3419            b"AAAAAAAAAAAAAAACAAAAAAAAAAAAQA8AACAAAAAAAAAAAAAAAAACAAAAAAAAAAA";
3420        assert!(PosixTimeZone::parse(s).is_err());
3421
3422        let s =
3423            b"<AAAAAAAAAAAAAAACAAAAAAAAAAAAQA>8<AACAAAAAAAAAAAAAAAAACAAAAAAAAAAA>";
3424        assert!(PosixTimeZone::parse(s).is_err());
3425
3426        let s = b"PPPPPPPPPPPPPPPPPPPPnoofPPPAAA6DaPPPPPPPPPPPPPPPPPPPPPnoofPPPPP,n";
3427        assert!(PosixTimeZone::parse(s).is_err());
3428
3429        let s = b"oooooooooovooooooooooooooooool9<ooooo2o-o-oooooookoorooooooooroo8";
3430        assert!(PosixTimeZone::parse(s).is_err());
3431    }
3432}