1 //! Types related to a time zone.
2 
3 use std::fs::{self, File};
4 use std::io::{self, Read};
5 use std::path::{Path, PathBuf};
6 use std::{cmp::Ordering, fmt, str};
7 
8 use super::rule::{AlternateTime, TransitionRule};
9 use super::{parser, Error, DAYS_PER_WEEK, SECONDS_PER_DAY};
10 
11 /// Time zone
12 #[derive(Debug, Clone, Eq, PartialEq)]
13 pub(crate) struct TimeZone {
14     /// List of transitions
15     transitions: Vec<Transition>,
16     /// List of local time types (cannot be empty)
17     local_time_types: Vec<LocalTimeType>,
18     /// List of leap seconds
19     leap_seconds: Vec<LeapSecond>,
20     /// Extra transition rule applicable after the last transition
21     extra_rule: Option<TransitionRule>,
22 }
23 
24 impl TimeZone {
25     /// Returns local time zone.
26     ///
27     /// This method in not supported on non-UNIX platforms, and returns the UTC time zone instead.
28     ///
local(env_tz: Option<&str>) -> Result<Self, Error>29     pub(crate) fn local(env_tz: Option<&str>) -> Result<Self, Error> {
30         match env_tz {
31             Some(tz) => Self::from_posix_tz(tz),
32             None => Self::from_posix_tz("localtime"),
33         }
34     }
35 
36     /// Construct a time zone from a POSIX TZ string, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html).
from_posix_tz(tz_string: &str) -> Result<Self, Error>37     fn from_posix_tz(tz_string: &str) -> Result<Self, Error> {
38         if tz_string.is_empty() {
39             return Err(Error::InvalidTzString("empty TZ string"));
40         }
41 
42         if tz_string == "localtime" {
43             return Self::from_tz_data(&fs::read("/etc/localtime")?);
44         }
45 
46         // attributes are not allowed on if blocks in Rust 1.38
47         #[cfg(target_os = "android")]
48         {
49             if let Ok(bytes) = android_tzdata::find_tz_data(tz_string) {
50                 return Self::from_tz_data(&bytes);
51             }
52         }
53 
54         let mut chars = tz_string.chars();
55         if chars.next() == Some(':') {
56             return Self::from_file(&mut find_tz_file(chars.as_str())?);
57         }
58 
59         if let Ok(mut file) = find_tz_file(tz_string) {
60             return Self::from_file(&mut file);
61         }
62 
63         // TZ string extensions are not allowed
64         let tz_string = tz_string.trim_matches(|c: char| c.is_ascii_whitespace());
65         let rule = TransitionRule::from_tz_string(tz_string.as_bytes(), false)?;
66         Self::new(
67             vec![],
68             match rule {
69                 TransitionRule::Fixed(local_time_type) => vec![local_time_type],
70                 TransitionRule::Alternate(AlternateTime { std, dst, .. }) => vec![std, dst],
71             },
72             vec![],
73             Some(rule),
74         )
75     }
76 
77     /// Construct a time zone
new( transitions: Vec<Transition>, local_time_types: Vec<LocalTimeType>, leap_seconds: Vec<LeapSecond>, extra_rule: Option<TransitionRule>, ) -> Result<Self, Error>78     pub(super) fn new(
79         transitions: Vec<Transition>,
80         local_time_types: Vec<LocalTimeType>,
81         leap_seconds: Vec<LeapSecond>,
82         extra_rule: Option<TransitionRule>,
83     ) -> Result<Self, Error> {
84         let new = Self { transitions, local_time_types, leap_seconds, extra_rule };
85         new.as_ref().validate()?;
86         Ok(new)
87     }
88 
89     /// Construct a time zone from the contents of a time zone file
from_file(file: &mut File) -> Result<Self, Error>90     fn from_file(file: &mut File) -> Result<Self, Error> {
91         let mut bytes = Vec::new();
92         file.read_to_end(&mut bytes)?;
93         Self::from_tz_data(&bytes)
94     }
95 
96     /// Construct a time zone from the contents of a time zone file
97     ///
98     /// Parse TZif data as described in [RFC 8536](https://datatracker.ietf.org/doc/html/rfc8536).
from_tz_data(bytes: &[u8]) -> Result<Self, Error>99     pub(crate) fn from_tz_data(bytes: &[u8]) -> Result<Self, Error> {
100         parser::parse(bytes)
101     }
102 
103     /// Construct a time zone with the specified UTC offset in seconds
fixed(ut_offset: i32) -> Result<Self, Error>104     fn fixed(ut_offset: i32) -> Result<Self, Error> {
105         Ok(Self {
106             transitions: Vec::new(),
107             local_time_types: vec![LocalTimeType::with_offset(ut_offset)?],
108             leap_seconds: Vec::new(),
109             extra_rule: None,
110         })
111     }
112 
113     /// Construct the time zone associated to UTC
utc() -> Self114     pub(crate) fn utc() -> Self {
115         Self {
116             transitions: Vec::new(),
117             local_time_types: vec![LocalTimeType::UTC],
118             leap_seconds: Vec::new(),
119             extra_rule: None,
120         }
121     }
122 
123     /// Find the local time type associated to the time zone at the specified Unix time in seconds
find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error>124     pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> {
125         self.as_ref().find_local_time_type(unix_time)
126     }
127 
128     // should we pass NaiveDateTime all the way through to this fn?
find_local_time_type_from_local( &self, local_time: i64, year: i32, ) -> Result<crate::LocalResult<LocalTimeType>, Error>129     pub(crate) fn find_local_time_type_from_local(
130         &self,
131         local_time: i64,
132         year: i32,
133     ) -> Result<crate::LocalResult<LocalTimeType>, Error> {
134         self.as_ref().find_local_time_type_from_local(local_time, year)
135     }
136 
137     /// Returns a reference to the time zone
as_ref(&self) -> TimeZoneRef138     fn as_ref(&self) -> TimeZoneRef {
139         TimeZoneRef {
140             transitions: &self.transitions,
141             local_time_types: &self.local_time_types,
142             leap_seconds: &self.leap_seconds,
143             extra_rule: &self.extra_rule,
144         }
145     }
146 }
147 
148 /// Reference to a time zone
149 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
150 pub(crate) struct TimeZoneRef<'a> {
151     /// List of transitions
152     transitions: &'a [Transition],
153     /// List of local time types (cannot be empty)
154     local_time_types: &'a [LocalTimeType],
155     /// List of leap seconds
156     leap_seconds: &'a [LeapSecond],
157     /// Extra transition rule applicable after the last transition
158     extra_rule: &'a Option<TransitionRule>,
159 }
160 
161 impl<'a> TimeZoneRef<'a> {
162     /// Find the local time type associated to the time zone at the specified Unix time in seconds
find_local_time_type(&self, unix_time: i64) -> Result<&'a LocalTimeType, Error>163     pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&'a LocalTimeType, Error> {
164         let extra_rule = match self.transitions.last() {
165             None => match self.extra_rule {
166                 Some(extra_rule) => extra_rule,
167                 None => return Ok(&self.local_time_types[0]),
168             },
169             Some(last_transition) => {
170                 let unix_leap_time = match self.unix_time_to_unix_leap_time(unix_time) {
171                     Ok(unix_leap_time) => unix_leap_time,
172                     Err(Error::OutOfRange(error)) => return Err(Error::FindLocalTimeType(error)),
173                     Err(err) => return Err(err),
174                 };
175 
176                 if unix_leap_time >= last_transition.unix_leap_time {
177                     match self.extra_rule {
178                         Some(extra_rule) => extra_rule,
179                         None => {
180                             // RFC 8536 3.2:
181                             // "Local time for timestamps on or after the last transition is
182                             // specified by the TZ string in the footer (Section 3.3) if present
183                             // and nonempty; otherwise, it is unspecified."
184                             //
185                             // Older versions of macOS (1.12 and before?) have TZif file with a
186                             // missing TZ string, and use the offset given by the last transition.
187                             return Ok(
188                                 &self.local_time_types[last_transition.local_time_type_index]
189                             );
190                         }
191                     }
192                 } else {
193                     let index = match self
194                         .transitions
195                         .binary_search_by_key(&unix_leap_time, Transition::unix_leap_time)
196                     {
197                         Ok(x) => x + 1,
198                         Err(x) => x,
199                     };
200 
201                     let local_time_type_index = if index > 0 {
202                         self.transitions[index - 1].local_time_type_index
203                     } else {
204                         0
205                     };
206                     return Ok(&self.local_time_types[local_time_type_index]);
207                 }
208             }
209         };
210 
211         match extra_rule.find_local_time_type(unix_time) {
212             Ok(local_time_type) => Ok(local_time_type),
213             Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)),
214             err => err,
215         }
216     }
217 
find_local_time_type_from_local( &self, local_time: i64, year: i32, ) -> Result<crate::LocalResult<LocalTimeType>, Error>218     pub(crate) fn find_local_time_type_from_local(
219         &self,
220         local_time: i64,
221         year: i32,
222     ) -> Result<crate::LocalResult<LocalTimeType>, Error> {
223         // #TODO: this is wrong as we need 'local_time_to_local_leap_time ?
224         // but ... does the local time even include leap seconds ??
225         // let unix_leap_time = match self.unix_time_to_unix_leap_time(local_time) {
226         //     Ok(unix_leap_time) => unix_leap_time,
227         //     Err(Error::OutOfRange(error)) => return Err(Error::FindLocalTimeType(error)),
228         //     Err(err) => return Err(err),
229         // };
230         let local_leap_time = local_time;
231 
232         // if we have at least one transition,
233         // we must check _all_ of them, incase of any Overlapping (LocalResult::Ambiguous) or Skipping (LocalResult::None) transitions
234         let offset_after_last = if !self.transitions.is_empty() {
235             let mut prev = self.local_time_types[0];
236 
237             for transition in self.transitions {
238                 let after_ltt = self.local_time_types[transition.local_time_type_index];
239 
240                 // the end and start here refers to where the time starts prior to the transition
241                 // and where it ends up after. not the temporal relationship.
242                 let transition_end = transition.unix_leap_time + i64::from(after_ltt.ut_offset);
243                 let transition_start = transition.unix_leap_time + i64::from(prev.ut_offset);
244 
245                 match transition_start.cmp(&transition_end) {
246                     Ordering::Greater => {
247                         // bakwards transition, eg from DST to regular
248                         // this means a given local time could have one of two possible offsets
249                         if local_leap_time < transition_end {
250                             return Ok(crate::LocalResult::Single(prev));
251                         } else if local_leap_time >= transition_end
252                             && local_leap_time <= transition_start
253                         {
254                             if prev.ut_offset < after_ltt.ut_offset {
255                                 return Ok(crate::LocalResult::Ambiguous(prev, after_ltt));
256                             } else {
257                                 return Ok(crate::LocalResult::Ambiguous(after_ltt, prev));
258                             }
259                         }
260                     }
261                     Ordering::Equal => {
262                         // should this ever happen? presumably we have to handle it anyway.
263                         if local_leap_time < transition_start {
264                             return Ok(crate::LocalResult::Single(prev));
265                         } else if local_leap_time == transition_end {
266                             if prev.ut_offset < after_ltt.ut_offset {
267                                 return Ok(crate::LocalResult::Ambiguous(prev, after_ltt));
268                             } else {
269                                 return Ok(crate::LocalResult::Ambiguous(after_ltt, prev));
270                             }
271                         }
272                     }
273                     Ordering::Less => {
274                         // forwards transition, eg from regular to DST
275                         // this means that times that are skipped are invalid local times
276                         if local_leap_time <= transition_start {
277                             return Ok(crate::LocalResult::Single(prev));
278                         } else if local_leap_time < transition_end {
279                             return Ok(crate::LocalResult::None);
280                         } else if local_leap_time == transition_end {
281                             return Ok(crate::LocalResult::Single(after_ltt));
282                         }
283                     }
284                 }
285 
286                 // try the next transition, we are fully after this one
287                 prev = after_ltt;
288             }
289 
290             prev
291         } else {
292             self.local_time_types[0]
293         };
294 
295         if let Some(extra_rule) = self.extra_rule {
296             match extra_rule.find_local_time_type_from_local(local_time, year) {
297                 Ok(local_time_type) => Ok(local_time_type),
298                 Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)),
299                 err => err,
300             }
301         } else {
302             Ok(crate::LocalResult::Single(offset_after_last))
303         }
304     }
305 
306     /// Check time zone inputs
validate(&self) -> Result<(), Error>307     fn validate(&self) -> Result<(), Error> {
308         // Check local time types
309         let local_time_types_size = self.local_time_types.len();
310         if local_time_types_size == 0 {
311             return Err(Error::TimeZone("list of local time types must not be empty"));
312         }
313 
314         // Check transitions
315         let mut i_transition = 0;
316         while i_transition < self.transitions.len() {
317             if self.transitions[i_transition].local_time_type_index >= local_time_types_size {
318                 return Err(Error::TimeZone("invalid local time type index"));
319             }
320 
321             if i_transition + 1 < self.transitions.len()
322                 && self.transitions[i_transition].unix_leap_time
323                     >= self.transitions[i_transition + 1].unix_leap_time
324             {
325                 return Err(Error::TimeZone("invalid transition"));
326             }
327 
328             i_transition += 1;
329         }
330 
331         // Check leap seconds
332         if !(self.leap_seconds.is_empty()
333             || self.leap_seconds[0].unix_leap_time >= 0
334                 && self.leap_seconds[0].correction.saturating_abs() == 1)
335         {
336             return Err(Error::TimeZone("invalid leap second"));
337         }
338 
339         let min_interval = SECONDS_PER_28_DAYS - 1;
340 
341         let mut i_leap_second = 0;
342         while i_leap_second < self.leap_seconds.len() {
343             if i_leap_second + 1 < self.leap_seconds.len() {
344                 let x0 = &self.leap_seconds[i_leap_second];
345                 let x1 = &self.leap_seconds[i_leap_second + 1];
346 
347                 let diff_unix_leap_time = x1.unix_leap_time.saturating_sub(x0.unix_leap_time);
348                 let abs_diff_correction =
349                     x1.correction.saturating_sub(x0.correction).saturating_abs();
350 
351                 if !(diff_unix_leap_time >= min_interval && abs_diff_correction == 1) {
352                     return Err(Error::TimeZone("invalid leap second"));
353                 }
354             }
355             i_leap_second += 1;
356         }
357 
358         // Check extra rule
359         let (extra_rule, last_transition) = match (&self.extra_rule, self.transitions.last()) {
360             (Some(rule), Some(trans)) => (rule, trans),
361             _ => return Ok(()),
362         };
363 
364         let last_local_time_type = &self.local_time_types[last_transition.local_time_type_index];
365         let unix_time = match self.unix_leap_time_to_unix_time(last_transition.unix_leap_time) {
366             Ok(unix_time) => unix_time,
367             Err(Error::OutOfRange(error)) => return Err(Error::TimeZone(error)),
368             Err(err) => return Err(err),
369         };
370 
371         let rule_local_time_type = match extra_rule.find_local_time_type(unix_time) {
372             Ok(rule_local_time_type) => rule_local_time_type,
373             Err(Error::OutOfRange(error)) => return Err(Error::TimeZone(error)),
374             Err(err) => return Err(err),
375         };
376 
377         let check = last_local_time_type.ut_offset == rule_local_time_type.ut_offset
378             && last_local_time_type.is_dst == rule_local_time_type.is_dst
379             && match (&last_local_time_type.name, &rule_local_time_type.name) {
380                 (Some(x), Some(y)) => x.equal(y),
381                 (None, None) => true,
382                 _ => false,
383             };
384 
385         if !check {
386             return Err(Error::TimeZone(
387                 "extra transition rule is inconsistent with the last transition",
388             ));
389         }
390 
391         Ok(())
392     }
393 
394     /// Convert Unix time to Unix leap time, from the list of leap seconds in a time zone
unix_time_to_unix_leap_time(&self, unix_time: i64) -> Result<i64, Error>395     const fn unix_time_to_unix_leap_time(&self, unix_time: i64) -> Result<i64, Error> {
396         let mut unix_leap_time = unix_time;
397 
398         let mut i = 0;
399         while i < self.leap_seconds.len() {
400             let leap_second = &self.leap_seconds[i];
401 
402             if unix_leap_time < leap_second.unix_leap_time {
403                 break;
404             }
405 
406             unix_leap_time = match unix_time.checked_add(leap_second.correction as i64) {
407                 Some(unix_leap_time) => unix_leap_time,
408                 None => return Err(Error::OutOfRange("out of range operation")),
409             };
410 
411             i += 1;
412         }
413 
414         Ok(unix_leap_time)
415     }
416 
417     /// Convert Unix leap time to Unix time, from the list of leap seconds in a time zone
unix_leap_time_to_unix_time(&self, unix_leap_time: i64) -> Result<i64, Error>418     fn unix_leap_time_to_unix_time(&self, unix_leap_time: i64) -> Result<i64, Error> {
419         if unix_leap_time == i64::min_value() {
420             return Err(Error::OutOfRange("out of range operation"));
421         }
422 
423         let index = match self
424             .leap_seconds
425             .binary_search_by_key(&(unix_leap_time - 1), LeapSecond::unix_leap_time)
426         {
427             Ok(x) => x + 1,
428             Err(x) => x,
429         };
430 
431         let correction = if index > 0 { self.leap_seconds[index - 1].correction } else { 0 };
432 
433         match unix_leap_time.checked_sub(correction as i64) {
434             Some(unix_time) => Ok(unix_time),
435             None => Err(Error::OutOfRange("out of range operation")),
436         }
437     }
438 
439     /// The UTC time zone
440     const UTC: TimeZoneRef<'static> = TimeZoneRef {
441         transitions: &[],
442         local_time_types: &[LocalTimeType::UTC],
443         leap_seconds: &[],
444         extra_rule: &None,
445     };
446 }
447 
448 /// Transition of a TZif file
449 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
450 pub(super) struct Transition {
451     /// Unix leap time
452     unix_leap_time: i64,
453     /// Index specifying the local time type of the transition
454     local_time_type_index: usize,
455 }
456 
457 impl Transition {
458     /// Construct a TZif file transition
new(unix_leap_time: i64, local_time_type_index: usize) -> Self459     pub(super) const fn new(unix_leap_time: i64, local_time_type_index: usize) -> Self {
460         Self { unix_leap_time, local_time_type_index }
461     }
462 
463     /// Returns Unix leap time
unix_leap_time(&self) -> i64464     const fn unix_leap_time(&self) -> i64 {
465         self.unix_leap_time
466     }
467 }
468 
469 /// Leap second of a TZif file
470 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
471 pub(super) struct LeapSecond {
472     /// Unix leap time
473     unix_leap_time: i64,
474     /// Leap second correction
475     correction: i32,
476 }
477 
478 impl LeapSecond {
479     /// Construct a TZif file leap second
new(unix_leap_time: i64, correction: i32) -> Self480     pub(super) const fn new(unix_leap_time: i64, correction: i32) -> Self {
481         Self { unix_leap_time, correction }
482     }
483 
484     /// Returns Unix leap time
unix_leap_time(&self) -> i64485     const fn unix_leap_time(&self) -> i64 {
486         self.unix_leap_time
487     }
488 }
489 
490 /// ASCII-encoded fixed-capacity string, used for storing time zone names
491 #[derive(Copy, Clone, Eq, PartialEq)]
492 struct TimeZoneName {
493     /// Length-prefixed string buffer
494     bytes: [u8; 8],
495 }
496 
497 impl TimeZoneName {
498     /// Construct a time zone name
499     ///
500     /// man tzfile(5):
501     /// Time zone designations should consist of at least three (3) and no more than six (6) ASCII
502     /// characters from the set of alphanumerics, “-”, and “+”. This is for compatibility with
503     /// POSIX requirements for time zone abbreviations.
new(input: &[u8]) -> Result<Self, Error>504     fn new(input: &[u8]) -> Result<Self, Error> {
505         let len = input.len();
506 
507         if !(3..=7).contains(&len) {
508             return Err(Error::LocalTimeType(
509                 "time zone name must have between 3 and 7 characters",
510             ));
511         }
512 
513         let mut bytes = [0; 8];
514         bytes[0] = input.len() as u8;
515 
516         let mut i = 0;
517         while i < len {
518             let b = input[i];
519             match b {
520                 b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-' => {}
521                 _ => return Err(Error::LocalTimeType("invalid characters in time zone name")),
522             }
523 
524             bytes[i + 1] = b;
525             i += 1;
526         }
527 
528         Ok(Self { bytes })
529     }
530 
531     /// Returns time zone name as a byte slice
as_bytes(&self) -> &[u8]532     fn as_bytes(&self) -> &[u8] {
533         match self.bytes[0] {
534             3 => &self.bytes[1..4],
535             4 => &self.bytes[1..5],
536             5 => &self.bytes[1..6],
537             6 => &self.bytes[1..7],
538             7 => &self.bytes[1..8],
539             _ => unreachable!(),
540         }
541     }
542 
543     /// Check if two time zone names are equal
equal(&self, other: &Self) -> bool544     fn equal(&self, other: &Self) -> bool {
545         self.bytes == other.bytes
546     }
547 }
548 
549 impl AsRef<str> for TimeZoneName {
as_ref(&self) -> &str550     fn as_ref(&self) -> &str {
551         // SAFETY: ASCII is valid UTF-8
552         unsafe { str::from_utf8_unchecked(self.as_bytes()) }
553     }
554 }
555 
556 impl fmt::Debug for TimeZoneName {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result557     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
558         self.as_ref().fmt(f)
559     }
560 }
561 
562 /// Local time type associated to a time zone
563 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
564 pub(crate) struct LocalTimeType {
565     /// Offset from UTC in seconds
566     pub(super) ut_offset: i32,
567     /// Daylight Saving Time indicator
568     is_dst: bool,
569     /// Time zone name
570     name: Option<TimeZoneName>,
571 }
572 
573 impl LocalTimeType {
574     /// Construct a local time type
new(ut_offset: i32, is_dst: bool, name: Option<&[u8]>) -> Result<Self, Error>575     pub(super) fn new(ut_offset: i32, is_dst: bool, name: Option<&[u8]>) -> Result<Self, Error> {
576         if ut_offset == i32::min_value() {
577             return Err(Error::LocalTimeType("invalid UTC offset"));
578         }
579 
580         let name = match name {
581             Some(name) => TimeZoneName::new(name)?,
582             None => return Ok(Self { ut_offset, is_dst, name: None }),
583         };
584 
585         Ok(Self { ut_offset, is_dst, name: Some(name) })
586     }
587 
588     /// Construct a local time type with the specified UTC offset in seconds
with_offset(ut_offset: i32) -> Result<Self, Error>589     pub(super) const fn with_offset(ut_offset: i32) -> Result<Self, Error> {
590         if ut_offset == i32::min_value() {
591             return Err(Error::LocalTimeType("invalid UTC offset"));
592         }
593 
594         Ok(Self { ut_offset, is_dst: false, name: None })
595     }
596 
597     /// Returns offset from UTC in seconds
offset(&self) -> i32598     pub(crate) const fn offset(&self) -> i32 {
599         self.ut_offset
600     }
601 
602     /// Returns daylight saving time indicator
is_dst(&self) -> bool603     pub(super) const fn is_dst(&self) -> bool {
604         self.is_dst
605     }
606 
607     pub(super) const UTC: LocalTimeType = Self { ut_offset: 0, is_dst: false, name: None };
608 }
609 
610 /// Open the TZif file corresponding to a TZ string
find_tz_file(path: impl AsRef<Path>) -> Result<File, Error>611 fn find_tz_file(path: impl AsRef<Path>) -> Result<File, Error> {
612     // Don't check system timezone directories on non-UNIX platforms
613     #[cfg(not(unix))]
614     return Ok(File::open(path)?);
615 
616     #[cfg(unix)]
617     {
618         let path = path.as_ref();
619         if path.is_absolute() {
620             return Ok(File::open(path)?);
621         }
622 
623         for folder in &ZONE_INFO_DIRECTORIES {
624             if let Ok(file) = File::open(PathBuf::from(folder).join(path)) {
625                 return Ok(file);
626             }
627         }
628 
629         Err(Error::Io(io::ErrorKind::NotFound.into()))
630     }
631 }
632 
633 // Possible system timezone directories
634 #[cfg(unix)]
635 const ZONE_INFO_DIRECTORIES: [&str; 4] =
636     ["/usr/share/zoneinfo", "/share/zoneinfo", "/etc/zoneinfo", "/usr/share/lib/zoneinfo"];
637 
638 /// Number of seconds in one week
639 pub(crate) const SECONDS_PER_WEEK: i64 = SECONDS_PER_DAY * DAYS_PER_WEEK;
640 /// Number of seconds in 28 days
641 const SECONDS_PER_28_DAYS: i64 = SECONDS_PER_DAY * 28;
642 
643 #[cfg(test)]
644 mod tests {
645     use super::super::Error;
646     use super::{LeapSecond, LocalTimeType, TimeZone, TimeZoneName, Transition, TransitionRule};
647 
648     #[test]
test_no_dst() -> Result<(), Error>649     fn test_no_dst() -> Result<(), Error> {
650         let tz_string = b"HST10";
651         let transition_rule = TransitionRule::from_tz_string(tz_string, false)?;
652         assert_eq!(transition_rule, LocalTimeType::new(-36000, false, Some(b"HST"))?.into());
653         Ok(())
654     }
655 
656     #[test]
test_error() -> Result<(), Error>657     fn test_error() -> Result<(), Error> {
658         assert!(matches!(
659             TransitionRule::from_tz_string(b"IST-1GMT0", false),
660             Err(Error::UnsupportedTzString(_))
661         ));
662         assert!(matches!(
663             TransitionRule::from_tz_string(b"EET-2EEST", false),
664             Err(Error::UnsupportedTzString(_))
665         ));
666 
667         Ok(())
668     }
669 
670     #[test]
test_v1_file_with_leap_seconds() -> Result<(), Error>671     fn test_v1_file_with_leap_seconds() -> Result<(), Error> {
672         let bytes = b"TZif\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\x1b\0\0\0\0\0\0\0\x01\0\0\0\x04\0\0\0\0\0\0UTC\0\x04\xb2\x58\0\0\0\0\x01\x05\xa4\xec\x01\0\0\0\x02\x07\x86\x1f\x82\0\0\0\x03\x09\x67\x53\x03\0\0\0\x04\x0b\x48\x86\x84\0\0\0\x05\x0d\x2b\x0b\x85\0\0\0\x06\x0f\x0c\x3f\x06\0\0\0\x07\x10\xed\x72\x87\0\0\0\x08\x12\xce\xa6\x08\0\0\0\x09\x15\x9f\xca\x89\0\0\0\x0a\x17\x80\xfe\x0a\0\0\0\x0b\x19\x62\x31\x8b\0\0\0\x0c\x1d\x25\xea\x0c\0\0\0\x0d\x21\xda\xe5\x0d\0\0\0\x0e\x25\x9e\x9d\x8e\0\0\0\x0f\x27\x7f\xd1\x0f\0\0\0\x10\x2a\x50\xf5\x90\0\0\0\x11\x2c\x32\x29\x11\0\0\0\x12\x2e\x13\x5c\x92\0\0\0\x13\x30\xe7\x24\x13\0\0\0\x14\x33\xb8\x48\x94\0\0\0\x15\x36\x8c\x10\x15\0\0\0\x16\x43\xb7\x1b\x96\0\0\0\x17\x49\x5c\x07\x97\0\0\0\x18\x4f\xef\x93\x18\0\0\0\x19\x55\x93\x2d\x99\0\0\0\x1a\x58\x68\x46\x9a\0\0\0\x1b\0\0";
673 
674         let time_zone = TimeZone::from_tz_data(bytes)?;
675 
676         let time_zone_result = TimeZone::new(
677             Vec::new(),
678             vec![LocalTimeType::new(0, false, Some(b"UTC"))?],
679             vec![
680                 LeapSecond::new(78796800, 1),
681                 LeapSecond::new(94694401, 2),
682                 LeapSecond::new(126230402, 3),
683                 LeapSecond::new(157766403, 4),
684                 LeapSecond::new(189302404, 5),
685                 LeapSecond::new(220924805, 6),
686                 LeapSecond::new(252460806, 7),
687                 LeapSecond::new(283996807, 8),
688                 LeapSecond::new(315532808, 9),
689                 LeapSecond::new(362793609, 10),
690                 LeapSecond::new(394329610, 11),
691                 LeapSecond::new(425865611, 12),
692                 LeapSecond::new(489024012, 13),
693                 LeapSecond::new(567993613, 14),
694                 LeapSecond::new(631152014, 15),
695                 LeapSecond::new(662688015, 16),
696                 LeapSecond::new(709948816, 17),
697                 LeapSecond::new(741484817, 18),
698                 LeapSecond::new(773020818, 19),
699                 LeapSecond::new(820454419, 20),
700                 LeapSecond::new(867715220, 21),
701                 LeapSecond::new(915148821, 22),
702                 LeapSecond::new(1136073622, 23),
703                 LeapSecond::new(1230768023, 24),
704                 LeapSecond::new(1341100824, 25),
705                 LeapSecond::new(1435708825, 26),
706                 LeapSecond::new(1483228826, 27),
707             ],
708             None,
709         )?;
710 
711         assert_eq!(time_zone, time_zone_result);
712 
713         Ok(())
714     }
715 
716     #[test]
test_v2_file() -> Result<(), Error>717     fn test_v2_file() -> Result<(), Error> {
718         let bytes = b"TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x06\0\0\0\x06\0\0\0\0\0\0\0\x07\0\0\0\x06\0\0\0\x14\x80\0\0\0\xbb\x05\x43\x48\xbb\x21\x71\x58\xcb\x89\x3d\xc8\xd2\x23\xf4\x70\xd2\x61\x49\x38\xd5\x8d\x73\x48\x01\x02\x01\x03\x04\x01\x05\xff\xff\x6c\x02\0\0\xff\xff\x6c\x58\0\x04\xff\xff\x7a\x68\x01\x08\xff\xff\x7a\x68\x01\x0c\xff\xff\x7a\x68\x01\x10\xff\xff\x73\x60\0\x04LMT\0HST\0HDT\0HWT\0HPT\0\0\0\0\0\x01\0\0\0\0\0\x01\0TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x06\0\0\0\x06\0\0\0\0\0\0\0\x07\0\0\0\x06\0\0\0\x14\xff\xff\xff\xff\x74\xe0\x70\xbe\xff\xff\xff\xff\xbb\x05\x43\x48\xff\xff\xff\xff\xbb\x21\x71\x58\xff\xff\xff\xff\xcb\x89\x3d\xc8\xff\xff\xff\xff\xd2\x23\xf4\x70\xff\xff\xff\xff\xd2\x61\x49\x38\xff\xff\xff\xff\xd5\x8d\x73\x48\x01\x02\x01\x03\x04\x01\x05\xff\xff\x6c\x02\0\0\xff\xff\x6c\x58\0\x04\xff\xff\x7a\x68\x01\x08\xff\xff\x7a\x68\x01\x0c\xff\xff\x7a\x68\x01\x10\xff\xff\x73\x60\0\x04LMT\0HST\0HDT\0HWT\0HPT\0\0\0\0\0\x01\0\0\0\0\0\x01\0\x0aHST10\x0a";
719 
720         let time_zone = TimeZone::from_tz_data(bytes)?;
721 
722         let time_zone_result = TimeZone::new(
723             vec![
724                 Transition::new(-2334101314, 1),
725                 Transition::new(-1157283000, 2),
726                 Transition::new(-1155436200, 1),
727                 Transition::new(-880198200, 3),
728                 Transition::new(-769395600, 4),
729                 Transition::new(-765376200, 1),
730                 Transition::new(-712150200, 5),
731             ],
732             vec![
733                 LocalTimeType::new(-37886, false, Some(b"LMT"))?,
734                 LocalTimeType::new(-37800, false, Some(b"HST"))?,
735                 LocalTimeType::new(-34200, true, Some(b"HDT"))?,
736                 LocalTimeType::new(-34200, true, Some(b"HWT"))?,
737                 LocalTimeType::new(-34200, true, Some(b"HPT"))?,
738                 LocalTimeType::new(-36000, false, Some(b"HST"))?,
739             ],
740             Vec::new(),
741             Some(TransitionRule::from(LocalTimeType::new(-36000, false, Some(b"HST"))?)),
742         )?;
743 
744         assert_eq!(time_zone, time_zone_result);
745 
746         assert_eq!(
747             *time_zone.find_local_time_type(-1156939200)?,
748             LocalTimeType::new(-34200, true, Some(b"HDT"))?
749         );
750         assert_eq!(
751             *time_zone.find_local_time_type(1546300800)?,
752             LocalTimeType::new(-36000, false, Some(b"HST"))?
753         );
754 
755         Ok(())
756     }
757 
758     #[test]
test_no_tz_string() -> Result<(), Error>759     fn test_no_tz_string() -> Result<(), Error> {
760         // Guayaquil from macOS 10.11
761         let bytes = b"TZif\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x02\0\0\0\x02\0\0\0\0\0\0\0\x01\0\0\0\x02\0\0\0\x08\xb6\xa4B\x18\x01\xff\xff\xb6h\0\0\xff\xff\xb9\xb0\0\x04QMT\0ECT\0\0\0\0\0";
762 
763         let time_zone = TimeZone::from_tz_data(bytes)?;
764         dbg!(&time_zone);
765 
766         let time_zone_result = TimeZone::new(
767             vec![Transition::new(-1230749160, 1)],
768             vec![
769                 LocalTimeType::new(-18840, false, Some(b"QMT"))?,
770                 LocalTimeType::new(-18000, false, Some(b"ECT"))?,
771             ],
772             Vec::new(),
773             None,
774         )?;
775 
776         assert_eq!(time_zone, time_zone_result);
777 
778         assert_eq!(
779             *time_zone.find_local_time_type(-1500000000)?,
780             LocalTimeType::new(-18840, false, Some(b"QMT"))?
781         );
782         assert_eq!(
783             *time_zone.find_local_time_type(0)?,
784             LocalTimeType::new(-18000, false, Some(b"ECT"))?
785         );
786 
787         Ok(())
788     }
789 
790     #[test]
test_tz_ascii_str() -> Result<(), Error>791     fn test_tz_ascii_str() -> Result<(), Error> {
792         assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_))));
793         assert!(matches!(TimeZoneName::new(b"A"), Err(Error::LocalTimeType(_))));
794         assert!(matches!(TimeZoneName::new(b"AB"), Err(Error::LocalTimeType(_))));
795         assert_eq!(TimeZoneName::new(b"CET")?.as_bytes(), b"CET");
796         assert_eq!(TimeZoneName::new(b"CHADT")?.as_bytes(), b"CHADT");
797         assert_eq!(TimeZoneName::new(b"abcdefg")?.as_bytes(), b"abcdefg");
798         assert_eq!(TimeZoneName::new(b"UTC+02")?.as_bytes(), b"UTC+02");
799         assert_eq!(TimeZoneName::new(b"-1230")?.as_bytes(), b"-1230");
800         assert!(matches!(TimeZoneName::new("−0330".as_bytes()), Err(Error::LocalTimeType(_)))); // MINUS SIGN (U+2212)
801         assert!(matches!(TimeZoneName::new(b"\x00123"), Err(Error::LocalTimeType(_))));
802         assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_))));
803         assert!(matches!(TimeZoneName::new(b"GMT\0\0\0"), Err(Error::LocalTimeType(_))));
804 
805         Ok(())
806     }
807 
808     #[test]
test_time_zone() -> Result<(), Error>809     fn test_time_zone() -> Result<(), Error> {
810         let utc = LocalTimeType::UTC;
811         let cet = LocalTimeType::with_offset(3600)?;
812 
813         let utc_local_time_types = vec![utc];
814         let fixed_extra_rule = TransitionRule::from(cet);
815 
816         let time_zone_1 = TimeZone::new(vec![], utc_local_time_types.clone(), vec![], None)?;
817         let time_zone_2 =
818             TimeZone::new(vec![], utc_local_time_types.clone(), vec![], Some(fixed_extra_rule))?;
819         let time_zone_3 =
820             TimeZone::new(vec![Transition::new(0, 0)], utc_local_time_types.clone(), vec![], None)?;
821         let time_zone_4 = TimeZone::new(
822             vec![Transition::new(i32::min_value().into(), 0), Transition::new(0, 1)],
823             vec![utc, cet],
824             Vec::new(),
825             Some(fixed_extra_rule),
826         )?;
827 
828         assert_eq!(*time_zone_1.find_local_time_type(0)?, utc);
829         assert_eq!(*time_zone_2.find_local_time_type(0)?, cet);
830 
831         assert_eq!(*time_zone_3.find_local_time_type(-1)?, utc);
832         assert_eq!(*time_zone_3.find_local_time_type(0)?, utc);
833 
834         assert_eq!(*time_zone_4.find_local_time_type(-1)?, utc);
835         assert_eq!(*time_zone_4.find_local_time_type(0)?, cet);
836 
837         let time_zone_err = TimeZone::new(
838             vec![Transition::new(0, 0)],
839             utc_local_time_types,
840             vec![],
841             Some(fixed_extra_rule),
842         );
843         assert!(time_zone_err.is_err());
844 
845         Ok(())
846     }
847 
848     #[test]
test_time_zone_from_posix_tz() -> Result<(), Error>849     fn test_time_zone_from_posix_tz() -> Result<(), Error> {
850         #[cfg(unix)]
851         {
852             // if the TZ var is set, this essentially _overrides_ the
853             // time set by the localtime symlink
854             // so just ensure that ::local() acts as expected
855             // in this case
856             if let Ok(tz) = std::env::var("TZ") {
857                 let time_zone_local = TimeZone::local(Some(tz.as_str()))?;
858                 let time_zone_local_1 = TimeZone::from_posix_tz(&tz)?;
859                 assert_eq!(time_zone_local, time_zone_local_1);
860             }
861 
862             // `TimeZone::from_posix_tz("UTC")` will return `Error` if the environment does not have
863             // a time zone database, like for example some docker containers.
864             // In that case skip the test.
865             if let Ok(time_zone_utc) = TimeZone::from_posix_tz("UTC") {
866                 assert_eq!(time_zone_utc.find_local_time_type(0)?.offset(), 0);
867             }
868         }
869 
870         assert!(TimeZone::from_posix_tz("EST5EDT,0/0,J365/25").is_err());
871         assert!(TimeZone::from_posix_tz("").is_err());
872 
873         Ok(())
874     }
875 
876     #[test]
test_leap_seconds() -> Result<(), Error>877     fn test_leap_seconds() -> Result<(), Error> {
878         let time_zone = TimeZone::new(
879             Vec::new(),
880             vec![LocalTimeType::new(0, false, Some(b"UTC"))?],
881             vec![
882                 LeapSecond::new(78796800, 1),
883                 LeapSecond::new(94694401, 2),
884                 LeapSecond::new(126230402, 3),
885                 LeapSecond::new(157766403, 4),
886                 LeapSecond::new(189302404, 5),
887                 LeapSecond::new(220924805, 6),
888                 LeapSecond::new(252460806, 7),
889                 LeapSecond::new(283996807, 8),
890                 LeapSecond::new(315532808, 9),
891                 LeapSecond::new(362793609, 10),
892                 LeapSecond::new(394329610, 11),
893                 LeapSecond::new(425865611, 12),
894                 LeapSecond::new(489024012, 13),
895                 LeapSecond::new(567993613, 14),
896                 LeapSecond::new(631152014, 15),
897                 LeapSecond::new(662688015, 16),
898                 LeapSecond::new(709948816, 17),
899                 LeapSecond::new(741484817, 18),
900                 LeapSecond::new(773020818, 19),
901                 LeapSecond::new(820454419, 20),
902                 LeapSecond::new(867715220, 21),
903                 LeapSecond::new(915148821, 22),
904                 LeapSecond::new(1136073622, 23),
905                 LeapSecond::new(1230768023, 24),
906                 LeapSecond::new(1341100824, 25),
907                 LeapSecond::new(1435708825, 26),
908                 LeapSecond::new(1483228826, 27),
909             ],
910             None,
911         )?;
912 
913         let time_zone_ref = time_zone.as_ref();
914 
915         assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073621), Ok(1136073599)));
916         assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073622), Ok(1136073600)));
917         assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073623), Ok(1136073600)));
918         assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073624), Ok(1136073601)));
919 
920         assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073599), Ok(1136073621)));
921         assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073600), Ok(1136073623)));
922         assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073601), Ok(1136073624)));
923 
924         Ok(())
925     }
926 
927     #[test]
test_leap_seconds_overflow() -> Result<(), Error>928     fn test_leap_seconds_overflow() -> Result<(), Error> {
929         let time_zone_err = TimeZone::new(
930             vec![Transition::new(i64::min_value(), 0)],
931             vec![LocalTimeType::UTC],
932             vec![LeapSecond::new(0, 1)],
933             Some(TransitionRule::from(LocalTimeType::UTC)),
934         );
935         assert!(time_zone_err.is_err());
936 
937         let time_zone = TimeZone::new(
938             vec![Transition::new(i64::max_value(), 0)],
939             vec![LocalTimeType::UTC],
940             vec![LeapSecond::new(0, 1)],
941             None,
942         )?;
943         assert!(matches!(
944             time_zone.find_local_time_type(i64::max_value()),
945             Err(Error::FindLocalTimeType(_))
946         ));
947 
948         Ok(())
949     }
950 }
951