1mod rule;
4
5pub use rule::*;
6
7use crate::datetime::{days_since_unix_epoch, is_leap_year};
8use crate::error::*;
9use crate::utils::*;
10use crate::UtcDateTime;
11
12use core::cmp::Ordering;
13use core::fmt;
14use core::str;
15
16#[cfg(feature = "alloc")]
17use alloc::{vec, vec::Vec};
18
19#[derive(Debug, Copy, Clone, Eq, PartialEq)]
21pub struct Transition {
22 unix_leap_time: i64,
24 local_time_type_index: usize,
26}
27
28impl Transition {
29 #[inline]
31 #[cfg_attr(feature = "const", const_fn::const_fn)]
32 pub fn new(unix_leap_time: i64, local_time_type_index: usize) -> Self {
33 Self { unix_leap_time, local_time_type_index }
34 }
35
36 #[inline]
38 #[cfg_attr(feature = "const", const_fn::const_fn)]
39 pub fn unix_leap_time(&self) -> i64 {
40 self.unix_leap_time
41 }
42
43 #[inline]
45 #[cfg_attr(feature = "const", const_fn::const_fn)]
46 pub fn local_time_type_index(&self) -> usize {
47 self.local_time_type_index
48 }
49}
50
51#[derive(Debug, Copy, Clone, Eq, PartialEq)]
53pub struct LeapSecond {
54 unix_leap_time: i64,
56 correction: i32,
58}
59
60impl LeapSecond {
61 #[inline]
63 #[cfg_attr(feature = "const", const_fn::const_fn)]
64 pub fn new(unix_leap_time: i64, correction: i32) -> Self {
65 Self { unix_leap_time, correction }
66 }
67
68 #[inline]
70 #[cfg_attr(feature = "const", const_fn::const_fn)]
71 pub fn unix_leap_time(&self) -> i64 {
72 self.unix_leap_time
73 }
74
75 #[inline]
77 #[cfg_attr(feature = "const", const_fn::const_fn)]
78 pub fn correction(&self) -> i32 {
79 self.correction
80 }
81}
82
83#[derive(Copy, Clone, Eq, PartialEq)]
85struct TzAsciiStr {
86 bytes: [u8; 8],
88}
89
90impl TzAsciiStr {
91 #[cfg_attr(feature = "const", const_fn::const_fn)]
93 fn new(input: &[u8]) -> Result<Self, LocalTimeTypeError> {
94 let len = input.len();
95
96 if !(3 <= len && len <= 7) {
97 return Err(LocalTimeTypeError("time zone designation must have between 3 and 7 characters"));
98 }
99
100 let mut bytes = [0; 8];
101 bytes[0] = input.len() as u8;
102
103 let mut i = 0;
104 while i < len {
105 let b = input[i];
106
107 if !matches!(b, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-') {
108 return Err(LocalTimeTypeError("invalid characters in time zone designation"));
109 }
110
111 bytes[i + 1] = b;
112
113 i += 1;
114 }
115
116 Ok(Self { bytes })
117 }
118
119 #[inline]
121 #[cfg_attr(feature = "const", const_fn::const_fn)]
122 fn as_bytes(&self) -> &[u8] {
123 match &self.bytes {
124 [3, head @ .., _, _, _, _] => head,
125 [4, head @ .., _, _, _] => head,
126 [5, head @ .., _, _] => head,
127 [6, head @ .., _] => head,
128 [7, head @ ..] => head,
129 _ => const_panic!(), }
131 }
132
133 #[inline]
135 #[cfg_attr(feature = "const", const_fn::const_fn)]
136 fn as_str(&self) -> &str {
137 unsafe { str::from_utf8_unchecked(self.as_bytes()) }
139 }
140
141 #[inline]
143 #[cfg_attr(feature = "const", const_fn::const_fn)]
144 fn equal(&self, other: &Self) -> bool {
145 u64::from_ne_bytes(self.bytes) == u64::from_ne_bytes(other.bytes)
146 }
147}
148
149impl fmt::Debug for TzAsciiStr {
150 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151 self.as_str().fmt(f)
152 }
153}
154
155#[derive(Debug, Copy, Clone, Eq, PartialEq)]
157pub struct LocalTimeType {
158 ut_offset: i32,
160 is_dst: bool,
162 time_zone_designation: Option<TzAsciiStr>,
164}
165
166impl LocalTimeType {
167 #[cfg_attr(feature = "const", const_fn::const_fn)]
169 pub fn new(ut_offset: i32, is_dst: bool, time_zone_designation: Option<&[u8]>) -> Result<Self, LocalTimeTypeError> {
170 if ut_offset == i32::MIN {
171 return Err(LocalTimeTypeError("invalid UTC offset"));
172 }
173
174 let time_zone_designation = match time_zone_designation {
175 None => None,
176 Some(time_zone_designation) => match TzAsciiStr::new(time_zone_designation) {
177 Err(error) => return Err(error),
178 Ok(time_zone_designation) => Some(time_zone_designation),
179 },
180 };
181
182 Ok(Self { ut_offset, is_dst, time_zone_designation })
183 }
184
185 #[inline]
187 pub const fn utc() -> Self {
188 Self { ut_offset: 0, is_dst: false, time_zone_designation: None }
189 }
190
191 #[inline]
193 #[cfg_attr(feature = "const", const_fn::const_fn)]
194 pub fn with_ut_offset(ut_offset: i32) -> Result<Self, LocalTimeTypeError> {
195 if ut_offset == i32::MIN {
196 return Err(LocalTimeTypeError("invalid UTC offset"));
197 }
198
199 Ok(Self { ut_offset, is_dst: false, time_zone_designation: None })
200 }
201
202 #[inline]
204 #[cfg_attr(feature = "const", const_fn::const_fn)]
205 pub fn ut_offset(&self) -> i32 {
206 self.ut_offset
207 }
208
209 #[inline]
211 #[cfg_attr(feature = "const", const_fn::const_fn)]
212 pub fn is_dst(&self) -> bool {
213 self.is_dst
214 }
215
216 #[inline]
218 #[cfg_attr(feature = "const", const_fn::const_fn)]
219 pub fn time_zone_designation(&self) -> &str {
220 match &self.time_zone_designation {
221 Some(s) => s.as_str(),
222 None => "",
223 }
224 }
225
226 #[inline]
228 #[cfg_attr(feature = "const", const_fn::const_fn)]
229 fn equal(&self, other: &Self) -> bool {
230 self.ut_offset == other.ut_offset
231 && self.is_dst == other.is_dst
232 && match (&self.time_zone_designation, &other.time_zone_designation) {
233 (Some(x), Some(y)) => x.equal(y),
234 (None, None) => true,
235 _ => false,
236 }
237 }
238}
239
240#[cfg(feature = "alloc")]
242#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
243#[derive(Debug, Clone, Eq, PartialEq)]
244pub struct TimeZone {
245 transitions: Vec<Transition>,
247 local_time_types: Vec<LocalTimeType>,
249 leap_seconds: Vec<LeapSecond>,
251 extra_rule: Option<TransitionRule>,
253}
254
255#[derive(Debug, Copy, Clone, Eq, PartialEq)]
257pub struct TimeZoneRef<'a> {
258 transitions: &'a [Transition],
260 local_time_types: &'a [LocalTimeType],
262 leap_seconds: &'a [LeapSecond],
264 extra_rule: &'a Option<TransitionRule>,
266}
267
268impl<'a> TimeZoneRef<'a> {
269 #[cfg_attr(feature = "const", const_fn::const_fn)]
271 pub fn new(
272 transitions: &'a [Transition],
273 local_time_types: &'a [LocalTimeType],
274 leap_seconds: &'a [LeapSecond],
275 extra_rule: &'a Option<TransitionRule>,
276 ) -> Result<Self, TimeZoneError> {
277 let time_zone_ref = Self::new_unchecked(transitions, local_time_types, leap_seconds, extra_rule);
278
279 if let Err(error) = time_zone_ref.check_inputs() {
280 return Err(error);
281 }
282
283 Ok(time_zone_ref)
284 }
285
286 #[inline]
288 #[cfg_attr(feature = "const", const_fn::const_fn)]
289 pub fn utc() -> Self {
290 const UTC: LocalTimeType = LocalTimeType::utc();
291 Self { transitions: &[], local_time_types: &[UTC], leap_seconds: &[], extra_rule: &None }
292 }
293
294 #[inline]
296 #[cfg_attr(feature = "const", const_fn::const_fn)]
297 pub fn transitions(&self) -> &'a [Transition] {
298 self.transitions
299 }
300
301 #[inline]
303 #[cfg_attr(feature = "const", const_fn::const_fn)]
304 pub fn local_time_types(&self) -> &'a [LocalTimeType] {
305 self.local_time_types
306 }
307
308 #[inline]
310 #[cfg_attr(feature = "const", const_fn::const_fn)]
311 pub fn leap_seconds(&self) -> &'a [LeapSecond] {
312 self.leap_seconds
313 }
314
315 #[inline]
317 #[cfg_attr(feature = "const", const_fn::const_fn)]
318 pub fn extra_rule(&self) -> &'a Option<TransitionRule> {
319 self.extra_rule
320 }
321
322 #[cfg_attr(feature = "const", const_fn::const_fn)]
324 pub fn find_local_time_type(&self, unix_time: i64) -> Result<&'a LocalTimeType, FindLocalTimeTypeError> {
325 let extra_rule = match self.transitions {
326 [] => match self.extra_rule {
327 Some(extra_rule) => extra_rule,
328 None => return Ok(&self.local_time_types[0]),
329 },
330 [.., last_transition] => {
331 let unix_leap_time = match self.unix_time_to_unix_leap_time(unix_time) {
332 Ok(unix_leap_time) => unix_leap_time,
333 Err(OutOfRangeError(error)) => return Err(FindLocalTimeTypeError(error)),
334 };
335
336 if unix_leap_time >= last_transition.unix_leap_time {
337 match self.extra_rule {
338 Some(extra_rule) => extra_rule,
339 None => return Err(FindLocalTimeTypeError("no local time type is available for the specified timestamp")),
340 }
341 } else {
342 let index = match binary_search_transitions(self.transitions, unix_leap_time) {
343 Ok(x) => x + 1,
344 Err(x) => x,
345 };
346
347 let local_time_type_index = if index > 0 { self.transitions[index - 1].local_time_type_index } else { 0 };
348 return Ok(&self.local_time_types[local_time_type_index]);
349 }
350 }
351 };
352
353 match extra_rule.find_local_time_type(unix_time) {
354 Ok(local_time_type) => Ok(local_time_type),
355 Err(OutOfRangeError(error)) => Err(FindLocalTimeTypeError(error)),
356 }
357 }
358
359 #[inline]
361 #[cfg_attr(feature = "const", const_fn::const_fn)]
362 fn new_unchecked(
363 transitions: &'a [Transition],
364 local_time_types: &'a [LocalTimeType],
365 leap_seconds: &'a [LeapSecond],
366 extra_rule: &'a Option<TransitionRule>,
367 ) -> Self {
368 Self { transitions, local_time_types, leap_seconds, extra_rule }
369 }
370
371 #[cfg_attr(feature = "const", const_fn::const_fn)]
373 fn check_inputs(&self) -> Result<(), TimeZoneError> {
374 use crate::constants::*;
375
376 let local_time_types_size = self.local_time_types.len();
378 if local_time_types_size == 0 {
379 return Err(TimeZoneError("list of local time types must not be empty"));
380 }
381
382 let mut i_transition = 0;
384 while i_transition < self.transitions.len() {
385 if self.transitions[i_transition].local_time_type_index >= local_time_types_size {
386 return Err(TimeZoneError("invalid local time type index"));
387 }
388
389 if i_transition + 1 < self.transitions.len() && self.transitions[i_transition].unix_leap_time >= self.transitions[i_transition + 1].unix_leap_time {
390 return Err(TimeZoneError("invalid transition"));
391 }
392
393 i_transition += 1;
394 }
395
396 if !(self.leap_seconds.is_empty() || self.leap_seconds[0].unix_leap_time >= 0 && self.leap_seconds[0].correction.saturating_abs() == 1) {
398 return Err(TimeZoneError("invalid leap second"));
399 }
400
401 let min_interval = SECONDS_PER_28_DAYS - 1;
402
403 let mut i_leap_second = 0;
404 while i_leap_second < self.leap_seconds.len() {
405 if i_leap_second + 1 < self.leap_seconds.len() {
406 let x0 = &self.leap_seconds[i_leap_second];
407 let x1 = &self.leap_seconds[i_leap_second + 1];
408
409 let diff_unix_leap_time = x1.unix_leap_time.saturating_sub(x0.unix_leap_time);
410 let abs_diff_correction = x1.correction.saturating_sub(x0.correction).saturating_abs();
411
412 if !(diff_unix_leap_time >= min_interval && abs_diff_correction == 1) {
413 return Err(TimeZoneError("invalid leap second"));
414 }
415 }
416 i_leap_second += 1;
417 }
418
419 if let (Some(extra_rule), [.., last_transition]) = (&self.extra_rule, self.transitions) {
421 let last_local_time_type = &self.local_time_types[last_transition.local_time_type_index];
422
423 let unix_time = match self.unix_leap_time_to_unix_time(last_transition.unix_leap_time) {
424 Ok(unix_time) => unix_time,
425 Err(OutOfRangeError(error)) => return Err(TimeZoneError(error)),
426 };
427
428 let rule_local_time_type = match extra_rule.find_local_time_type(unix_time) {
429 Ok(rule_local_time_type) => rule_local_time_type,
430 Err(OutOfRangeError(error)) => return Err(TimeZoneError(error)),
431 };
432
433 if !last_local_time_type.equal(rule_local_time_type) {
434 return Err(TimeZoneError("extra transition rule is inconsistent with the last transition"));
435 }
436 }
437
438 Ok(())
439 }
440
441 #[cfg_attr(feature = "const", const_fn::const_fn)]
443 pub(crate) fn unix_time_to_unix_leap_time(&self, unix_time: i64) -> Result<i64, OutOfRangeError> {
444 let mut unix_leap_time = unix_time;
445
446 let mut i = 0;
447 while i < self.leap_seconds.len() {
448 let leap_second = &self.leap_seconds[i];
449
450 if unix_leap_time < leap_second.unix_leap_time {
451 break;
452 }
453
454 unix_leap_time = match unix_time.checked_add(leap_second.correction as i64) {
455 Some(unix_leap_time) => unix_leap_time,
456 None => return Err(OutOfRangeError("out of range operation")),
457 };
458
459 i += 1;
460 }
461
462 Ok(unix_leap_time)
463 }
464
465 #[cfg_attr(feature = "const", const_fn::const_fn)]
467 pub(crate) fn unix_leap_time_to_unix_time(&self, unix_leap_time: i64) -> Result<i64, OutOfRangeError> {
468 if unix_leap_time == i64::MIN {
469 return Err(OutOfRangeError("out of range operation"));
470 }
471
472 let index = match binary_search_leap_seconds(self.leap_seconds, unix_leap_time - 1) {
473 Ok(x) => x + 1,
474 Err(x) => x,
475 };
476
477 let correction = if index > 0 { self.leap_seconds[index - 1].correction } else { 0 };
478
479 match unix_leap_time.checked_sub(correction as i64) {
480 Some(unix_time) => Ok(unix_time),
481 None => Err(OutOfRangeError("out of range operation")),
482 }
483 }
484}
485
486#[cfg(feature = "alloc")]
487impl TimeZone {
488 pub fn new(
490 transitions: Vec<Transition>,
491 local_time_types: Vec<LocalTimeType>,
492 leap_seconds: Vec<LeapSecond>,
493 extra_rule: Option<TransitionRule>,
494 ) -> Result<Self, TimeZoneError> {
495 TimeZoneRef::new_unchecked(&transitions, &local_time_types, &leap_seconds, &extra_rule).check_inputs()?;
496 Ok(Self { transitions, local_time_types, leap_seconds, extra_rule })
497 }
498
499 #[inline]
501 pub fn as_ref(&self) -> TimeZoneRef {
502 TimeZoneRef::new_unchecked(&self.transitions, &self.local_time_types, &self.leap_seconds, &self.extra_rule)
503 }
504
505 #[inline]
507 pub fn utc() -> Self {
508 Self { transitions: Vec::new(), local_time_types: vec![LocalTimeType::utc()], leap_seconds: Vec::new(), extra_rule: None }
509 }
510
511 #[inline]
513 pub fn fixed(ut_offset: i32) -> Result<Self, LocalTimeTypeError> {
514 Ok(Self { transitions: Vec::new(), local_time_types: vec![LocalTimeType::with_ut_offset(ut_offset)?], leap_seconds: Vec::new(), extra_rule: None })
515 }
516
517 #[cfg(feature = "std")]
522 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
523 pub fn local() -> Result<Self, TzError> {
524 #[cfg(not(unix))]
525 let local_time_zone = Self::utc();
526
527 #[cfg(unix)]
528 let local_time_zone = Self::from_posix_tz("localtime")?;
529
530 Ok(local_time_zone)
531 }
532
533 #[cfg(feature = "std")]
535 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
536 pub fn from_tz_data(bytes: &[u8]) -> Result<Self, TzError> {
537 crate::parse::parse_tz_file(bytes)
538 }
539
540 #[cfg(feature = "std")]
542 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
543 pub fn from_posix_tz(tz_string: &str) -> Result<Self, TzError> {
544 use crate::parse::*;
545
546 use std::fs::{self, File};
547 use std::io::{self, Read};
548
549 if tz_string.is_empty() {
550 return Err(TzError::TzStringError(TzStringError::InvalidTzString("empty TZ string")));
551 }
552
553 if tz_string == "localtime" {
554 return parse_tz_file(&fs::read("/etc/localtime")?);
555 }
556
557 let read = |mut file: File| -> io::Result<_> {
558 let mut bytes = Vec::new();
559 file.read_to_end(&mut bytes)?;
560 Ok(bytes)
561 };
562
563 let mut chars = tz_string.chars();
564 if chars.next() == Some(':') {
565 return parse_tz_file(&read(get_tz_file(chars.as_str())?)?);
566 }
567
568 match get_tz_file(tz_string) {
569 Ok(file) => parse_tz_file(&read(file)?),
570 Err(_) => {
571 let tz_string = tz_string.trim_matches(|c: char| c.is_ascii_whitespace());
572
573 let rule = parse_posix_tz(tz_string.as_bytes(), false)?;
575
576 let local_time_types = match rule {
577 TransitionRule::Fixed(local_time_type) => vec![local_time_type],
578 TransitionRule::Alternate(alternate_time) => vec![*alternate_time.std(), *alternate_time.dst()],
579 };
580
581 Ok(TimeZone::new(vec![], local_time_types, vec![], Some(rule))?)
582 }
583 }
584 }
585
586 #[cfg(feature = "std")]
588 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
589 pub fn find_current_local_time_type(&self) -> Result<&LocalTimeType, TzError> {
590 use core::convert::TryInto;
591 use std::time::SystemTime;
592
593 Ok(self.find_local_time_type(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_secs().try_into()?)?)
594 }
595
596 pub fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, FindLocalTimeTypeError> {
598 self.as_ref().find_local_time_type(unix_time)
599 }
600}
601
602#[cfg(test)]
603mod test {
604 use super::*;
605 use crate::Result;
606
607 #[test]
608 fn test_tz_ascii_str() -> Result<()> {
609 assert!(matches!(TzAsciiStr::new(b""), Err(LocalTimeTypeError(_))));
610 assert!(matches!(TzAsciiStr::new(b"1"), Err(LocalTimeTypeError(_))));
611 assert!(matches!(TzAsciiStr::new(b"12"), Err(LocalTimeTypeError(_))));
612 assert_eq!(TzAsciiStr::new(b"123")?.as_bytes(), b"123");
613 assert_eq!(TzAsciiStr::new(b"1234")?.as_bytes(), b"1234");
614 assert_eq!(TzAsciiStr::new(b"12345")?.as_bytes(), b"12345");
615 assert_eq!(TzAsciiStr::new(b"123456")?.as_bytes(), b"123456");
616 assert_eq!(TzAsciiStr::new(b"1234567")?.as_bytes(), b"1234567");
617 assert!(matches!(TzAsciiStr::new(b"12345678"), Err(LocalTimeTypeError(_))));
618 assert!(matches!(TzAsciiStr::new(b"123456789"), Err(LocalTimeTypeError(_))));
619 assert!(matches!(TzAsciiStr::new(b"1234567890"), Err(LocalTimeTypeError(_))));
620
621 assert!(matches!(TzAsciiStr::new(b"123\0\0\0"), Err(LocalTimeTypeError(_))));
622
623 Ok(())
624 }
625
626 #[cfg(feature = "alloc")]
627 #[test]
628 fn test_time_zone() -> Result<()> {
629 let utc = LocalTimeType::utc();
630 let cet = LocalTimeType::with_ut_offset(3600)?;
631
632 let utc_local_time_types = vec![utc];
633 let fixed_extra_rule = TransitionRule::Fixed(cet);
634
635 let time_zone_1 = TimeZone::new(vec![], utc_local_time_types.clone(), vec![], None)?;
636 let time_zone_2 = TimeZone::new(vec![], utc_local_time_types.clone(), vec![], Some(fixed_extra_rule))?;
637 let time_zone_3 = TimeZone::new(vec![Transition::new(0, 0)], utc_local_time_types.clone(), vec![], None)?;
638 let time_zone_4 = TimeZone::new(vec![Transition::new(i32::MIN.into(), 0), Transition::new(0, 1)], vec![utc, cet], vec![], Some(fixed_extra_rule))?;
639
640 assert_eq!(*time_zone_1.find_local_time_type(0)?, utc);
641 assert_eq!(*time_zone_2.find_local_time_type(0)?, cet);
642
643 assert_eq!(*time_zone_3.find_local_time_type(-1)?, utc);
644 assert!(matches!(time_zone_3.find_local_time_type(0), Err(FindLocalTimeTypeError(_))));
645
646 assert_eq!(*time_zone_4.find_local_time_type(-1)?, utc);
647 assert_eq!(*time_zone_4.find_local_time_type(0)?, cet);
648
649 let time_zone_err = TimeZone::new(vec![Transition::new(0, 0)], utc_local_time_types, vec![], Some(fixed_extra_rule));
650 assert!(time_zone_err.is_err());
651
652 Ok(())
653 }
654
655 #[cfg(feature = "std")]
656 #[test]
657 fn test_time_zone_from_posix_tz() -> Result<()> {
658 #[cfg(unix)]
659 {
660 let time_zone_local = TimeZone::local()?;
661 let time_zone_local_1 = TimeZone::from_posix_tz("localtime")?;
662 let time_zone_local_2 = TimeZone::from_posix_tz("/etc/localtime")?;
663 let time_zone_local_3 = TimeZone::from_posix_tz(":/etc/localtime")?;
664
665 assert_eq!(time_zone_local, time_zone_local_1);
666 assert_eq!(time_zone_local, time_zone_local_2);
667 assert_eq!(time_zone_local, time_zone_local_3);
668
669 assert!(matches!(time_zone_local.find_current_local_time_type(), Ok(_) | Err(TzError::FindLocalTimeTypeError(_))));
670
671 let time_zone_utc = TimeZone::from_posix_tz("UTC")?;
672 assert_eq!(time_zone_utc.find_local_time_type(0)?.ut_offset(), 0);
673 }
674
675 assert!(TimeZone::from_posix_tz("EST5EDT,0/0,J365/25").is_err());
676 assert!(TimeZone::from_posix_tz("").is_err());
677
678 Ok(())
679 }
680
681 #[cfg(feature = "alloc")]
682 #[test]
683 fn test_leap_seconds() -> Result<()> {
684 let time_zone = TimeZone::new(
685 vec![],
686 vec![LocalTimeType::new(0, false, Some(b"UTC"))?],
687 vec![
688 LeapSecond::new(78796800, 1),
689 LeapSecond::new(94694401, 2),
690 LeapSecond::new(126230402, 3),
691 LeapSecond::new(157766403, 4),
692 LeapSecond::new(189302404, 5),
693 LeapSecond::new(220924805, 6),
694 LeapSecond::new(252460806, 7),
695 LeapSecond::new(283996807, 8),
696 LeapSecond::new(315532808, 9),
697 LeapSecond::new(362793609, 10),
698 LeapSecond::new(394329610, 11),
699 LeapSecond::new(425865611, 12),
700 LeapSecond::new(489024012, 13),
701 LeapSecond::new(567993613, 14),
702 LeapSecond::new(631152014, 15),
703 LeapSecond::new(662688015, 16),
704 LeapSecond::new(709948816, 17),
705 LeapSecond::new(741484817, 18),
706 LeapSecond::new(773020818, 19),
707 LeapSecond::new(820454419, 20),
708 LeapSecond::new(867715220, 21),
709 LeapSecond::new(915148821, 22),
710 LeapSecond::new(1136073622, 23),
711 LeapSecond::new(1230768023, 24),
712 LeapSecond::new(1341100824, 25),
713 LeapSecond::new(1435708825, 26),
714 LeapSecond::new(1483228826, 27),
715 ],
716 None,
717 )?;
718
719 let time_zone_ref = time_zone.as_ref();
720
721 assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073621), Ok(1136073599)));
722 assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073622), Ok(1136073600)));
723 assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073623), Ok(1136073600)));
724 assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073624), Ok(1136073601)));
725
726 assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073599), Ok(1136073621)));
727 assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073600), Ok(1136073623)));
728 assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073601), Ok(1136073624)));
729
730 Ok(())
731 }
732
733 #[cfg(feature = "alloc")]
734 #[test]
735 fn test_leap_seconds_overflow() -> Result<()> {
736 let time_zone_err = TimeZone::new(
737 vec![Transition::new(i64::MIN, 0)],
738 vec![LocalTimeType::utc()],
739 vec![LeapSecond::new(0, 1)],
740 Some(TransitionRule::Fixed(LocalTimeType::utc())),
741 );
742 assert!(time_zone_err.is_err());
743
744 let time_zone = TimeZone::new(vec![Transition::new(i64::MAX, 0)], vec![LocalTimeType::utc()], vec![LeapSecond::new(0, 1)], None)?;
745 assert!(matches!(time_zone.find_local_time_type(i64::MAX), Err(FindLocalTimeTypeError(_))));
746
747 Ok(())
748 }
749}