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::{parser, Error, DAYS_PER_WEEK, SECONDS_PER_DAY};
10
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    // should we pass NaiveDateTime all the way through to this fn?
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
146    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)]
158pub(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
169impl<'a> TimeZoneRef<'a> {
170    /// Find the local time type associated to the time zone at the specified Unix time in seconds
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
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
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
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
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)]
458pub(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
465impl Transition {
466    /// Construct a TZif file transition
467    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
472    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)]
479pub(super) struct LeapSecond {
480    /// Unix leap time
481    unix_leap_time: i64,
482    /// Leap second correction
483    correction: i32,
484}
485
486impl LeapSecond {
487    /// Construct a TZif file leap second
488    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
493    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)]
500struct TimeZoneName {
501    /// Length-prefixed string buffer
502    bytes: [u8; 8],
503}
504
505impl 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.
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
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
552    fn equal(&self, other: &Self) -> bool {
553        self.bytes == other.bytes
554    }
555}
556
557impl AsRef<str> for TimeZoneName {
558    fn as_ref(&self) -> &str {
559        // SAFETY: ASCII is valid UTF-8
560        unsafe { str::from_utf8_unchecked(self.as_bytes()) }
561    }
562}
563
564impl fmt::Debug for TimeZoneName {
565    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)]
572pub(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
581impl LocalTimeType {
582    /// Construct a local time type
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
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
606    pub(crate) const fn offset(&self) -> i32 {
607        self.ut_offset
608    }
609
610    /// Returns daylight saving time indicator
611    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
619fn 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")]
642fn 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")]
678fn 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")]
685fn 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)]
695const 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
699pub(crate) const SECONDS_PER_WEEK: i64 = SECONDS_PER_DAY * DAYS_PER_WEEK;
700/// Number of seconds in 28 days
701const SECONDS_PER_28_DAYS: i64 = SECONDS_PER_DAY * 28;
702
703#[cfg(test)]
704mod tests {
705    use super::super::Error;
706    use super::{LeapSecond, LocalTimeType, TimeZone, TimeZoneName, Transition, TransitionRule};
707
708    #[test]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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}