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