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