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