1use 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#[derive(Debug, Clone, Eq, PartialEq)]
13pub(crate) struct TimeZone {
14 transitions: Vec<Transition>,
16 local_time_types: Vec<LocalTimeType>,
18 leap_seconds: Vec<LeapSecond>,
20 extra_rule: Option<TransitionRule>,
22}
23
24impl TimeZone {
25 pub(crate) fn local(env_tz: Option<&str>) -> Result<Self, Error> {
29 match env_tz {
30 Some(tz) => Self::from_posix_tz(tz),
31 None => Self::from_posix_tz("localtime"),
32 }
33 }
34
35 fn from_posix_tz(tz_string: &str) -> Result<Self, Error> {
37 if tz_string.is_empty() {
38 return Err(Error::InvalidTzString("empty TZ string"));
39 }
40
41 if tz_string == "localtime" {
42 return Self::from_tz_data(&fs::read("/etc/localtime")?);
43 }
44
45 #[cfg(target_os = "android")]
47 {
48 if let Ok(bytes) = android_tzdata::find_tz_data(tz_string) {
49 return Self::from_tz_data(&bytes);
50 }
51 }
52
53 let mut chars = tz_string.chars();
54 if chars.next() == Some(':') {
55 return Self::from_file(&mut find_tz_file(chars.as_str())?);
56 }
57
58 if let Ok(mut file) = find_tz_file(tz_string) {
59 return Self::from_file(&mut file);
60 }
61
62 let tz_string = tz_string.trim_matches(|c: char| c.is_ascii_whitespace());
64 let rule = TransitionRule::from_tz_string(tz_string.as_bytes(), false)?;
65 Self::new(
66 vec![],
67 match rule {
68 TransitionRule::Fixed(local_time_type) => vec![local_time_type],
69 TransitionRule::Alternate(AlternateTime { std, dst, .. }) => vec![std, dst],
70 },
71 vec![],
72 Some(rule),
73 )
74 }
75
76 pub(super) fn new(
78 transitions: Vec<Transition>,
79 local_time_types: Vec<LocalTimeType>,
80 leap_seconds: Vec<LeapSecond>,
81 extra_rule: Option<TransitionRule>,
82 ) -> Result<Self, Error> {
83 let new = Self { transitions, local_time_types, leap_seconds, extra_rule };
84 new.as_ref().validate()?;
85 Ok(new)
86 }
87
88 fn from_file(file: &mut File) -> Result<Self, Error> {
90 let mut bytes = Vec::new();
91 file.read_to_end(&mut bytes)?;
92 Self::from_tz_data(&bytes)
93 }
94
95 pub(crate) fn from_tz_data(bytes: &[u8]) -> Result<Self, Error> {
99 parser::parse(bytes)
100 }
101
102 fn fixed(ut_offset: i32) -> Result<Self, Error> {
104 Ok(Self {
105 transitions: Vec::new(),
106 local_time_types: vec![LocalTimeType::with_offset(ut_offset)?],
107 leap_seconds: Vec::new(),
108 extra_rule: None,
109 })
110 }
111
112 pub(crate) fn utc() -> Self {
114 Self {
115 transitions: Vec::new(),
116 local_time_types: vec![LocalTimeType::UTC],
117 leap_seconds: Vec::new(),
118 extra_rule: None,
119 }
120 }
121
122 pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> {
124 self.as_ref().find_local_time_type(unix_time)
125 }
126
127 pub(crate) fn find_local_time_type_from_local(
129 &self,
130 local_time: i64,
131 year: i32,
132 ) -> Result<crate::MappedLocalTime<LocalTimeType>, Error> {
133 self.as_ref().find_local_time_type_from_local(local_time, year)
134 }
135
136 fn as_ref(&self) -> TimeZoneRef {
138 TimeZoneRef {
139 transitions: &self.transitions,
140 local_time_types: &self.local_time_types,
141 leap_seconds: &self.leap_seconds,
142 extra_rule: &self.extra_rule,
143 }
144 }
145}
146
147#[derive(Debug, Copy, Clone, Eq, PartialEq)]
149pub(crate) struct TimeZoneRef<'a> {
150 transitions: &'a [Transition],
152 local_time_types: &'a [LocalTimeType],
154 leap_seconds: &'a [LeapSecond],
156 extra_rule: &'a Option<TransitionRule>,
158}
159
160impl<'a> TimeZoneRef<'a> {
161 pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&'a LocalTimeType, Error> {
163 let extra_rule = match self.transitions.last() {
164 None => match self.extra_rule {
165 Some(extra_rule) => extra_rule,
166 None => return Ok(&self.local_time_types[0]),
167 },
168 Some(last_transition) => {
169 let unix_leap_time = match self.unix_time_to_unix_leap_time(unix_time) {
170 Ok(unix_leap_time) => unix_leap_time,
171 Err(Error::OutOfRange(error)) => return Err(Error::FindLocalTimeType(error)),
172 Err(err) => return Err(err),
173 };
174
175 if unix_leap_time >= last_transition.unix_leap_time {
176 match self.extra_rule {
177 Some(extra_rule) => extra_rule,
178 None => {
179 return Ok(
187 &self.local_time_types[last_transition.local_time_type_index]
188 );
189 }
190 }
191 } else {
192 let index = match self
193 .transitions
194 .binary_search_by_key(&unix_leap_time, Transition::unix_leap_time)
195 {
196 Ok(x) => x + 1,
197 Err(x) => x,
198 };
199
200 let local_time_type_index = if index > 0 {
201 self.transitions[index - 1].local_time_type_index
202 } else {
203 0
204 };
205 return Ok(&self.local_time_types[local_time_type_index]);
206 }
207 }
208 };
209
210 match extra_rule.find_local_time_type(unix_time) {
211 Ok(local_time_type) => Ok(local_time_type),
212 Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)),
213 err => err,
214 }
215 }
216
217 pub(crate) fn find_local_time_type_from_local(
218 &self,
219 local_time: i64,
220 year: i32,
221 ) -> Result<crate::MappedLocalTime<LocalTimeType>, Error> {
222 let local_leap_time = local_time;
230
231 let offset_after_last = if !self.transitions.is_empty() {
234 let mut prev = self.local_time_types[0];
235
236 for transition in self.transitions {
237 let after_ltt = self.local_time_types[transition.local_time_type_index];
238
239 let transition_end = transition.unix_leap_time + i64::from(after_ltt.ut_offset);
242 let transition_start = transition.unix_leap_time + i64::from(prev.ut_offset);
243
244 match transition_start.cmp(&transition_end) {
245 Ordering::Greater => {
246 if local_leap_time < transition_end {
249 return Ok(crate::MappedLocalTime::Single(prev));
250 } else if local_leap_time >= transition_end
251 && local_leap_time <= transition_start
252 {
253 if prev.ut_offset < after_ltt.ut_offset {
254 return Ok(crate::MappedLocalTime::Ambiguous(prev, after_ltt));
255 } else {
256 return Ok(crate::MappedLocalTime::Ambiguous(after_ltt, prev));
257 }
258 }
259 }
260 Ordering::Equal => {
261 if local_leap_time < transition_start {
263 return Ok(crate::MappedLocalTime::Single(prev));
264 } else if local_leap_time == transition_end {
265 if prev.ut_offset < after_ltt.ut_offset {
266 return Ok(crate::MappedLocalTime::Ambiguous(prev, after_ltt));
267 } else {
268 return Ok(crate::MappedLocalTime::Ambiguous(after_ltt, prev));
269 }
270 }
271 }
272 Ordering::Less => {
273 if local_leap_time <= transition_start {
276 return Ok(crate::MappedLocalTime::Single(prev));
277 } else if local_leap_time < transition_end {
278 return Ok(crate::MappedLocalTime::None);
279 } else if local_leap_time == transition_end {
280 return Ok(crate::MappedLocalTime::Single(after_ltt));
281 }
282 }
283 }
284
285 prev = after_ltt;
287 }
288
289 prev
290 } else {
291 self.local_time_types[0]
292 };
293
294 if let Some(extra_rule) = self.extra_rule {
295 match extra_rule.find_local_time_type_from_local(local_time, year) {
296 Ok(local_time_type) => Ok(local_time_type),
297 Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)),
298 err => err,
299 }
300 } else {
301 Ok(crate::MappedLocalTime::Single(offset_after_last))
302 }
303 }
304
305 fn validate(&self) -> Result<(), Error> {
307 let local_time_types_size = self.local_time_types.len();
309 if local_time_types_size == 0 {
310 return Err(Error::TimeZone("list of local time types must not be empty"));
311 }
312
313 let mut i_transition = 0;
315 while i_transition < self.transitions.len() {
316 if self.transitions[i_transition].local_time_type_index >= local_time_types_size {
317 return Err(Error::TimeZone("invalid local time type index"));
318 }
319
320 if i_transition + 1 < self.transitions.len()
321 && self.transitions[i_transition].unix_leap_time
322 >= self.transitions[i_transition + 1].unix_leap_time
323 {
324 return Err(Error::TimeZone("invalid transition"));
325 }
326
327 i_transition += 1;
328 }
329
330 if !(self.leap_seconds.is_empty()
332 || self.leap_seconds[0].unix_leap_time >= 0
333 && self.leap_seconds[0].correction.saturating_abs() == 1)
334 {
335 return Err(Error::TimeZone("invalid leap second"));
336 }
337
338 let min_interval = SECONDS_PER_28_DAYS - 1;
339
340 let mut i_leap_second = 0;
341 while i_leap_second < self.leap_seconds.len() {
342 if i_leap_second + 1 < self.leap_seconds.len() {
343 let x0 = &self.leap_seconds[i_leap_second];
344 let x1 = &self.leap_seconds[i_leap_second + 1];
345
346 let diff_unix_leap_time = x1.unix_leap_time.saturating_sub(x0.unix_leap_time);
347 let abs_diff_correction =
348 x1.correction.saturating_sub(x0.correction).saturating_abs();
349
350 if !(diff_unix_leap_time >= min_interval && abs_diff_correction == 1) {
351 return Err(Error::TimeZone("invalid leap second"));
352 }
353 }
354 i_leap_second += 1;
355 }
356
357 let (extra_rule, last_transition) = match (&self.extra_rule, self.transitions.last()) {
359 (Some(rule), Some(trans)) => (rule, trans),
360 _ => return Ok(()),
361 };
362
363 let last_local_time_type = &self.local_time_types[last_transition.local_time_type_index];
364 let unix_time = match self.unix_leap_time_to_unix_time(last_transition.unix_leap_time) {
365 Ok(unix_time) => unix_time,
366 Err(Error::OutOfRange(error)) => return Err(Error::TimeZone(error)),
367 Err(err) => return Err(err),
368 };
369
370 let rule_local_time_type = match extra_rule.find_local_time_type(unix_time) {
371 Ok(rule_local_time_type) => rule_local_time_type,
372 Err(Error::OutOfRange(error)) => return Err(Error::TimeZone(error)),
373 Err(err) => return Err(err),
374 };
375
376 let check = last_local_time_type.ut_offset == rule_local_time_type.ut_offset
377 && last_local_time_type.is_dst == rule_local_time_type.is_dst
378 && match (&last_local_time_type.name, &rule_local_time_type.name) {
379 (Some(x), Some(y)) => x.equal(y),
380 (None, None) => true,
381 _ => false,
382 };
383
384 if !check {
385 return Err(Error::TimeZone(
386 "extra transition rule is inconsistent with the last transition",
387 ));
388 }
389
390 Ok(())
391 }
392
393 const fn unix_time_to_unix_leap_time(&self, unix_time: i64) -> Result<i64, Error> {
395 let mut unix_leap_time = unix_time;
396
397 let mut i = 0;
398 while i < self.leap_seconds.len() {
399 let leap_second = &self.leap_seconds[i];
400
401 if unix_leap_time < leap_second.unix_leap_time {
402 break;
403 }
404
405 unix_leap_time = match unix_time.checked_add(leap_second.correction as i64) {
406 Some(unix_leap_time) => unix_leap_time,
407 None => return Err(Error::OutOfRange("out of range operation")),
408 };
409
410 i += 1;
411 }
412
413 Ok(unix_leap_time)
414 }
415
416 fn unix_leap_time_to_unix_time(&self, unix_leap_time: i64) -> Result<i64, Error> {
418 if unix_leap_time == i64::min_value() {
419 return Err(Error::OutOfRange("out of range operation"));
420 }
421
422 let index = match self
423 .leap_seconds
424 .binary_search_by_key(&(unix_leap_time - 1), LeapSecond::unix_leap_time)
425 {
426 Ok(x) => x + 1,
427 Err(x) => x,
428 };
429
430 let correction = if index > 0 { self.leap_seconds[index - 1].correction } else { 0 };
431
432 match unix_leap_time.checked_sub(correction as i64) {
433 Some(unix_time) => Ok(unix_time),
434 None => Err(Error::OutOfRange("out of range operation")),
435 }
436 }
437
438 const UTC: TimeZoneRef<'static> = TimeZoneRef {
440 transitions: &[],
441 local_time_types: &[LocalTimeType::UTC],
442 leap_seconds: &[],
443 extra_rule: &None,
444 };
445}
446
447#[derive(Debug, Copy, Clone, Eq, PartialEq)]
449pub(super) struct Transition {
450 unix_leap_time: i64,
452 local_time_type_index: usize,
454}
455
456impl Transition {
457 pub(super) const fn new(unix_leap_time: i64, local_time_type_index: usize) -> Self {
459 Self { unix_leap_time, local_time_type_index }
460 }
461
462 const fn unix_leap_time(&self) -> i64 {
464 self.unix_leap_time
465 }
466}
467
468#[derive(Debug, Copy, Clone, Eq, PartialEq)]
470pub(super) struct LeapSecond {
471 unix_leap_time: i64,
473 correction: i32,
475}
476
477impl LeapSecond {
478 pub(super) const fn new(unix_leap_time: i64, correction: i32) -> Self {
480 Self { unix_leap_time, correction }
481 }
482
483 const fn unix_leap_time(&self) -> i64 {
485 self.unix_leap_time
486 }
487}
488
489#[derive(Copy, Clone, Eq, PartialEq)]
491struct TimeZoneName {
492 bytes: [u8; 8],
494}
495
496impl TimeZoneName {
497 fn new(input: &[u8]) -> Result<Self, Error> {
504 let len = input.len();
505
506 if !(3..=7).contains(&len) {
507 return Err(Error::LocalTimeType(
508 "time zone name must have between 3 and 7 characters",
509 ));
510 }
511
512 let mut bytes = [0; 8];
513 bytes[0] = input.len() as u8;
514
515 let mut i = 0;
516 while i < len {
517 let b = input[i];
518 match b {
519 b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-' => {}
520 _ => return Err(Error::LocalTimeType("invalid characters in time zone name")),
521 }
522
523 bytes[i + 1] = b;
524 i += 1;
525 }
526
527 Ok(Self { bytes })
528 }
529
530 fn as_bytes(&self) -> &[u8] {
532 match self.bytes[0] {
533 3 => &self.bytes[1..4],
534 4 => &self.bytes[1..5],
535 5 => &self.bytes[1..6],
536 6 => &self.bytes[1..7],
537 7 => &self.bytes[1..8],
538 _ => unreachable!(),
539 }
540 }
541
542 fn equal(&self, other: &Self) -> bool {
544 self.bytes == other.bytes
545 }
546}
547
548impl AsRef<str> for TimeZoneName {
549 fn as_ref(&self) -> &str {
550 unsafe { str::from_utf8_unchecked(self.as_bytes()) }
552 }
553}
554
555impl fmt::Debug for TimeZoneName {
556 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
557 self.as_ref().fmt(f)
558 }
559}
560
561#[derive(Debug, Copy, Clone, Eq, PartialEq)]
563pub(crate) struct LocalTimeType {
564 pub(super) ut_offset: i32,
566 is_dst: bool,
568 name: Option<TimeZoneName>,
570}
571
572impl LocalTimeType {
573 pub(super) fn new(ut_offset: i32, is_dst: bool, name: Option<&[u8]>) -> Result<Self, Error> {
575 if ut_offset == i32::min_value() {
576 return Err(Error::LocalTimeType("invalid UTC offset"));
577 }
578
579 let name = match name {
580 Some(name) => TimeZoneName::new(name)?,
581 None => return Ok(Self { ut_offset, is_dst, name: None }),
582 };
583
584 Ok(Self { ut_offset, is_dst, name: Some(name) })
585 }
586
587 pub(super) const fn with_offset(ut_offset: i32) -> Result<Self, Error> {
589 if ut_offset == i32::min_value() {
590 return Err(Error::LocalTimeType("invalid UTC offset"));
591 }
592
593 Ok(Self { ut_offset, is_dst: false, name: None })
594 }
595
596 pub(crate) const fn offset(&self) -> i32 {
598 self.ut_offset
599 }
600
601 pub(super) const fn is_dst(&self) -> bool {
603 self.is_dst
604 }
605
606 pub(super) const UTC: LocalTimeType = Self { ut_offset: 0, is_dst: false, name: None };
607}
608
609fn find_tz_file(path: impl AsRef<Path>) -> Result<File, Error> {
611 #[cfg(not(unix))]
613 return Ok(File::open(path)?);
614
615 #[cfg(unix)]
616 {
617 let path = path.as_ref();
618 if path.is_absolute() {
619 return Ok(File::open(path)?);
620 }
621
622 for folder in &ZONE_INFO_DIRECTORIES {
623 if let Ok(file) = File::open(PathBuf::from(folder).join(path)) {
624 return Ok(file);
625 }
626 }
627
628 Err(Error::Io(io::ErrorKind::NotFound.into()))
629 }
630}
631
632#[cfg(unix)]
634const ZONE_INFO_DIRECTORIES: [&str; 4] =
635 ["/usr/share/zoneinfo", "/share/zoneinfo", "/etc/zoneinfo", "/usr/share/lib/zoneinfo"];
636
637pub(crate) const SECONDS_PER_WEEK: i64 = SECONDS_PER_DAY * DAYS_PER_WEEK;
639const SECONDS_PER_28_DAYS: i64 = SECONDS_PER_DAY * 28;
641
642#[cfg(test)]
643mod tests {
644 use super::super::Error;
645 use super::{LeapSecond, LocalTimeType, TimeZone, TimeZoneName, Transition, TransitionRule};
646
647 #[test]
648 fn test_no_dst() -> Result<(), Error> {
649 let tz_string = b"HST10";
650 let transition_rule = TransitionRule::from_tz_string(tz_string, false)?;
651 assert_eq!(transition_rule, LocalTimeType::new(-36000, false, Some(b"HST"))?.into());
652 Ok(())
653 }
654
655 #[test]
656 fn test_error() -> Result<(), Error> {
657 assert!(matches!(
658 TransitionRule::from_tz_string(b"IST-1GMT0", false),
659 Err(Error::UnsupportedTzString(_))
660 ));
661 assert!(matches!(
662 TransitionRule::from_tz_string(b"EET-2EEST", false),
663 Err(Error::UnsupportedTzString(_))
664 ));
665
666 Ok(())
667 }
668
669 #[test]
670 fn test_v1_file_with_leap_seconds() -> Result<(), Error> {
671 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";
672
673 let time_zone = TimeZone::from_tz_data(bytes)?;
674
675 let time_zone_result = TimeZone::new(
676 Vec::new(),
677 vec![LocalTimeType::new(0, false, Some(b"UTC"))?],
678 vec![
679 LeapSecond::new(78796800, 1),
680 LeapSecond::new(94694401, 2),
681 LeapSecond::new(126230402, 3),
682 LeapSecond::new(157766403, 4),
683 LeapSecond::new(189302404, 5),
684 LeapSecond::new(220924805, 6),
685 LeapSecond::new(252460806, 7),
686 LeapSecond::new(283996807, 8),
687 LeapSecond::new(315532808, 9),
688 LeapSecond::new(362793609, 10),
689 LeapSecond::new(394329610, 11),
690 LeapSecond::new(425865611, 12),
691 LeapSecond::new(489024012, 13),
692 LeapSecond::new(567993613, 14),
693 LeapSecond::new(631152014, 15),
694 LeapSecond::new(662688015, 16),
695 LeapSecond::new(709948816, 17),
696 LeapSecond::new(741484817, 18),
697 LeapSecond::new(773020818, 19),
698 LeapSecond::new(820454419, 20),
699 LeapSecond::new(867715220, 21),
700 LeapSecond::new(915148821, 22),
701 LeapSecond::new(1136073622, 23),
702 LeapSecond::new(1230768023, 24),
703 LeapSecond::new(1341100824, 25),
704 LeapSecond::new(1435708825, 26),
705 LeapSecond::new(1483228826, 27),
706 ],
707 None,
708 )?;
709
710 assert_eq!(time_zone, time_zone_result);
711
712 Ok(())
713 }
714
715 #[test]
716 fn test_v2_file() -> Result<(), Error> {
717 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";
718
719 let time_zone = TimeZone::from_tz_data(bytes)?;
720
721 let time_zone_result = TimeZone::new(
722 vec![
723 Transition::new(-2334101314, 1),
724 Transition::new(-1157283000, 2),
725 Transition::new(-1155436200, 1),
726 Transition::new(-880198200, 3),
727 Transition::new(-769395600, 4),
728 Transition::new(-765376200, 1),
729 Transition::new(-712150200, 5),
730 ],
731 vec![
732 LocalTimeType::new(-37886, false, Some(b"LMT"))?,
733 LocalTimeType::new(-37800, false, Some(b"HST"))?,
734 LocalTimeType::new(-34200, true, Some(b"HDT"))?,
735 LocalTimeType::new(-34200, true, Some(b"HWT"))?,
736 LocalTimeType::new(-34200, true, Some(b"HPT"))?,
737 LocalTimeType::new(-36000, false, Some(b"HST"))?,
738 ],
739 Vec::new(),
740 Some(TransitionRule::from(LocalTimeType::new(-36000, false, Some(b"HST"))?)),
741 )?;
742
743 assert_eq!(time_zone, time_zone_result);
744
745 assert_eq!(
746 *time_zone.find_local_time_type(-1156939200)?,
747 LocalTimeType::new(-34200, true, Some(b"HDT"))?
748 );
749 assert_eq!(
750 *time_zone.find_local_time_type(1546300800)?,
751 LocalTimeType::new(-36000, false, Some(b"HST"))?
752 );
753
754 Ok(())
755 }
756
757 #[test]
758 fn test_no_tz_string() -> Result<(), Error> {
759 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";
761
762 let time_zone = TimeZone::from_tz_data(bytes)?;
763 dbg!(&time_zone);
764
765 let time_zone_result = TimeZone::new(
766 vec![Transition::new(-1230749160, 1)],
767 vec![
768 LocalTimeType::new(-18840, false, Some(b"QMT"))?,
769 LocalTimeType::new(-18000, false, Some(b"ECT"))?,
770 ],
771 Vec::new(),
772 None,
773 )?;
774
775 assert_eq!(time_zone, time_zone_result);
776
777 assert_eq!(
778 *time_zone.find_local_time_type(-1500000000)?,
779 LocalTimeType::new(-18840, false, Some(b"QMT"))?
780 );
781 assert_eq!(
782 *time_zone.find_local_time_type(0)?,
783 LocalTimeType::new(-18000, false, Some(b"ECT"))?
784 );
785
786 Ok(())
787 }
788
789 #[test]
790 fn test_tz_ascii_str() -> Result<(), Error> {
791 assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_))));
792 assert!(matches!(TimeZoneName::new(b"A"), Err(Error::LocalTimeType(_))));
793 assert!(matches!(TimeZoneName::new(b"AB"), Err(Error::LocalTimeType(_))));
794 assert_eq!(TimeZoneName::new(b"CET")?.as_bytes(), b"CET");
795 assert_eq!(TimeZoneName::new(b"CHADT")?.as_bytes(), b"CHADT");
796 assert_eq!(TimeZoneName::new(b"abcdefg")?.as_bytes(), b"abcdefg");
797 assert_eq!(TimeZoneName::new(b"UTC+02")?.as_bytes(), b"UTC+02");
798 assert_eq!(TimeZoneName::new(b"-1230")?.as_bytes(), b"-1230");
799 assert!(matches!(TimeZoneName::new("−0330".as_bytes()), Err(Error::LocalTimeType(_)))); assert!(matches!(TimeZoneName::new(b"\x00123"), Err(Error::LocalTimeType(_))));
801 assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_))));
802 assert!(matches!(TimeZoneName::new(b"GMT\0\0\0"), Err(Error::LocalTimeType(_))));
803
804 Ok(())
805 }
806
807 #[test]
808 fn test_time_zone() -> Result<(), Error> {
809 let utc = LocalTimeType::UTC;
810 let cet = LocalTimeType::with_offset(3600)?;
811
812 let utc_local_time_types = vec![utc];
813 let fixed_extra_rule = TransitionRule::from(cet);
814
815 let time_zone_1 = TimeZone::new(vec![], utc_local_time_types.clone(), vec![], None)?;
816 let time_zone_2 =
817 TimeZone::new(vec![], utc_local_time_types.clone(), vec![], Some(fixed_extra_rule))?;
818 let time_zone_3 =
819 TimeZone::new(vec![Transition::new(0, 0)], utc_local_time_types.clone(), vec![], None)?;
820 let time_zone_4 = TimeZone::new(
821 vec![Transition::new(i32::min_value().into(), 0), Transition::new(0, 1)],
822 vec![utc, cet],
823 Vec::new(),
824 Some(fixed_extra_rule),
825 )?;
826
827 assert_eq!(*time_zone_1.find_local_time_type(0)?, utc);
828 assert_eq!(*time_zone_2.find_local_time_type(0)?, cet);
829
830 assert_eq!(*time_zone_3.find_local_time_type(-1)?, utc);
831 assert_eq!(*time_zone_3.find_local_time_type(0)?, utc);
832
833 assert_eq!(*time_zone_4.find_local_time_type(-1)?, utc);
834 assert_eq!(*time_zone_4.find_local_time_type(0)?, cet);
835
836 let time_zone_err = TimeZone::new(
837 vec![Transition::new(0, 0)],
838 utc_local_time_types,
839 vec![],
840 Some(fixed_extra_rule),
841 );
842 assert!(time_zone_err.is_err());
843
844 Ok(())
845 }
846
847 #[test]
848 fn test_time_zone_from_posix_tz() -> Result<(), Error> {
849 #[cfg(unix)]
850 {
851 if let Ok(tz) = std::env::var("TZ") {
856 let time_zone_local = TimeZone::local(Some(tz.as_str()))?;
857 let time_zone_local_1 = TimeZone::from_posix_tz(&tz)?;
858 assert_eq!(time_zone_local, time_zone_local_1);
859 }
860
861 if let Ok(time_zone_utc) = TimeZone::from_posix_tz("UTC") {
865 assert_eq!(time_zone_utc.find_local_time_type(0)?.offset(), 0);
866 }
867 }
868
869 assert!(TimeZone::from_posix_tz("EST5EDT,0/0,J365/25").is_err());
870 assert!(TimeZone::from_posix_tz("").is_err());
871
872 Ok(())
873 }
874
875 #[test]
876 fn test_leap_seconds() -> Result<(), Error> {
877 let time_zone = TimeZone::new(
878 Vec::new(),
879 vec![LocalTimeType::new(0, false, Some(b"UTC"))?],
880 vec![
881 LeapSecond::new(78796800, 1),
882 LeapSecond::new(94694401, 2),
883 LeapSecond::new(126230402, 3),
884 LeapSecond::new(157766403, 4),
885 LeapSecond::new(189302404, 5),
886 LeapSecond::new(220924805, 6),
887 LeapSecond::new(252460806, 7),
888 LeapSecond::new(283996807, 8),
889 LeapSecond::new(315532808, 9),
890 LeapSecond::new(362793609, 10),
891 LeapSecond::new(394329610, 11),
892 LeapSecond::new(425865611, 12),
893 LeapSecond::new(489024012, 13),
894 LeapSecond::new(567993613, 14),
895 LeapSecond::new(631152014, 15),
896 LeapSecond::new(662688015, 16),
897 LeapSecond::new(709948816, 17),
898 LeapSecond::new(741484817, 18),
899 LeapSecond::new(773020818, 19),
900 LeapSecond::new(820454419, 20),
901 LeapSecond::new(867715220, 21),
902 LeapSecond::new(915148821, 22),
903 LeapSecond::new(1136073622, 23),
904 LeapSecond::new(1230768023, 24),
905 LeapSecond::new(1341100824, 25),
906 LeapSecond::new(1435708825, 26),
907 LeapSecond::new(1483228826, 27),
908 ],
909 None,
910 )?;
911
912 let time_zone_ref = time_zone.as_ref();
913
914 assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073621), Ok(1136073599)));
915 assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073622), Ok(1136073600)));
916 assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073623), Ok(1136073600)));
917 assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073624), Ok(1136073601)));
918
919 assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073599), Ok(1136073621)));
920 assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073600), Ok(1136073623)));
921 assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073601), Ok(1136073624)));
922
923 Ok(())
924 }
925
926 #[test]
927 fn test_leap_seconds_overflow() -> Result<(), Error> {
928 let time_zone_err = TimeZone::new(
929 vec![Transition::new(i64::min_value(), 0)],
930 vec![LocalTimeType::UTC],
931 vec![LeapSecond::new(0, 1)],
932 Some(TransitionRule::from(LocalTimeType::UTC)),
933 );
934 assert!(time_zone_err.is_err());
935
936 let time_zone = TimeZone::new(
937 vec![Transition::new(i64::max_value(), 0)],
938 vec![LocalTimeType::UTC],
939 vec![LeapSecond::new(0, 1)],
940 None,
941 )?;
942 assert!(matches!(
943 time_zone.find_local_time_type(i64::max_value()),
944 Err(Error::FindLocalTimeType(_))
945 ));
946
947 Ok(())
948 }
949}