chrono/offset/local/tz_info/
timezone.rs

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