time_tz/
timezone_impl.rs

1// Copyright (c) 2022, Yuri6037
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification,
6// are permitted provided that the following conditions are met:
7//
8// * Redistributions of source code must retain the above copyright notice,
9// this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13// * Neither the name of time-tz nor the names of its contributors
14// may be used to endorse or promote products derived from this software
15// without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29use crate::binary_search::binary_search;
30use crate::{Offset, OffsetResult, TimeZone};
31use std::cmp::Ordering;
32use std::ops::Index;
33use time::{OffsetDateTime, UtcOffset};
34
35//Inspired from https://github.com/chronotope/chrono-tz/blob/main/src/timezone_impl.rs
36
37struct 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}