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