Coverage Report

Created: 2026-01-30 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/jiff-0.2.17/src/shared/posix.rs
Line
Count
Source
1
use core::fmt::Debug;
2
3
use 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
15
impl 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
0
    pub fn parse(
19
0
        bytes: &[u8],
20
0
    ) -> 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
0
        let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
27
0
        parser.parse()
28
0
    }
29
30
    // only-jiff-start
31
    /// Like parse, but parses a prefix of the input given and returns whatever
32
    /// is remaining.
33
0
    pub fn parse_prefix<'b>(
34
0
        bytes: &'b [u8],
35
0
    ) -> Result<(PosixTimeZone<Abbreviation>, &'b [u8]), PosixTimeZoneError>
36
    {
37
0
        let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
38
0
        parser.parse_prefix()
39
0
    }
40
    // only-jiff-end
41
}
42
43
impl<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
0
    pub(crate) fn to_offset(&self, timestamp: ITimestamp) -> IOffset {
52
0
        let std_offset = self.std_offset.to_ioffset();
53
0
        if self.dst.is_none() {
54
0
            return std_offset;
55
0
        }
56
57
0
        let dt = timestamp.to_datetime(IOffset::UTC);
58
0
        self.dst_info_utc(dt.date.year)
59
0
            .filter(|dst_info| dst_info.in_dst(dt))
Unexecuted instantiation: <jiff::shared::PosixTimeZone<jiff::shared::util::array_str::ArrayStr<30>>>::to_offset::{closure#0}
Unexecuted instantiation: <jiff::shared::PosixTimeZone<&str>>::to_offset::{closure#0}
60
0
            .map(|dst_info| dst_info.offset().to_ioffset())
Unexecuted instantiation: <jiff::shared::PosixTimeZone<jiff::shared::util::array_str::ArrayStr<30>>>::to_offset::{closure#1}
Unexecuted instantiation: <jiff::shared::PosixTimeZone<&str>>::to_offset::{closure#1}
61
0
            .unwrap_or_else(|| std_offset)
62
0
    }
Unexecuted instantiation: <jiff::shared::PosixTimeZone<jiff::shared::util::array_str::ArrayStr<30>>>::to_offset
Unexecuted instantiation: <jiff::shared::PosixTimeZone<&str>>::to_offset
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
0
    pub(crate) fn to_offset_info(
71
0
        &self,
72
0
        timestamp: ITimestamp,
73
0
    ) -> (IOffset, &'_ str, bool) {
74
0
        let std_offset = self.std_offset.to_ioffset();
75
0
        if self.dst.is_none() {
76
0
            return (std_offset, self.std_abbrev.as_ref(), false);
77
0
        }
78
79
0
        let dt = timestamp.to_datetime(IOffset::UTC);
80
0
        self.dst_info_utc(dt.date.year)
81
0
            .filter(|dst_info| dst_info.in_dst(dt))
Unexecuted instantiation: <jiff::shared::PosixTimeZone<jiff::shared::util::array_str::ArrayStr<30>>>::to_offset_info::{closure#0}
Unexecuted instantiation: <jiff::shared::PosixTimeZone<&str>>::to_offset_info::{closure#0}
82
0
            .map(|dst_info| {
83
0
                (
84
0
                    dst_info.offset().to_ioffset(),
85
0
                    dst_info.dst.abbrev.as_ref(),
86
0
                    true,
87
0
                )
88
0
            })
Unexecuted instantiation: <jiff::shared::PosixTimeZone<jiff::shared::util::array_str::ArrayStr<30>>>::to_offset_info::{closure#1}
Unexecuted instantiation: <jiff::shared::PosixTimeZone<&str>>::to_offset_info::{closure#1}
89
0
            .unwrap_or_else(|| (std_offset, self.std_abbrev.as_ref(), false))
Unexecuted instantiation: <jiff::shared::PosixTimeZone<jiff::shared::util::array_str::ArrayStr<30>>>::to_offset_info::{closure#2}
Unexecuted instantiation: <jiff::shared::PosixTimeZone<&str>>::to_offset_info::{closure#2}
90
0
    }
Unexecuted instantiation: <jiff::shared::PosixTimeZone<jiff::shared::util::array_str::ArrayStr<30>>>::to_offset_info
Unexecuted instantiation: <jiff::shared::PosixTimeZone<&str>>::to_offset_info
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
0
    pub(crate) fn to_ambiguous_kind(&self, dt: IDateTime) -> IAmbiguousOffset {
104
0
        let year = dt.date.year;
105
0
        let std_offset = self.std_offset.to_ioffset();
106
0
        let Some(dst_info) = self.dst_info_wall(year) else {
107
0
            return IAmbiguousOffset::Unambiguous { offset: std_offset };
108
        };
109
0
        let dst_offset = dst_info.offset().to_ioffset();
110
0
        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
0
        if diff == 0 {
126
0
            debug_assert_eq!(std_offset, dst_offset);
127
0
            IAmbiguousOffset::Unambiguous { offset: std_offset }
128
0
        } 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
0
            if dst_info.in_dst(dt) {
133
0
                IAmbiguousOffset::Unambiguous { offset: dst_offset }
134
            } else {
135
0
                let fold_start = dst_info.start.saturating_add_seconds(diff);
136
0
                let gap_end =
137
0
                    dst_info.end.saturating_add_seconds(diff.saturating_neg());
138
0
                if fold_start <= dt && dt < dst_info.start {
139
0
                    IAmbiguousOffset::Fold {
140
0
                        before: std_offset,
141
0
                        after: dst_offset,
142
0
                    }
143
0
                } else if dst_info.end <= dt && dt < gap_end {
144
0
                    IAmbiguousOffset::Gap {
145
0
                        before: dst_offset,
146
0
                        after: std_offset,
147
0
                    }
148
                } else {
149
0
                    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
0
            if !dst_info.in_dst(dt) {
157
0
                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
0
                let gap_end = dst_info.start.saturating_add_seconds(diff);
164
0
                let fold_start =
165
0
                    dst_info.end.saturating_add_seconds(diff.saturating_neg());
166
0
                if dst_info.start <= dt && dt < gap_end {
167
0
                    IAmbiguousOffset::Gap {
168
0
                        before: std_offset,
169
0
                        after: dst_offset,
170
0
                    }
171
0
                } else if fold_start <= dt && dt < dst_info.end {
172
0
                    IAmbiguousOffset::Fold {
173
0
                        before: dst_offset,
174
0
                        after: std_offset,
175
0
                    }
176
                } else {
177
0
                    IAmbiguousOffset::Unambiguous { offset: dst_offset }
178
                }
179
            }
180
        }
181
0
    }
Unexecuted instantiation: <jiff::shared::PosixTimeZone<jiff::shared::util::array_str::ArrayStr<30>>>::to_ambiguous_kind
Unexecuted instantiation: <jiff::shared::PosixTimeZone<&str>>::to_ambiguous_kind
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
0
    pub(crate) fn previous_transition(
186
0
        &self,
187
0
        timestamp: ITimestamp,
188
0
    ) -> Option<(ITimestamp, IOffset, &'_ str, bool)> {
189
0
        let dt = timestamp.to_datetime(IOffset::UTC);
190
0
        let dst_info = self.dst_info_utc(dt.date.year)?;
191
0
        let (earlier, later) = dst_info.ordered();
192
0
        let (prev, dst_info) = if dt > later {
193
0
            (later, dst_info)
194
0
        } else if dt > earlier {
195
0
            (earlier, dst_info)
196
        } else {
197
0
            let prev_year = dt.date.prev_year().ok()?;
198
0
            let dst_info = self.dst_info_utc(prev_year)?;
199
0
            let (_, later) = dst_info.ordered();
200
0
            (later, dst_info)
201
        };
202
203
0
        let timestamp = prev.to_timestamp_checked(IOffset::UTC)?;
204
0
        let dt = timestamp.to_datetime(IOffset::UTC);
205
0
        let (offset, abbrev, dst) = if dst_info.in_dst(dt) {
206
0
            (dst_info.offset(), dst_info.dst.abbrev.as_ref(), true)
207
        } else {
208
0
            (&self.std_offset, self.std_abbrev.as_ref(), false)
209
        };
210
0
        Some((timestamp, offset.to_ioffset(), abbrev, dst))
211
0
    }
Unexecuted instantiation: <jiff::shared::PosixTimeZone<jiff::shared::util::array_str::ArrayStr<30>>>::previous_transition
Unexecuted instantiation: <jiff::shared::PosixTimeZone<&str>>::previous_transition
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
0
    pub(crate) fn next_transition(
216
0
        &self,
217
0
        timestamp: ITimestamp,
218
0
    ) -> Option<(ITimestamp, IOffset, &'_ str, bool)> {
219
0
        let dt = timestamp.to_datetime(IOffset::UTC);
220
0
        let dst_info = self.dst_info_utc(dt.date.year)?;
221
0
        let (earlier, later) = dst_info.ordered();
222
0
        let (next, dst_info) = if dt < earlier {
223
0
            (earlier, dst_info)
224
0
        } else if dt < later {
225
0
            (later, dst_info)
226
        } else {
227
0
            let next_year = dt.date.next_year().ok()?;
228
0
            let dst_info = self.dst_info_utc(next_year)?;
229
0
            let (earlier, _) = dst_info.ordered();
230
0
            (earlier, dst_info)
231
        };
232
233
0
        let timestamp = next.to_timestamp_checked(IOffset::UTC)?;
234
0
        let dt = timestamp.to_datetime(IOffset::UTC);
235
0
        let (offset, abbrev, dst) = if dst_info.in_dst(dt) {
236
0
            (dst_info.offset(), dst_info.dst.abbrev.as_ref(), true)
237
        } else {
238
0
            (&self.std_offset, self.std_abbrev.as_ref(), false)
239
        };
240
0
        Some((timestamp, offset.to_ioffset(), abbrev, dst))
241
0
    }
Unexecuted instantiation: <jiff::shared::PosixTimeZone<jiff::shared::util::array_str::ArrayStr<30>>>::next_transition
Unexecuted instantiation: <jiff::shared::PosixTimeZone<&str>>::next_transition
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
0
    fn dst_info_utc(&self, year: i16) -> Option<DstInfo<'_, ABBREV>> {
248
0
        let dst = self.dst.as_ref()?;
249
        // DST time starts with respect to standard time, so offset it by the
250
        // standard offset.
251
0
        let start =
252
0
            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
0
        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
0
        if start.date.month == 1
307
0
            && start.date.day == 1
308
0
            && start.time == ITime::MIN
309
            // NOTE: This should come last because it is potentially expensive.
310
0
            && year
311
0
                != end.saturating_add_seconds(self.std_offset.second).date.year
312
0
        {
313
0
            end = IDateTime {
314
0
                date: IDate { year, month: 12, day: 31 },
315
0
                time: ITime::MAX,
316
0
            };
317
0
        }
318
0
        Some(DstInfo { dst, start, end })
319
0
    }
Unexecuted instantiation: <jiff::shared::PosixTimeZone<jiff::shared::util::array_str::ArrayStr<30>>>::dst_info_utc
Unexecuted instantiation: <jiff::shared::PosixTimeZone<&str>>::dst_info_utc
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
0
    fn dst_info_wall(&self, year: i16) -> Option<DstInfo<'_, ABBREV>> {
327
0
        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
0
        let start = dst.rule.start.to_datetime(year, IOffset::UTC);
332
0
        let end = dst.rule.end.to_datetime(year, IOffset::UTC);
333
0
        Some(DstInfo { dst, start, end })
334
0
    }
Unexecuted instantiation: <jiff::shared::PosixTimeZone<jiff::shared::util::array_str::ArrayStr<30>>>::dst_info_wall
Unexecuted instantiation: <jiff::shared::PosixTimeZone<&str>>::dst_info_wall
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
344
impl<ABBREV: AsRef<str>> core::fmt::Display for PosixTimeZone<ABBREV> {
345
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
346
0
        core::fmt::Display::fmt(
347
0
            &AbbreviationDisplay(self.std_abbrev.as_ref()),
348
0
            f,
349
0
        )?;
350
0
        core::fmt::Display::fmt(&self.std_offset, f)?;
351
0
        if let Some(ref dst) = self.dst {
352
0
            dst.display(&self.std_offset, f)?;
353
0
        }
354
0
        Ok(())
355
0
    }
356
}
357
358
impl<ABBREV: AsRef<str>> PosixDst<ABBREV> {
359
0
    fn display(
360
0
        &self,
361
0
        std_offset: &PosixOffset,
362
0
        f: &mut core::fmt::Formatter,
363
0
    ) -> core::fmt::Result {
364
0
        core::fmt::Display::fmt(
365
0
            &AbbreviationDisplay(self.abbrev.as_ref()),
366
0
            f,
367
0
        )?;
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
0
        let default = PosixOffset { second: std_offset.second + 3600 };
372
0
        if self.offset != default {
373
0
            core::fmt::Display::fmt(&self.offset, f)?;
374
0
        }
375
0
        f.write_str(",")?;
376
0
        core::fmt::Display::fmt(&self.rule, f)?;
377
0
        Ok(())
378
0
    }
379
}
380
381
impl core::fmt::Display for PosixRule {
382
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
383
0
        core::fmt::Display::fmt(&self.start, f)?;
384
0
        f.write_str(",")?;
385
0
        core::fmt::Display::fmt(&self.end, f)?;
386
0
        Ok(())
387
0
    }
388
}
389
390
impl 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
0
    pub(crate) fn to_datetime(&self, year: i16, offset: IOffset) -> IDateTime {
403
0
        let mkmin = || IDateTime {
404
0
            date: IDate { year, month: 1, day: 1 },
405
            time: ITime::MIN,
406
0
        };
407
0
        let mkmax = || IDateTime {
408
0
            date: IDate { year, month: 12, day: 31 },
409
            time: ITime::MAX,
410
0
        };
411
0
        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
0
        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
0
        let days = offset.div_euclid(86400);
419
0
        let second = offset.rem_euclid(86400);
420
421
0
        let Ok(date) = date.checked_add_days(days) else {
422
0
            return if offset < 0 { mkmin() } else { mkmax() };
423
        };
424
0
        if date.year < year {
425
0
            mkmin()
426
0
        } else if date.year > year {
427
0
            mkmax()
428
        } else {
429
0
            let time = ITimeSecond { second }.to_time();
430
0
            IDateTime { date, time }
431
        }
432
0
    }
433
}
434
435
impl core::fmt::Display for PosixDayTime {
436
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
437
0
        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
0
        if self.time != PosixTime::DEFAULT {
441
0
            f.write_str("/")?;
442
0
            core::fmt::Display::fmt(&self.time, f)?;
443
0
        }
444
0
        Ok(())
445
0
    }
446
}
447
448
impl 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
0
    fn to_date(&self, year: i16) -> Option<IDate> {
456
0
        match *self {
457
0
            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
0
                Some(
462
0
                    IDate::from_day_of_year_no_leap(year, day)
463
0
                        .expect("Julian `J day` should be in bounds"),
464
0
                )
465
            }
466
0
            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
0
                IDate::from_day_of_year(year, day + 1).ok()
477
            }
478
0
            PosixDay::WeekdayOfMonth { month, week, weekday } => {
479
0
                let weekday = IWeekday::from_sunday_zero_offset(weekday);
480
0
                let first = IDate { year, month, day: 1 };
481
0
                let week = if week == 5 { -1 } else { week };
482
0
                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
0
                Some(
498
0
                    first
499
0
                        .nth_weekday_of_month(week, weekday)
500
0
                        .expect("nth weekday always exists"),
501
0
                )
502
            }
503
        }
504
0
    }
505
}
506
507
impl core::fmt::Display for PosixDay {
508
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
509
0
        match *self {
510
0
            PosixDay::JulianOne(n) => {
511
0
                f.write_str("J")?;
512
0
                core::fmt::Display::fmt(&n, f)
513
            }
514
0
            PosixDay::JulianZero(n) => core::fmt::Display::fmt(&n, f),
515
0
            PosixDay::WeekdayOfMonth { month, week, weekday } => {
516
0
                f.write_str("M")?;
517
0
                core::fmt::Display::fmt(&month, f)?;
518
0
                f.write_str(".")?;
519
0
                core::fmt::Display::fmt(&week, f)?;
520
0
                f.write_str(".")?;
521
0
                core::fmt::Display::fmt(&weekday, f)?;
522
0
                Ok(())
523
            }
524
        }
525
0
    }
526
}
527
528
impl PosixTime {
529
    const DEFAULT: PosixTime = PosixTime { second: 2 * 60 * 60 };
530
}
531
532
impl core::fmt::Display for PosixTime {
533
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
534
0
        if self.second.is_negative() {
535
0
            f.write_str("-")?;
536
            // The default is positive, so when
537
            // positive, we write nothing.
538
0
        }
539
0
        let second = self.second.unsigned_abs();
540
0
        let h = second / 3600;
541
0
        let m = (second / 60) % 60;
542
0
        let s = second % 60;
543
0
        core::fmt::Display::fmt(&h, f)?;
544
0
        if m != 0 || s != 0 {
545
0
            write!(f, ":{m:02}")?;
546
0
            if s != 0 {
547
0
                write!(f, ":{s:02}")?;
548
0
            }
549
0
        }
550
0
        Ok(())
551
0
    }
552
}
553
554
impl PosixOffset {
555
0
    fn to_ioffset(&self) -> IOffset {
556
0
        IOffset { second: self.second }
557
0
    }
558
}
559
560
impl core::fmt::Display for PosixOffset {
561
0
    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
0
        if self.second > 0 {
566
0
            f.write_str("-")?;
567
0
        }
568
0
        let second = self.second.unsigned_abs();
569
0
        let h = second / 3600;
570
0
        let m = (second / 60) % 60;
571
0
        let s = second % 60;
572
0
        core::fmt::Display::fmt(&h, f)?;
573
0
        if m != 0 || s != 0 {
574
0
            write!(f, ":{m:02}")?;
575
0
            if s != 0 {
576
0
                write!(f, ":{s:02}")?;
577
0
            }
578
0
        }
579
0
        Ok(())
580
0
    }
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)]
588
struct AbbreviationDisplay<S>(S);
589
590
impl<S: AsRef<str>> core::fmt::Display for AbbreviationDisplay<S> {
591
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
592
0
        let s = self.0.as_ref();
593
0
        if s.chars().any(|ch| ch == '+' || ch == '-') {
594
0
            f.write_str("<")?;
595
0
            core::fmt::Display::fmt(&s, f)?;
596
0
            f.write_str(">")
597
        } else {
598
0
            core::fmt::Display::fmt(&s, f)
599
        }
600
0
    }
601
}
602
603
/// The daylight saving time (DST) info for a POSIX time zone in a particular
604
/// year.
605
#[derive(Debug, Eq, PartialEq)]
606
struct 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
629
impl<'a, ABBREV> DstInfo<'a, ABBREV> {
630
    /// Returns true if and only if the given civil datetime ought to be
631
    /// considered in DST.
632
0
    fn in_dst(&self, utc_dt: IDateTime) -> bool {
633
0
        if self.start <= self.end {
634
0
            self.start <= utc_dt && utc_dt < self.end
635
        } else {
636
0
            !(self.end <= utc_dt && utc_dt < self.start)
637
        }
638
0
    }
Unexecuted instantiation: <jiff::shared::posix::DstInfo<jiff::shared::util::array_str::ArrayStr<30>>>::in_dst
Unexecuted instantiation: <jiff::shared::posix::DstInfo<&str>>::in_dst
639
640
    /// Returns the earlier and later times for this DST info.
641
0
    fn ordered(&self) -> (IDateTime, IDateTime) {
642
0
        if self.start <= self.end {
643
0
            (self.start, self.end)
644
        } else {
645
0
            (self.end, self.start)
646
        }
647
0
    }
Unexecuted instantiation: <jiff::shared::posix::DstInfo<jiff::shared::util::array_str::ArrayStr<30>>>::ordered
Unexecuted instantiation: <jiff::shared::posix::DstInfo<&str>>::ordered
648
649
    /// Returns the DST offset.
650
0
    fn offset(&self) -> &PosixOffset {
651
0
        &self.dst.offset
652
0
    }
Unexecuted instantiation: <jiff::shared::posix::DstInfo<jiff::shared::util::array_str::ArrayStr<30>>>::offset
Unexecuted instantiation: <jiff::shared::posix::DstInfo<&str>>::offset
653
}
654
655
/// A parser for POSIX time zones.
656
#[derive(Debug)]
657
struct 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
678
impl<'s> Parser<'s> {
679
    /// Create a new parser for extracting a POSIX time zone from the given
680
    /// bytes.
681
0
    fn new<B: ?Sized + AsRef<[u8]>>(tz: &'s B) -> Parser<'s> {
682
0
        Parser {
683
0
            tz: tz.as_ref(),
684
0
            pos: core::cell::Cell::new(0),
685
0
            ianav3plus: false,
686
0
        }
687
0
    }
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
0
    fn parse(
693
0
        &self,
694
0
    ) -> Result<PosixTimeZone<Abbreviation>, PosixTimeZoneError> {
695
0
        let (time_zone, remaining) = self.parse_prefix()?;
696
0
        if !remaining.is_empty() {
697
0
            return Err(ErrorKind::FoundRemaining.into());
698
0
        }
699
0
        Ok(time_zone)
700
0
    }
701
702
    /// Parses a POSIX time zone from the current position of the parser and
703
    /// returns the remaining input.
704
0
    fn parse_prefix(
705
0
        &self,
706
0
    ) -> Result<(PosixTimeZone<Abbreviation>, &'s [u8]), PosixTimeZoneError>
707
    {
708
0
        let time_zone = self.parse_posix_time_zone()?;
709
0
        Ok((time_zone, self.remaining()))
710
0
    }
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
0
    fn parse_posix_time_zone(
717
0
        &self,
718
0
    ) -> Result<PosixTimeZone<Abbreviation>, PosixTimeZoneError> {
719
0
        if self.is_done() {
720
0
            return Err(ErrorKind::Empty.into());
721
0
        }
722
0
        let std_abbrev =
723
0
            self.parse_abbreviation().map_err(ErrorKind::AbbreviationStd)?;
724
0
        let std_offset =
725
0
            self.parse_posix_offset().map_err(ErrorKind::OffsetStd)?;
726
0
        let mut dst = None;
727
0
        if !self.is_done()
728
0
            && (self.byte().is_ascii_alphabetic() || self.byte() == b'<')
729
        {
730
0
            dst = Some(self.parse_posix_dst(&std_offset)?);
731
0
        }
732
0
        Ok(PosixTimeZone { std_abbrev, std_offset, dst })
733
0
    }
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
0
    fn parse_posix_dst(
744
0
        &self,
745
0
        std_offset: &PosixOffset,
746
0
    ) -> Result<PosixDst<Abbreviation>, PosixTimeZoneError> {
747
0
        let abbrev =
748
0
            self.parse_abbreviation().map_err(ErrorKind::AbbreviationDst)?;
749
0
        if self.is_done() {
750
0
            return Err(ErrorKind::FoundDstNoRule.into());
751
0
        }
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
0
        let mut offset = PosixOffset { second: std_offset.second + 3600 };
756
0
        if self.byte() != b',' {
757
            offset =
758
0
                self.parse_posix_offset().map_err(ErrorKind::OffsetDst)?;
759
0
            if self.is_done() {
760
0
                return Err(ErrorKind::FoundDstNoRuleWithOffset.into());
761
0
            }
762
0
        }
763
0
        if self.byte() != b',' {
764
0
            return Err(ErrorKind::ExpectedCommaAfterDst.into());
765
0
        }
766
0
        if !self.bump() {
767
0
            return Err(ErrorKind::FoundEndAfterComma.into());
768
0
        }
769
0
        let rule = self.parse_rule().map_err(ErrorKind::Rule)?;
770
0
        Ok(PosixDst { abbrev, offset, rule })
771
0
    }
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
0
    fn parse_abbreviation(&self) -> Result<Abbreviation, AbbreviationError> {
786
0
        if self.byte() == b'<' {
787
0
            if !self.bump() {
788
0
                return Err(AbbreviationError::Quoted(
789
0
                    QuotedAbbreviationError::UnexpectedEndAfterOpening,
790
0
                ));
791
0
            }
792
0
            self.parse_quoted_abbreviation().map_err(AbbreviationError::Quoted)
793
        } else {
794
0
            self.parse_unquoted_abbreviation()
795
0
                .map_err(AbbreviationError::Unquoted)
796
        }
797
0
    }
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
0
    fn parse_unquoted_abbreviation(
811
0
        &self,
812
0
    ) -> Result<Abbreviation, UnquotedAbbreviationError> {
813
0
        let start = self.pos();
814
0
        for i in 0.. {
815
0
            if !self.byte().is_ascii_alphabetic() {
816
0
                break;
817
0
            }
818
0
            if i >= Abbreviation::capacity() {
819
0
                return Err(UnquotedAbbreviationError::TooLong);
820
0
            }
821
0
            if !self.bump() {
822
0
                break;
823
0
            }
824
        }
825
0
        let end = self.pos();
826
0
        let abbrev =
827
0
            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
0
                UnquotedAbbreviationError::InvalidUtf8
835
0
            })?;
836
0
        if abbrev.len() < 3 {
837
0
            return Err(UnquotedAbbreviationError::TooShort);
838
0
        }
839
        // OK because we verified above that the abbreviation
840
        // does not exceed `Abbreviation::capacity`.
841
0
        Ok(Abbreviation::new(abbrev).unwrap())
842
0
    }
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
0
    fn parse_quoted_abbreviation(
856
0
        &self,
857
0
    ) -> Result<Abbreviation, QuotedAbbreviationError> {
858
0
        let start = self.pos();
859
0
        for i in 0.. {
860
0
            if !self.byte().is_ascii_alphanumeric()
861
0
                && self.byte() != b'+'
862
0
                && self.byte() != b'-'
863
            {
864
0
                break;
865
0
            }
866
0
            if i >= Abbreviation::capacity() {
867
0
                return Err(QuotedAbbreviationError::TooLong);
868
0
            }
869
0
            if !self.bump() {
870
0
                break;
871
0
            }
872
        }
873
0
        let end = self.pos();
874
0
        let abbrev =
875
0
            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
0
                QuotedAbbreviationError::InvalidUtf8
883
0
            })?;
884
0
        if self.is_done() {
885
0
            return Err(QuotedAbbreviationError::UnexpectedEnd);
886
0
        }
887
0
        if self.byte() != b'>' {
888
0
            return Err(QuotedAbbreviationError::UnexpectedLastByte);
889
0
        }
890
0
        self.bump();
891
0
        if abbrev.len() < 3 {
892
0
            return Err(QuotedAbbreviationError::TooShort);
893
0
        }
894
        // OK because we verified above that the abbreviation
895
        // does not exceed `Abbreviation::capacity`.
896
0
        Ok(Abbreviation::new(abbrev).unwrap())
897
0
    }
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
0
    fn parse_posix_offset(&self) -> Result<PosixOffset, PosixOffsetError> {
908
0
        let sign = self.parse_optional_sign()?.unwrap_or(1);
909
0
        let hour = self.parse_hour_posix()?;
910
0
        let (mut minute, mut second) = (0, 0);
911
0
        if self.maybe_byte() == Some(b':') {
912
0
            if !self.bump() {
913
0
                return Err(PosixOffsetError::IncompleteMinutes);
914
0
            }
915
0
            minute = self.parse_minute()?;
916
0
            if self.maybe_byte() == Some(b':') {
917
0
                if !self.bump() {
918
0
                    return Err(PosixOffsetError::IncompleteSeconds);
919
0
                }
920
0
                second = self.parse_second()?;
921
0
            }
922
0
        }
923
0
        let mut offset = PosixOffset { second: i32::from(hour) * 3600 };
924
0
        offset.second += i32::from(minute) * 60;
925
0
        offset.second += i32::from(second);
926
        // Yes, we flip the sign, because POSIX is backwards.
927
        // For example, `EST5` corresponds to `-05:00`.
928
0
        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
0
        assert!(
933
0
            -89999 <= offset.second && offset.second <= 89999,
934
0
            "POSIX offset seconds {} is out of range",
935
            offset.second
936
        );
937
0
        Ok(offset)
938
0
    }
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
0
    fn parse_rule(&self) -> Result<PosixRule, PosixRuleError> {
950
0
        let start = self
951
0
            .parse_posix_datetime()
952
0
            .map_err(PosixRuleError::DateTimeStart)?;
953
0
        if self.maybe_byte() != Some(b',') || !self.bump() {
954
0
            return Err(PosixRuleError::ExpectedEnd);
955
0
        }
956
0
        let end = self
957
0
            .parse_posix_datetime()
958
0
            .map_err(PosixRuleError::DateTimeEnd)?;
959
0
        Ok(PosixRule { start, end })
960
0
    }
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
0
    fn parse_posix_datetime(
971
0
        &self,
972
0
    ) -> Result<PosixDayTime, PosixDateTimeError> {
973
0
        let mut daytime = PosixDayTime {
974
0
            date: self.parse_posix_date()?,
975
            time: PosixTime::DEFAULT,
976
        };
977
0
        if self.maybe_byte() != Some(b'/') {
978
0
            return Ok(daytime);
979
0
        }
980
0
        if !self.bump() {
981
0
            return Err(PosixDateTimeError::ExpectedTime);
982
0
        }
983
0
        daytime.time = self.parse_posix_time()?;
984
0
        Ok(daytime)
985
0
    }
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
0
    fn parse_posix_date(&self) -> Result<PosixDay, PosixDateError> {
1001
0
        match self.byte() {
1002
            b'J' => {
1003
0
                if !self.bump() {
1004
0
                    return Err(PosixDateError::ExpectedJulianNoLeap);
1005
0
                }
1006
0
                Ok(PosixDay::JulianOne(self.parse_posix_julian_day_no_leap()?))
1007
            }
1008
0
            b'0'..=b'9' => Ok(PosixDay::JulianZero(
1009
0
                self.parse_posix_julian_day_with_leap()?,
1010
            )),
1011
            b'M' => {
1012
0
                if !self.bump() {
1013
0
                    return Err(PosixDateError::ExpectedMonthWeekWeekday);
1014
0
                }
1015
0
                let (month, week, weekday) = self.parse_weekday_of_month()?;
1016
0
                Ok(PosixDay::WeekdayOfMonth { month, week, weekday })
1017
            }
1018
0
            _ => Err(PosixDateError::UnexpectedByte),
1019
        }
1020
0
    }
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
0
    fn parse_posix_julian_day_no_leap(
1029
0
        &self,
1030
0
    ) -> Result<i16, PosixJulianNoLeapError> {
1031
0
        let number = self
1032
0
            .parse_number_with_upto_n_digits(3)
1033
0
            .map_err(PosixJulianNoLeapError::Parse)?;
1034
0
        let number = i16::try_from(number)
1035
0
            .map_err(|_| PosixJulianNoLeapError::Range)?;
1036
0
        if !(1 <= number && number <= 365) {
1037
0
            return Err(PosixJulianNoLeapError::Range);
1038
0
        }
1039
0
        Ok(number)
1040
0
    }
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
0
    fn parse_posix_julian_day_with_leap(
1049
0
        &self,
1050
0
    ) -> Result<i16, PosixJulianLeapError> {
1051
0
        let number = self
1052
0
            .parse_number_with_upto_n_digits(3)
1053
0
            .map_err(PosixJulianLeapError::Parse)?;
1054
0
        let number =
1055
0
            i16::try_from(number).map_err(|_| PosixJulianLeapError::Range)?;
1056
0
        if !(0 <= number && number <= 365) {
1057
0
            return Err(PosixJulianLeapError::Range);
1058
0
        }
1059
0
        Ok(number)
1060
0
    }
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
0
    fn parse_weekday_of_month(
1072
0
        &self,
1073
0
    ) -> Result<(i8, i8, i8), WeekdayOfMonthError> {
1074
0
        let month = self.parse_month()?;
1075
0
        if self.maybe_byte() != Some(b'.') {
1076
0
            return Err(WeekdayOfMonthError::ExpectedDotAfterMonth);
1077
0
        }
1078
0
        if !self.bump() {
1079
0
            return Err(WeekdayOfMonthError::ExpectedWeekAfterMonth);
1080
0
        }
1081
0
        let week = self.parse_week()?;
1082
0
        if self.maybe_byte() != Some(b'.') {
1083
0
            return Err(WeekdayOfMonthError::ExpectedDotAfterWeek);
1084
0
        }
1085
0
        if !self.bump() {
1086
0
            return Err(WeekdayOfMonthError::ExpectedDayOfWeekAfterWeek);
1087
0
        }
1088
0
        let weekday = self.parse_weekday()?;
1089
0
        Ok((month, week, weekday))
1090
0
    }
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
0
    fn parse_posix_time(&self) -> Result<PosixTime, PosixTimeError> {
1099
0
        let (sign, hour) = if self.ianav3plus {
1100
0
            let sign = self.parse_optional_sign()?.unwrap_or(1);
1101
0
            let hour = self.parse_hour_ianav3plus()?;
1102
0
            (sign, hour)
1103
        } else {
1104
0
            (1, i16::from(self.parse_hour_posix()?))
1105
        };
1106
0
        let (mut minute, mut second) = (0, 0);
1107
0
        if self.maybe_byte() == Some(b':') {
1108
0
            if !self.bump() {
1109
0
                return Err(PosixTimeError::IncompleteMinutes);
1110
0
            }
1111
0
            minute = self.parse_minute()?;
1112
0
            if self.maybe_byte() == Some(b':') {
1113
0
                if !self.bump() {
1114
0
                    return Err(PosixTimeError::IncompleteSeconds);
1115
0
                }
1116
0
                second = self.parse_second()?;
1117
0
            }
1118
0
        }
1119
0
        let mut time = PosixTime { second: i32::from(hour) * 3600 };
1120
0
        time.second += i32::from(minute) * 60;
1121
0
        time.second += i32::from(second);
1122
0
        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
0
        assert!(
1127
0
            -604799 <= time.second && time.second <= 604799,
1128
0
            "POSIX time seconds {} is out of range",
1129
            time.second
1130
        );
1131
0
        Ok(time)
1132
0
    }
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
0
    fn parse_month(&self) -> Result<i8, MonthError> {
1140
0
        let number = self
1141
0
            .parse_number_with_upto_n_digits(2)
1142
0
            .map_err(MonthError::Parse)?;
1143
0
        let number = i8::try_from(number).map_err(|_| MonthError::Range)?;
1144
0
        if !(1 <= number && number <= 12) {
1145
0
            return Err(MonthError::Range);
1146
0
        }
1147
0
        Ok(number)
1148
0
    }
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
0
    fn parse_week(&self) -> Result<i8, WeekOfMonthError> {
1155
0
        let number = self
1156
0
            .parse_number_with_exactly_n_digits(1)
1157
0
            .map_err(WeekOfMonthError::Parse)?;
1158
0
        let number =
1159
0
            i8::try_from(number).map_err(|_| WeekOfMonthError::Range)?;
1160
0
        if !(1 <= number && number <= 5) {
1161
0
            return Err(WeekOfMonthError::Range);
1162
0
        }
1163
0
        Ok(number)
1164
0
    }
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
0
    fn parse_weekday(&self) -> Result<i8, WeekdayError> {
1174
0
        let number = self
1175
0
            .parse_number_with_exactly_n_digits(1)
1176
0
            .map_err(WeekdayError::Parse)?;
1177
0
        let number = i8::try_from(number).map_err(|_| WeekdayError::Range)?;
1178
0
        if !(0 <= number && number <= 6) {
1179
0
            return Err(WeekdayError::Range);
1180
0
        }
1181
0
        Ok(number)
1182
0
    }
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
0
    fn parse_hour_ianav3plus(&self) -> Result<i16, HourIanaError> {
1196
        // Callers should only be using this method when IANA v3+ parsing
1197
        // is enabled.
1198
0
        assert!(self.ianav3plus);
1199
0
        let number = self
1200
0
            .parse_number_with_upto_n_digits(3)
1201
0
            .map_err(HourIanaError::Parse)?;
1202
0
        let number =
1203
0
            i16::try_from(number).map_err(|_| HourIanaError::Range)?;
1204
0
        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
0
            return Err(HourIanaError::Range);
1209
0
        }
1210
0
        Ok(number)
1211
0
    }
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
0
    fn parse_hour_posix(&self) -> Result<i8, HourPosixError> {
1223
0
        let number = self
1224
0
            .parse_number_with_upto_n_digits(2)
1225
0
            .map_err(HourPosixError::Parse)?;
1226
0
        let number =
1227
0
            i8::try_from(number).map_err(|_| HourPosixError::Range)?;
1228
0
        if !(0 <= number && number <= 24) {
1229
0
            return Err(HourPosixError::Range);
1230
0
        }
1231
0
        Ok(number)
1232
0
    }
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
0
    fn parse_minute(&self) -> Result<i8, MinuteError> {
1242
0
        let number = self
1243
0
            .parse_number_with_exactly_n_digits(2)
1244
0
            .map_err(MinuteError::Parse)?;
1245
0
        let number = i8::try_from(number).map_err(|_| MinuteError::Range)?;
1246
0
        if !(0 <= number && number <= 59) {
1247
0
            return Err(MinuteError::Range);
1248
0
        }
1249
0
        Ok(number)
1250
0
    }
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
0
    fn parse_second(&self) -> Result<i8, SecondError> {
1260
0
        let number = self
1261
0
            .parse_number_with_exactly_n_digits(2)
1262
0
            .map_err(SecondError::Parse)?;
1263
0
        let number = i8::try_from(number).map_err(|_| SecondError::Range)?;
1264
0
        if !(0 <= number && number <= 59) {
1265
0
            return Err(SecondError::Range);
1266
0
        }
1267
0
        Ok(number)
1268
0
    }
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
0
    fn parse_number_with_exactly_n_digits(
1279
0
        &self,
1280
0
        n: usize,
1281
0
    ) -> Result<i32, NumberError> {
1282
0
        assert!(n >= 1, "numbers must have at least 1 digit");
1283
0
        let mut number: i32 = 0;
1284
0
        for _ in 0..n {
1285
0
            if self.is_done() {
1286
0
                return Err(NumberError::ExpectedLength);
1287
0
            }
1288
0
            let byte = self.byte();
1289
0
            let digit = match byte.checked_sub(b'0') {
1290
                None => {
1291
0
                    return Err(NumberError::InvalidDigit);
1292
                }
1293
0
                Some(digit) if digit > 9 => {
1294
0
                    return Err(NumberError::InvalidDigit);
1295
                }
1296
0
                Some(digit) => {
1297
0
                    debug_assert!((0..=9).contains(&digit));
1298
0
                    i32::from(digit)
1299
                }
1300
            };
1301
0
            number = number
1302
0
                .checked_mul(10)
1303
0
                .and_then(|n| n.checked_add(digit))
1304
0
                .ok_or(NumberError::TooBig)?;
1305
0
            self.bump();
1306
        }
1307
0
        Ok(number)
1308
0
    }
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
0
    fn parse_number_with_upto_n_digits(
1317
0
        &self,
1318
0
        n: usize,
1319
0
    ) -> Result<i32, NumberError> {
1320
0
        assert!(n >= 1, "numbers must have at least 1 digit");
1321
0
        let mut number: i32 = 0;
1322
0
        for i in 0..n {
1323
0
            if self.is_done() || !self.byte().is_ascii_digit() {
1324
0
                if i == 0 {
1325
0
                    return Err(NumberError::Empty);
1326
0
                }
1327
0
                break;
1328
0
            }
1329
0
            let digit = i32::from(self.byte() - b'0');
1330
0
            number = number
1331
0
                .checked_mul(10)
1332
0
                .and_then(|n| n.checked_add(digit))
1333
0
                .ok_or(NumberError::TooBig)?;
1334
0
            self.bump();
1335
        }
1336
0
        Ok(number)
1337
0
    }
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
0
    fn parse_optional_sign(&self) -> Result<Option<i8>, OptionalSignError> {
1347
0
        if self.is_done() {
1348
0
            return Ok(None);
1349
0
        }
1350
0
        Ok(match self.byte() {
1351
            b'-' => {
1352
0
                if !self.bump() {
1353
0
                    return Err(OptionalSignError::ExpectedDigitAfterMinus);
1354
0
                }
1355
0
                Some(-1)
1356
            }
1357
            b'+' => {
1358
0
                if !self.bump() {
1359
0
                    return Err(OptionalSignError::ExpectedDigitAfterPlus);
1360
0
                }
1361
0
                Some(1)
1362
            }
1363
0
            _ => None,
1364
        })
1365
0
    }
1366
}
1367
1368
/// Helper routines for parsing a POSIX `TZ` string.
1369
impl<'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
0
    fn bump(&self) -> bool {
1374
0
        if self.is_done() {
1375
0
            return false;
1376
0
        }
1377
0
        self.pos.set(
1378
0
            self.pos().checked_add(1).expect("pos cannot overflow usize"),
1379
        );
1380
0
        !self.is_done()
1381
0
    }
1382
1383
    /// Returns true if the next call to `bump` would return false.
1384
0
    fn is_done(&self) -> bool {
1385
0
        self.pos() == self.tz.len()
1386
0
    }
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
0
    fn byte(&self) -> u8 {
1393
0
        self.tz[self.pos()]
1394
0
    }
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
0
    fn maybe_byte(&self) -> Option<u8> {
1399
0
        self.tz.get(self.pos()).copied()
1400
0
    }
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
0
    fn pos(&self) -> usize {
1406
0
        self.pos.get()
1407
0
    }
1408
1409
    /// Returns the remaining bytes of the TZ string.
1410
    ///
1411
    /// This includes `self.byte()`. It may be empty.
1412
0
    fn remaining(&self) -> &'s [u8] {
1413
0
        &self.tz[self.pos()..]
1414
0
    }
1415
}
1416
1417
#[derive(Clone, Debug, Eq, PartialEq)]
1418
pub struct PosixTimeZoneError {
1419
    kind: ErrorKind,
1420
}
1421
1422
#[derive(Clone, Debug, Eq, PartialEq)]
1423
enum 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
1437
impl core::fmt::Display for PosixTimeZoneError {
1438
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1439
        use self::ErrorKind::*;
1440
0
        match self.kind {
1441
0
            AbbreviationDst(ref err) => {
1442
0
                f.write_str("failed to parse DST time zone abbreviation: ")?;
1443
0
                core::fmt::Display::fmt(err, f)
1444
            }
1445
0
            AbbreviationStd(ref err) => {
1446
0
                f.write_str(
1447
0
                    "failed to parse standard time zone abbreviation: ",
1448
0
                )?;
1449
0
                core::fmt::Display::fmt(err, f)
1450
            }
1451
0
            Empty => f.write_str(
1452
0
                "an empty string is not a valid POSIX time zone \
1453
0
                 transition rule",
1454
            ),
1455
0
            ExpectedCommaAfterDst => f.write_str(
1456
0
                "expected `,` after parsing DST offset \
1457
0
                 in POSIX time zone string",
1458
            ),
1459
0
            FoundDstNoRule => f.write_str(
1460
0
                "found DST abbreviation in POSIX time zone string, \
1461
0
                 but no transition rule \
1462
0
                 (this is technically allowed by POSIX, but has \
1463
0
                 unspecified behavior)",
1464
            ),
1465
0
            FoundDstNoRuleWithOffset => f.write_str(
1466
0
                "found DST abbreviation and offset in POSIX time zone string, \
1467
0
                 but no transition rule \
1468
0
                 (this is technically allowed by POSIX, but has \
1469
0
                 unspecified behavior)",
1470
            ),
1471
0
            FoundEndAfterComma => f.write_str(
1472
0
                "after parsing DST offset in POSIX time zone string, \
1473
0
                 found end of string after a trailing `,`",
1474
            ),
1475
0
            FoundRemaining => f.write_str(
1476
0
                "expected entire POSIX TZ string to be a valid \
1477
0
                 time zone transition rule, but found data after \
1478
0
                 parsing a valid time zone transition rule",
1479
            ),
1480
0
            OffsetDst(ref err) => {
1481
0
                f.write_str("failed to parse DST offset: ")?;
1482
0
                core::fmt::Display::fmt(err, f)
1483
            }
1484
0
            OffsetStd(ref err) => {
1485
0
                f.write_str("failed to parse standard offset: ")?;
1486
0
                core::fmt::Display::fmt(err, f)
1487
            }
1488
0
            Rule(ref err) => core::fmt::Display::fmt(err, f),
1489
        }
1490
0
    }
1491
}
1492
1493
impl From<ErrorKind> for PosixTimeZoneError {
1494
0
    fn from(kind: ErrorKind) -> PosixTimeZoneError {
1495
0
        PosixTimeZoneError { kind }
1496
0
    }
1497
}
1498
1499
#[derive(Clone, Debug, Eq, PartialEq)]
1500
enum PosixOffsetError {
1501
    HourPosix(HourPosixError),
1502
    IncompleteMinutes,
1503
    IncompleteSeconds,
1504
    Minute(MinuteError),
1505
    OptionalSign(OptionalSignError),
1506
    Second(SecondError),
1507
}
1508
1509
impl core::fmt::Display for PosixOffsetError {
1510
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1511
        use self::PosixOffsetError::*;
1512
0
        match *self {
1513
0
            HourPosix(ref err) => core::fmt::Display::fmt(err, f),
1514
0
            IncompleteMinutes => f.write_str(
1515
0
                "incomplete time in \
1516
0
                 POSIX time zone string (missing minutes)",
1517
            ),
1518
0
            IncompleteSeconds => f.write_str(
1519
0
                "incomplete time in \
1520
0
                 POSIX time zone string (missing seconds)",
1521
            ),
1522
0
            Minute(ref err) => core::fmt::Display::fmt(err, f),
1523
0
            Second(ref err) => core::fmt::Display::fmt(err, f),
1524
0
            OptionalSign(ref err) => {
1525
0
                f.write_str(
1526
0
                    "failed to parse sign for time offset \
1527
0
                     POSIX time zone string",
1528
0
                )?;
1529
0
                core::fmt::Display::fmt(err, f)
1530
            }
1531
        }
1532
0
    }
1533
}
1534
1535
impl From<HourPosixError> for PosixOffsetError {
1536
0
    fn from(err: HourPosixError) -> PosixOffsetError {
1537
0
        PosixOffsetError::HourPosix(err)
1538
0
    }
1539
}
1540
1541
impl From<MinuteError> for PosixOffsetError {
1542
0
    fn from(err: MinuteError) -> PosixOffsetError {
1543
0
        PosixOffsetError::Minute(err)
1544
0
    }
1545
}
1546
1547
impl From<OptionalSignError> for PosixOffsetError {
1548
0
    fn from(err: OptionalSignError) -> PosixOffsetError {
1549
0
        PosixOffsetError::OptionalSign(err)
1550
0
    }
1551
}
1552
1553
impl From<SecondError> for PosixOffsetError {
1554
0
    fn from(err: SecondError) -> PosixOffsetError {
1555
0
        PosixOffsetError::Second(err)
1556
0
    }
1557
}
1558
1559
#[derive(Clone, Debug, Eq, PartialEq)]
1560
enum PosixRuleError {
1561
    DateTimeEnd(PosixDateTimeError),
1562
    DateTimeStart(PosixDateTimeError),
1563
    ExpectedEnd,
1564
}
1565
1566
impl core::fmt::Display for PosixRuleError {
1567
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1568
        use self::PosixRuleError::*;
1569
0
        match *self {
1570
0
            DateTimeEnd(ref err) => {
1571
0
                f.write_str("failed to parse end of DST transition rule: ")?;
1572
0
                core::fmt::Display::fmt(err, f)
1573
            }
1574
0
            DateTimeStart(ref err) => {
1575
0
                f.write_str("failed to parse start of DST transition rule: ")?;
1576
0
                core::fmt::Display::fmt(err, f)
1577
            }
1578
0
            ExpectedEnd => f.write_str(
1579
0
                "expected end of DST rule after parsing the start \
1580
0
                 of the DST rule",
1581
            ),
1582
        }
1583
0
    }
1584
}
1585
1586
#[derive(Clone, Debug, Eq, PartialEq)]
1587
enum PosixDateTimeError {
1588
    Date(PosixDateError),
1589
    ExpectedTime,
1590
    Time(PosixTimeError),
1591
}
1592
1593
impl core::fmt::Display for PosixDateTimeError {
1594
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1595
        use self::PosixDateTimeError::*;
1596
0
        match *self {
1597
0
            Date(ref err) => core::fmt::Display::fmt(err, f),
1598
0
            ExpectedTime => f.write_str(
1599
0
                "expected time specification after `/` following a date
1600
0
                 specification in a POSIX time zone DST transition rule",
1601
            ),
1602
0
            Time(ref err) => core::fmt::Display::fmt(err, f),
1603
        }
1604
0
    }
1605
}
1606
1607
impl From<PosixDateError> for PosixDateTimeError {
1608
0
    fn from(err: PosixDateError) -> PosixDateTimeError {
1609
0
        PosixDateTimeError::Date(err)
1610
0
    }
1611
}
1612
1613
impl From<PosixTimeError> for PosixDateTimeError {
1614
0
    fn from(err: PosixTimeError) -> PosixDateTimeError {
1615
0
        PosixDateTimeError::Time(err)
1616
0
    }
1617
}
1618
1619
#[derive(Clone, Debug, Eq, PartialEq)]
1620
enum PosixDateError {
1621
    ExpectedJulianNoLeap,
1622
    ExpectedMonthWeekWeekday,
1623
    JulianLeap(PosixJulianLeapError),
1624
    JulianNoLeap(PosixJulianNoLeapError),
1625
    UnexpectedByte,
1626
    WeekdayOfMonth(WeekdayOfMonthError),
1627
}
1628
1629
impl core::fmt::Display for PosixDateError {
1630
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1631
        use self::PosixDateError::*;
1632
0
        match *self {
1633
0
            ExpectedJulianNoLeap => f.write_str(
1634
0
                "expected one-based Julian day after `J` in date \
1635
0
                 specification of a POSIX time zone DST \
1636
0
                 transition rule, but found the end of input",
1637
            ),
1638
0
            ExpectedMonthWeekWeekday => f.write_str(
1639
0
                "expected month-week-weekday after `M` in date \
1640
0
                 specification of a POSIX time zone DST \
1641
0
                 transition rule, but found the end of input",
1642
            ),
1643
0
            JulianLeap(ref err) => core::fmt::Display::fmt(err, f),
1644
0
            JulianNoLeap(ref err) => core::fmt::Display::fmt(err, f),
1645
0
            UnexpectedByte => f.write_str(
1646
0
                "expected `J`, a digit or `M` at the beginning of a date \
1647
0
                 specification of a POSIX time zone DST transition rule",
1648
            ),
1649
0
            WeekdayOfMonth(ref err) => core::fmt::Display::fmt(err, f),
1650
        }
1651
0
    }
1652
}
1653
1654
impl From<PosixJulianLeapError> for PosixDateError {
1655
0
    fn from(err: PosixJulianLeapError) -> PosixDateError {
1656
0
        PosixDateError::JulianLeap(err)
1657
0
    }
1658
}
1659
1660
impl From<PosixJulianNoLeapError> for PosixDateError {
1661
0
    fn from(err: PosixJulianNoLeapError) -> PosixDateError {
1662
0
        PosixDateError::JulianNoLeap(err)
1663
0
    }
1664
}
1665
1666
impl From<WeekdayOfMonthError> for PosixDateError {
1667
0
    fn from(err: WeekdayOfMonthError) -> PosixDateError {
1668
0
        PosixDateError::WeekdayOfMonth(err)
1669
0
    }
1670
}
1671
1672
#[derive(Clone, Debug, Eq, PartialEq)]
1673
enum PosixJulianNoLeapError {
1674
    Parse(NumberError),
1675
    Range,
1676
}
1677
1678
impl core::fmt::Display for PosixJulianNoLeapError {
1679
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1680
        use self::PosixJulianNoLeapError::*;
1681
0
        match *self {
1682
0
            Parse(ref err) => {
1683
0
                f.write_str("invalid one-based Julian day digits: ")?;
1684
0
                core::fmt::Display::fmt(err, f)
1685
            }
1686
0
            Range => f.write_str(
1687
0
                "parsed one-based Julian day, but it's not in supported \
1688
0
                 range of `1..=365`",
1689
            ),
1690
        }
1691
0
    }
1692
}
1693
1694
#[derive(Clone, Debug, Eq, PartialEq)]
1695
enum PosixJulianLeapError {
1696
    Parse(NumberError),
1697
    Range,
1698
}
1699
1700
impl core::fmt::Display for PosixJulianLeapError {
1701
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1702
        use self::PosixJulianLeapError::*;
1703
0
        match *self {
1704
0
            Parse(ref err) => {
1705
0
                f.write_str("invalid zero-based Julian day digits: ")?;
1706
0
                core::fmt::Display::fmt(err, f)
1707
            }
1708
0
            Range => f.write_str(
1709
0
                "parsed zero-based Julian day, but it's not in supported \
1710
0
                 range of `0..=365`",
1711
            ),
1712
        }
1713
0
    }
1714
}
1715
1716
#[derive(Clone, Debug, Eq, PartialEq)]
1717
enum AbbreviationError {
1718
    Quoted(QuotedAbbreviationError),
1719
    Unquoted(UnquotedAbbreviationError),
1720
}
1721
1722
impl core::fmt::Display for AbbreviationError {
1723
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1724
        use self::AbbreviationError::*;
1725
0
        match *self {
1726
0
            Quoted(ref err) => core::fmt::Display::fmt(err, f),
1727
0
            Unquoted(ref err) => core::fmt::Display::fmt(err, f),
1728
        }
1729
0
    }
1730
}
1731
1732
#[derive(Clone, Debug, Eq, PartialEq)]
1733
enum UnquotedAbbreviationError {
1734
    InvalidUtf8,
1735
    TooLong,
1736
    TooShort,
1737
}
1738
1739
impl core::fmt::Display for UnquotedAbbreviationError {
1740
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1741
        use self::UnquotedAbbreviationError::*;
1742
0
        match *self {
1743
0
            InvalidUtf8 => f.write_str(
1744
0
                "unquoted time zone abbreviation must be valid UTF-8",
1745
            ),
1746
0
            TooLong => write!(
1747
0
                f,
1748
0
                "expected unquoted time zone abbreviation with at most \
1749
0
                 {} bytes, but found an abbreviation that is longer",
1750
0
                Abbreviation::capacity(),
1751
            ),
1752
0
            TooShort => f.write_str(
1753
0
                "expected unquoted time zone abbreviation to have length of \
1754
0
                 3 or more bytes, but an abbreviation that is shorter",
1755
            ),
1756
        }
1757
0
    }
1758
}
1759
1760
#[derive(Clone, Debug, Eq, PartialEq)]
1761
enum QuotedAbbreviationError {
1762
    InvalidUtf8,
1763
    TooLong,
1764
    TooShort,
1765
    UnexpectedEnd,
1766
    UnexpectedEndAfterOpening,
1767
    UnexpectedLastByte,
1768
}
1769
1770
impl core::fmt::Display for QuotedAbbreviationError {
1771
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1772
        use self::QuotedAbbreviationError::*;
1773
0
        match *self {
1774
0
            InvalidUtf8 => f.write_str(
1775
0
                "quoted time zone abbreviation must be valid UTF-8",
1776
            ),
1777
0
            TooLong => write!(
1778
0
                f,
1779
0
                "expected quoted time zone abbreviation with at most \
1780
0
                 {} bytes, but found an abbreviation that is longer",
1781
0
                Abbreviation::capacity(),
1782
            ),
1783
0
            TooShort => f.write_str(
1784
0
                "expected quoted time zone abbreviation to have length of \
1785
0
                 3 or more bytes, but an abbreviation that is shorter",
1786
            ),
1787
0
            UnexpectedEnd => f.write_str(
1788
0
                "found non-empty quoted time zone abbreviation, but \
1789
0
                 found end of input before an end-of-quoted abbreviation \
1790
0
                 `>` character",
1791
            ),
1792
0
            UnexpectedEndAfterOpening => f.write_str(
1793
0
                "found opening `<` quote for time zone abbreviation in \
1794
0
                 POSIX time zone transition rule, and expected a name \
1795
0
                 following it, but found the end of input instead",
1796
            ),
1797
0
            UnexpectedLastByte => f.write_str(
1798
0
                "found non-empty quoted time zone abbreviation, but \
1799
0
                 found did not find end-of-quoted abbreviation `>` \
1800
0
                 character",
1801
            ),
1802
        }
1803
0
    }
1804
}
1805
1806
#[derive(Clone, Debug, Eq, PartialEq)]
1807
enum WeekdayOfMonthError {
1808
    ExpectedDayOfWeekAfterWeek,
1809
    ExpectedDotAfterMonth,
1810
    ExpectedDotAfterWeek,
1811
    ExpectedWeekAfterMonth,
1812
    Month(MonthError),
1813
    WeekOfMonth(WeekOfMonthError),
1814
    Weekday(WeekdayError),
1815
}
1816
1817
impl core::fmt::Display for WeekdayOfMonthError {
1818
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1819
        use self::WeekdayOfMonthError::*;
1820
0
        match *self {
1821
0
            ExpectedDayOfWeekAfterWeek => f.write_str(
1822
0
                "expected day-of-week after week in POSIX time zone rule",
1823
            ),
1824
            ExpectedDotAfterMonth => {
1825
0
                f.write_str("expected `.` after month in POSIX time zone rule")
1826
            }
1827
0
            ExpectedWeekAfterMonth => f.write_str(
1828
0
                "expected week after month in POSIX time zone rule",
1829
            ),
1830
            ExpectedDotAfterWeek => {
1831
0
                f.write_str("expected `.` after week in POSIX time zone rule")
1832
            }
1833
0
            Month(ref err) => core::fmt::Display::fmt(err, f),
1834
0
            WeekOfMonth(ref err) => core::fmt::Display::fmt(err, f),
1835
0
            Weekday(ref err) => core::fmt::Display::fmt(err, f),
1836
        }
1837
0
    }
1838
}
1839
1840
impl From<MonthError> for WeekdayOfMonthError {
1841
0
    fn from(err: MonthError) -> WeekdayOfMonthError {
1842
0
        WeekdayOfMonthError::Month(err)
1843
0
    }
1844
}
1845
1846
impl From<WeekOfMonthError> for WeekdayOfMonthError {
1847
0
    fn from(err: WeekOfMonthError) -> WeekdayOfMonthError {
1848
0
        WeekdayOfMonthError::WeekOfMonth(err)
1849
0
    }
1850
}
1851
1852
impl From<WeekdayError> for WeekdayOfMonthError {
1853
0
    fn from(err: WeekdayError) -> WeekdayOfMonthError {
1854
0
        WeekdayOfMonthError::Weekday(err)
1855
0
    }
1856
}
1857
1858
#[derive(Clone, Debug, Eq, PartialEq)]
1859
enum PosixTimeError {
1860
    HourIana(HourIanaError),
1861
    HourPosix(HourPosixError),
1862
    IncompleteMinutes,
1863
    IncompleteSeconds,
1864
    Minute(MinuteError),
1865
    OptionalSign(OptionalSignError),
1866
    Second(SecondError),
1867
}
1868
1869
impl core::fmt::Display for PosixTimeError {
1870
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1871
        use self::PosixTimeError::*;
1872
0
        match *self {
1873
0
            HourIana(ref err) => core::fmt::Display::fmt(err, f),
1874
0
            HourPosix(ref err) => core::fmt::Display::fmt(err, f),
1875
0
            IncompleteMinutes => f.write_str(
1876
0
                "incomplete time zone transition time in \
1877
0
                 POSIX time zone string (missing minutes)",
1878
            ),
1879
0
            IncompleteSeconds => f.write_str(
1880
0
                "incomplete time zone transition time in \
1881
0
                 POSIX time zone string (missing seconds)",
1882
            ),
1883
0
            Minute(ref err) => core::fmt::Display::fmt(err, f),
1884
0
            Second(ref err) => core::fmt::Display::fmt(err, f),
1885
0
            OptionalSign(ref err) => {
1886
0
                f.write_str(
1887
0
                    "failed to parse sign for time zone transition time",
1888
0
                )?;
1889
0
                core::fmt::Display::fmt(err, f)
1890
            }
1891
        }
1892
0
    }
1893
}
1894
1895
impl From<HourIanaError> for PosixTimeError {
1896
0
    fn from(err: HourIanaError) -> PosixTimeError {
1897
0
        PosixTimeError::HourIana(err)
1898
0
    }
1899
}
1900
1901
impl From<HourPosixError> for PosixTimeError {
1902
0
    fn from(err: HourPosixError) -> PosixTimeError {
1903
0
        PosixTimeError::HourPosix(err)
1904
0
    }
1905
}
1906
1907
impl From<MinuteError> for PosixTimeError {
1908
0
    fn from(err: MinuteError) -> PosixTimeError {
1909
0
        PosixTimeError::Minute(err)
1910
0
    }
1911
}
1912
1913
impl From<OptionalSignError> for PosixTimeError {
1914
0
    fn from(err: OptionalSignError) -> PosixTimeError {
1915
0
        PosixTimeError::OptionalSign(err)
1916
0
    }
1917
}
1918
1919
impl From<SecondError> for PosixTimeError {
1920
0
    fn from(err: SecondError) -> PosixTimeError {
1921
0
        PosixTimeError::Second(err)
1922
0
    }
1923
}
1924
1925
#[derive(Clone, Debug, Eq, PartialEq)]
1926
enum MonthError {
1927
    Parse(NumberError),
1928
    Range,
1929
}
1930
1931
impl core::fmt::Display for MonthError {
1932
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1933
        use self::MonthError::*;
1934
0
        match *self {
1935
0
            Parse(ref err) => {
1936
0
                f.write_str("invalid month digits: ")?;
1937
0
                core::fmt::Display::fmt(err, f)
1938
            }
1939
0
            Range => f.write_str(
1940
0
                "parsed month, but it's not in supported \
1941
0
                 range of `1..=12`",
1942
            ),
1943
        }
1944
0
    }
1945
}
1946
1947
#[derive(Clone, Debug, Eq, PartialEq)]
1948
enum WeekOfMonthError {
1949
    Parse(NumberError),
1950
    Range,
1951
}
1952
1953
impl core::fmt::Display for WeekOfMonthError {
1954
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1955
        use self::WeekOfMonthError::*;
1956
0
        match *self {
1957
0
            Parse(ref err) => {
1958
0
                f.write_str("invalid week-of-month digits: ")?;
1959
0
                core::fmt::Display::fmt(err, f)
1960
            }
1961
0
            Range => f.write_str(
1962
0
                "parsed week-of-month, but it's not in supported \
1963
0
                 range of `1..=5`",
1964
            ),
1965
        }
1966
0
    }
1967
}
1968
1969
#[derive(Clone, Debug, Eq, PartialEq)]
1970
enum WeekdayError {
1971
    Parse(NumberError),
1972
    Range,
1973
}
1974
1975
impl core::fmt::Display for WeekdayError {
1976
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1977
        use self::WeekdayError::*;
1978
0
        match *self {
1979
0
            Parse(ref err) => {
1980
0
                f.write_str("invalid weekday digits: ")?;
1981
0
                core::fmt::Display::fmt(err, f)
1982
            }
1983
0
            Range => f.write_str(
1984
0
                "parsed weekday, but it's not in supported \
1985
0
                 range of `0..=6` (with `0` corresponding to Sunday)",
1986
            ),
1987
        }
1988
0
    }
1989
}
1990
1991
#[derive(Clone, Debug, Eq, PartialEq)]
1992
enum HourIanaError {
1993
    Parse(NumberError),
1994
    Range,
1995
}
1996
1997
impl core::fmt::Display for HourIanaError {
1998
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
1999
        use self::HourIanaError::*;
2000
0
        match *self {
2001
0
            Parse(ref err) => {
2002
0
                f.write_str("invalid hour digits: ")?;
2003
0
                core::fmt::Display::fmt(err, f)
2004
            }
2005
0
            Range => f.write_str(
2006
0
                "parsed hours, but it's not in supported \
2007
0
                 range of `-167..=167`",
2008
            ),
2009
        }
2010
0
    }
2011
}
2012
2013
#[derive(Clone, Debug, Eq, PartialEq)]
2014
enum HourPosixError {
2015
    Parse(NumberError),
2016
    Range,
2017
}
2018
2019
impl core::fmt::Display for HourPosixError {
2020
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2021
        use self::HourPosixError::*;
2022
0
        match *self {
2023
0
            Parse(ref err) => {
2024
0
                f.write_str("invalid hour digits: ")?;
2025
0
                core::fmt::Display::fmt(err, f)
2026
            }
2027
0
            Range => f.write_str(
2028
0
                "parsed hours, but it's not in supported \
2029
0
                 range of `0..=24`",
2030
            ),
2031
        }
2032
0
    }
2033
}
2034
2035
#[derive(Clone, Debug, Eq, PartialEq)]
2036
enum MinuteError {
2037
    Parse(NumberError),
2038
    Range,
2039
}
2040
2041
impl core::fmt::Display for MinuteError {
2042
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2043
        use self::MinuteError::*;
2044
0
        match *self {
2045
0
            Parse(ref err) => {
2046
0
                f.write_str("invalid minute digits: ")?;
2047
0
                core::fmt::Display::fmt(err, f)
2048
            }
2049
0
            Range => f.write_str(
2050
0
                "parsed minutes, but it's not in supported \
2051
0
                 range of `0..=59`",
2052
            ),
2053
        }
2054
0
    }
2055
}
2056
2057
#[derive(Clone, Debug, Eq, PartialEq)]
2058
enum SecondError {
2059
    Parse(NumberError),
2060
    Range,
2061
}
2062
2063
impl core::fmt::Display for SecondError {
2064
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2065
        use self::SecondError::*;
2066
0
        match *self {
2067
0
            Parse(ref err) => {
2068
0
                f.write_str("invalid second digits: ")?;
2069
0
                core::fmt::Display::fmt(err, f)
2070
            }
2071
0
            Range => f.write_str(
2072
0
                "parsed seconds, but it's not in supported \
2073
0
                 range of `0..=59`",
2074
            ),
2075
        }
2076
0
    }
2077
}
2078
2079
#[derive(Clone, Debug, Eq, PartialEq)]
2080
enum NumberError {
2081
    Empty,
2082
    ExpectedLength,
2083
    InvalidDigit,
2084
    TooBig,
2085
}
2086
2087
impl core::fmt::Display for NumberError {
2088
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2089
        use self::NumberError::*;
2090
0
        match *self {
2091
0
            Empty => f.write_str("invalid number, no digits found"),
2092
0
            ExpectedLength => f.write_str(
2093
0
                "expected a fixed number of digits, \
2094
0
                 but found incorrect number",
2095
            ),
2096
0
            InvalidDigit => f.write_str("expected digit in range `0..=9`"),
2097
0
            TooBig => f.write_str(
2098
0
                "parsed number too big to fit into a 32-bit signed integer",
2099
            ),
2100
        }
2101
0
    }
2102
}
2103
2104
#[derive(Clone, Debug, Eq, PartialEq)]
2105
enum OptionalSignError {
2106
    ExpectedDigitAfterMinus,
2107
    ExpectedDigitAfterPlus,
2108
}
2109
2110
impl core::fmt::Display for OptionalSignError {
2111
0
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2112
        use self::OptionalSignError::*;
2113
0
        match *self {
2114
0
            ExpectedDigitAfterMinus => f.write_str(
2115
0
                "expected digit after `-` sign, \
2116
0
                 but got end of input",
2117
            ),
2118
0
            ExpectedDigitAfterPlus => f.write_str(
2119
0
                "expected digit after `+` sign, \
2120
0
                 but got end of input",
2121
            ),
2122
        }
2123
0
    }
2124
}
2125
2126
#[cfg(test)]
2127
mod 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
}