1use crate::binary_search::binary_search;
30use crate::{Offset, OffsetResult, TimeZone};
31use std::cmp::Ordering;
32use std::ops::Index;
33use time::{OffsetDateTime, UtcOffset};
34
35struct Span {
38 start: Option<i64>,
39 end: Option<i64>,
40}
41
42impl Span {
43 fn contains(&self, x: i64) -> bool {
44 match (self.start, self.end) {
45 (Some(a), Some(b)) if a <= x && x < b => true,
46 (Some(a), None) if a <= x => true,
47 (None, Some(b)) if b > x => true,
48 (None, None) => true,
49 _ => false,
50 }
51 }
52
53 fn cmp(&self, x: i64) -> Ordering {
54 match (self.start, self.end) {
55 (Some(a), Some(b)) if a <= x && x < b => Ordering::Equal,
56 (Some(a), Some(b)) if a <= x && b <= x => Ordering::Less,
57 (Some(_), Some(_)) => Ordering::Greater,
58 (Some(a), None) if a <= x => Ordering::Equal,
59 (Some(_), None) => Ordering::Greater,
60 (None, Some(b)) if b <= x => Ordering::Less,
61 (None, Some(_)) => Ordering::Equal,
62 (None, None) => Ordering::Equal,
63 }
64 }
65}
66
67#[derive(Debug, PartialEq, Eq)]
68pub struct FixedTimespan {
69 pub utc_offset: i64,
70 pub dst_offset: i64,
71 pub name: &'static str,
72}
73
74#[derive(Debug, PartialEq, Eq)]
75pub struct FixedTimespanSet {
76 pub name: &'static str,
77 pub first: FixedTimespan,
78 pub others: &'static [(i64, FixedTimespan)],
79}
80
81impl FixedTimespanSet {
82 fn len(&self) -> usize {
83 1 + self.others.len()
84 }
85
86 fn span_utc(&self, i: usize) -> Span {
87 let start = match i {
88 0 => None,
89 _ => Some(self.others[i - 1].0),
90 };
91 let end = if i >= self.others.len() {
92 None
93 } else {
94 Some(self.others[i].0)
95 };
96 Span { start, end }
97 }
98
99 fn span_local(&self, i: usize) -> Span {
100 let start = match i {
101 0 => None,
102 _ => Some(&self.others[i - 1]),
103 }
104 .map(|(i, v)| i + v.utc_offset + v.dst_offset);
105 let end = if i >= self.others.len() {
106 None
107 } else if i == 0 {
108 Some(self.others[i].0 + self.first.utc_offset + self.first.dst_offset)
109 } else {
110 let (_, v) = &self.others[i - 1];
111 Some(self.others[i].0 + v.utc_offset + v.dst_offset)
112 };
113 Span { start, end }
114 }
115}
116
117impl Index<usize> for FixedTimespanSet {
118 type Output = FixedTimespan;
119
120 fn index(&self, index: usize) -> &Self::Output {
121 debug_assert!(index < self.len());
122 match index {
123 0 => &self.first,
124 _ => &self.others[index - 1].1,
125 }
126 }
127}
128
129#[derive(Debug, PartialEq, Eq)]
130pub struct TzOffset {
131 timespan: &'static FixedTimespan,
132}
133
134impl Offset for TzOffset {
135 fn to_utc(&self) -> UtcOffset {
136 UtcOffset::from_whole_seconds((self.timespan.utc_offset + self.timespan.dst_offset) as i32)
137 .unwrap()
138 }
139
140 fn name(&self) -> &str {
141 self.timespan.name
142 }
143
144 fn is_dst(&self) -> bool {
145 self.timespan.dst_offset > 0
146 }
147}
148
149#[derive(Debug, PartialEq, Eq)]
150pub struct Tz {
151 set: &'static FixedTimespanSet,
152}
153
154impl TimeZone for Tz {
155 type Offset = TzOffset;
156
157 fn get_offset_utc(&self, date_time: &OffsetDateTime) -> TzOffset {
158 let timestamp = date_time.unix_timestamp();
159 let index =
160 binary_search(0, self.set.len(), |i| self.set.span_utc(i).cmp(timestamp)).unwrap();
161 TzOffset {
162 timespan: &self.set[index],
163 }
164 }
165
166 fn get_offset_local(&self, date_time: &OffsetDateTime) -> OffsetResult<Self::Offset> {
167 let timestamp = date_time.unix_timestamp();
168 if let Some(i) = binary_search(0, self.set.len(), |i| self.set.span_local(i).cmp(timestamp))
169 {
170 return if self.set.len() == 1 {
171 OffsetResult::Some(TzOffset {
172 timespan: &self.set[i],
173 })
174 } else if i == 0 && self.set.span_local(1).contains(timestamp) {
175 OffsetResult::Ambiguous(
176 TzOffset {
177 timespan: &self.set[0],
178 },
179 TzOffset {
180 timespan: &self.set[1],
181 },
182 )
183 } else if i == 0 {
184 OffsetResult::Some(TzOffset {
185 timespan: &self.set[0],
186 })
187 } else if self.set.span_local(i - 1).contains(timestamp) {
188 OffsetResult::Ambiguous(
189 TzOffset {
190 timespan: &self.set[i - 1],
191 },
192 TzOffset {
193 timespan: &self.set[i],
194 },
195 )
196 } else if i == self.set.len() - 1 {
197 OffsetResult::Some(TzOffset {
198 timespan: &self.set[i],
199 })
200 } else if self.set.span_local(i + 1).contains(timestamp) {
201 OffsetResult::Ambiguous(
202 TzOffset {
203 timespan: &self.set[i],
204 },
205 TzOffset {
206 timespan: &self.set[i + 1],
207 },
208 )
209 } else {
210 OffsetResult::Some(TzOffset {
211 timespan: &self.set[i],
212 })
213 };
214 }
215 OffsetResult::None
216 }
217
218 fn get_offset_primary(&self) -> Self::Offset {
219 TzOffset {
220 timespan: &self.set.first,
221 }
222 }
223
224 fn name(&self) -> &str {
225 self.set.name
226 }
227}
228
229pub const fn internal_tz_new(set: &'static FixedTimespanSet) -> Tz {
230 Tz { set }
231}