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