1use crate::{Date, OfDay, Span, Time};
2
3#[derive(Copy, Clone)]
4pub struct TzOffset {
5 pub utc_offset: i32,
6 pub dst_offset: i32,
7}
8
9#[derive(Clone)]
10pub struct TzInfo {
11 pub first: TzOffset,
12 pub rest: &'static [(i64, TzOffset)],
13}
14
15impl TzOffset {
16 pub const ZERO: TzOffset = TzOffset { utc_offset: 0, dst_offset: 0 };
17
18 pub fn total_offset_sec(&self) -> i32 {
19 self.utc_offset + self.dst_offset
20 }
21}
22
23#[derive(Clone, Debug, PartialEq, Eq)]
24#[allow(clippy::enum_variant_names)]
25pub enum TzError {
26 NoTimeInThisTz,
27 TwoTimesInThisTz(Time, Time),
28}
29
30impl std::fmt::Display for TzError {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 write!(f, "{self:?}")
33 }
34}
35
36impl std::error::Error for TzError {}
37
38#[derive(Clone, Debug, PartialEq, Eq)]
39pub enum TzParseError {
40 UnknownZone(String),
41}
42
43impl std::fmt::Display for TzParseError {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 write!(f, "{self:?}")
46 }
47}
48
49impl std::error::Error for TzParseError {}
50
51impl TzInfo {
52 pub fn find(&self, time: Time) -> &TzOffset {
53 let sec = time.to_int_ns_since_epoch().div_euclid(Span::SEC.to_int_ns());
54 let index = self.rest.partition_point(|&(start_sec, _)| sec >= start_sec);
55 if index == 0 {
56 &self.first
57 } else {
58 &self.rest[index - 1].1
59 }
60 }
61
62 pub fn offset(&self, time: Time) -> Span {
63 let fixed_timespan = self.find(time);
64 Span::of_int_sec(fixed_timespan.total_offset_sec() as i64)
65 }
66
67 pub const GMT: TzInfo = TzInfo { first: TzOffset::ZERO, rest: &[] };
68
69 fn valid_time(&self, gmt_sec: i64, nanosecond: i64, next_i: usize) -> Option<Time> {
70 let (min_sec, tz_info) = if next_i == 0 {
71 (i64::MIN, self.first)
72 } else if next_i > self.rest.len() {
73 return None;
74 } else {
75 self.rest[next_i - 1]
76 };
77 let sec = gmt_sec - tz_info.total_offset_sec() as i64;
78 if sec >= min_sec && (self.rest.len() == next_i || sec < self.rest[next_i].0) {
79 Some(crate::Time::of_int_ns_since_epoch(sec * Span::SEC.to_int_ns() + nanosecond))
80 } else {
81 None
82 }
83 }
84
85 pub fn date_ofday_to_time(&self, date: Date, ofday: OfDay) -> Result<Time, TzError> {
86 let gmt_ns = (date - Date::UNIX_EPOCH) as i64 * Span::DAY.to_int_ns();
87 let gmt_ns = gmt_ns + ofday.to_ns_since_midnight();
88 let gmt_sec = gmt_ns.div_euclid(Span::SEC.to_int_ns());
89 let nanosecond = gmt_ns.rem_euclid(Span::SEC.to_int_ns());
90 let next_i = self.rest.partition_point(|&(start_sec, _)| gmt_sec >= start_sec);
91 if next_i == 0 {
92 let t1 = self.valid_time(gmt_sec, nanosecond, next_i);
93 let t2 = self.valid_time(gmt_sec, nanosecond, next_i + 1);
94 match (t1, t2) {
95 (None, None) => Err(TzError::NoTimeInThisTz),
96 (Some(v), None) | (None, Some(v)) => Ok(v),
97 (Some(v1), Some(v2)) => Err(TzError::TwoTimesInThisTz(v1, v2)),
98 }
99 } else {
100 let t0 = self.valid_time(gmt_sec, nanosecond, next_i - 1);
101 let t1 = self.valid_time(gmt_sec, nanosecond, next_i);
102 let t2 = self.valid_time(gmt_sec, nanosecond, next_i + 1);
103 match (t0, t1, t2) {
104 (None, None, None) => Err(TzError::NoTimeInThisTz),
105 (Some(v), None, None) | (None, Some(v), None) | (None, None, Some(v)) => Ok(v),
106 (Some(v1), Some(v2), _) | (Some(v1), _, Some(v2)) | (_, Some(v1), Some(v2)) => {
107 Err(TzError::TwoTimesInThisTz(v1, v2))
108 }
109 }
110 }
111 }
112}