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