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