1use core::fmt;
7use core::ops::{Add, Sub};
8use core::str::FromStr;
9
10#[cfg(feature = "rkyv")]
11use rkyv::{Archive, Deserialize, Serialize};
12
13use super::{LocalResult, Offset, TimeZone};
14use crate::duration::Duration as OldDuration;
15use crate::format::{scan, OUT_OF_RANGE};
16use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
17use crate::{DateTime, ParseError, Timelike};
18
19#[derive(PartialEq, Eq, Hash, Copy, Clone)]
26#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))]
27pub struct FixedOffset {
28 local_minus_utc: i32,
29}
30
31impl FixedOffset {
32 #[deprecated(since = "0.4.23", note = "use `east_opt()` instead")]
37 #[must_use]
38 pub fn east(secs: i32) -> FixedOffset {
39 FixedOffset::east_opt(secs).expect("FixedOffset::east out of bounds")
40 }
41
42 #[cfg_attr(not(feature = "std"), doc = "```ignore")]
50 #[cfg_attr(feature = "std", doc = "```")]
51 #[must_use]
60 pub const fn east_opt(secs: i32) -> Option<FixedOffset> {
61 if -86_400 < secs && secs < 86_400 {
62 Some(FixedOffset { local_minus_utc: secs })
63 } else {
64 None
65 }
66 }
67
68 #[deprecated(since = "0.4.23", note = "use `west_opt()` instead")]
73 #[must_use]
74 pub fn west(secs: i32) -> FixedOffset {
75 FixedOffset::west_opt(secs).expect("FixedOffset::west out of bounds")
76 }
77
78 #[cfg_attr(not(feature = "std"), doc = "```ignore")]
86 #[cfg_attr(feature = "std", doc = "```")]
87 #[must_use]
96 pub const fn west_opt(secs: i32) -> Option<FixedOffset> {
97 if -86_400 < secs && secs < 86_400 {
98 Some(FixedOffset { local_minus_utc: -secs })
99 } else {
100 None
101 }
102 }
103
104 #[inline]
106 pub const fn local_minus_utc(&self) -> i32 {
107 self.local_minus_utc
108 }
109
110 #[inline]
112 pub const fn utc_minus_local(&self) -> i32 {
113 -self.local_minus_utc
114 }
115}
116
117impl FromStr for FixedOffset {
119 type Err = ParseError;
120 fn from_str(s: &str) -> Result<Self, Self::Err> {
121 let (_, offset) = scan::timezone_offset(s, scan::colon_or_space, false, false, true)?;
122 Self::east_opt(offset).ok_or(OUT_OF_RANGE)
123 }
124}
125
126impl TimeZone for FixedOffset {
127 type Offset = FixedOffset;
128
129 fn from_offset(offset: &FixedOffset) -> FixedOffset {
130 *offset
131 }
132
133 fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<FixedOffset> {
134 LocalResult::Single(*self)
135 }
136 fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<FixedOffset> {
137 LocalResult::Single(*self)
138 }
139
140 fn offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset {
141 *self
142 }
143 fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> FixedOffset {
144 *self
145 }
146}
147
148impl Offset for FixedOffset {
149 fn fix(&self) -> FixedOffset {
150 *self
151 }
152}
153
154impl fmt::Debug for FixedOffset {
155 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
156 let offset = self.local_minus_utc;
157 let (sign, offset) = if offset < 0 { ('-', -offset) } else { ('+', offset) };
158 let sec = offset.rem_euclid(60);
159 let mins = offset.div_euclid(60);
160 let min = mins.rem_euclid(60);
161 let hour = mins.div_euclid(60);
162 if sec == 0 {
163 write!(f, "{}{:02}:{:02}", sign, hour, min)
164 } else {
165 write!(f, "{}{:02}:{:02}:{:02}", sign, hour, min, sec)
166 }
167 }
168}
169
170impl fmt::Display for FixedOffset {
171 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
172 fmt::Debug::fmt(self, f)
173 }
174}
175
176#[cfg(feature = "arbitrary")]
177impl arbitrary::Arbitrary<'_> for FixedOffset {
178 fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<FixedOffset> {
179 let secs = u.int_in_range(-86_399..=86_399)?;
180 let fixed_offset = FixedOffset::east_opt(secs)
181 .expect("Could not generate a valid chrono::FixedOffset. It looks like implementation of Arbitrary for FixedOffset is erroneous.");
182 Ok(fixed_offset)
183 }
184}
185
186fn add_with_leapsecond<T>(lhs: &T, rhs: i32) -> T
192where
193 T: Timelike + Add<OldDuration, Output = T>,
194{
195 let nanos = lhs.nanosecond();
197 let lhs = lhs.with_nanosecond(0).unwrap();
198 (lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap()
199}
200
201impl Add<FixedOffset> for NaiveTime {
202 type Output = NaiveTime;
203
204 #[inline]
205 fn add(self, rhs: FixedOffset) -> NaiveTime {
206 add_with_leapsecond(&self, rhs.local_minus_utc)
207 }
208}
209
210impl Sub<FixedOffset> for NaiveTime {
211 type Output = NaiveTime;
212
213 #[inline]
214 fn sub(self, rhs: FixedOffset) -> NaiveTime {
215 add_with_leapsecond(&self, -rhs.local_minus_utc)
216 }
217}
218
219impl Add<FixedOffset> for NaiveDateTime {
220 type Output = NaiveDateTime;
221
222 #[inline]
223 fn add(self, rhs: FixedOffset) -> NaiveDateTime {
224 add_with_leapsecond(&self, rhs.local_minus_utc)
225 }
226}
227
228impl Sub<FixedOffset> for NaiveDateTime {
229 type Output = NaiveDateTime;
230
231 #[inline]
232 fn sub(self, rhs: FixedOffset) -> NaiveDateTime {
233 add_with_leapsecond(&self, -rhs.local_minus_utc)
234 }
235}
236
237impl<Tz: TimeZone> Add<FixedOffset> for DateTime<Tz> {
238 type Output = DateTime<Tz>;
239
240 #[inline]
241 fn add(self, rhs: FixedOffset) -> DateTime<Tz> {
242 add_with_leapsecond(&self, rhs.local_minus_utc)
243 }
244}
245
246impl<Tz: TimeZone> Sub<FixedOffset> for DateTime<Tz> {
247 type Output = DateTime<Tz>;
248
249 #[inline]
250 fn sub(self, rhs: FixedOffset) -> DateTime<Tz> {
251 add_with_leapsecond(&self, -rhs.local_minus_utc)
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::FixedOffset;
258 use crate::offset::TimeZone;
259 use std::str::FromStr;
260
261 #[test]
262 fn test_date_extreme_offset() {
263 let offset = FixedOffset::east_opt(86399).unwrap();
266 assert_eq!(
267 format!("{:?}", offset.with_ymd_and_hms(2012, 2, 29, 5, 6, 7).unwrap()),
268 "2012-02-29T05:06:07+23:59:59"
269 );
270 let offset = FixedOffset::east_opt(-86399).unwrap();
271 assert_eq!(
272 format!("{:?}", offset.with_ymd_and_hms(2012, 2, 29, 5, 6, 7).unwrap()),
273 "2012-02-29T05:06:07-23:59:59"
274 );
275 let offset = FixedOffset::west_opt(86399).unwrap();
276 assert_eq!(
277 format!("{:?}", offset.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap()),
278 "2012-03-04T05:06:07-23:59:59"
279 );
280 let offset = FixedOffset::west_opt(-86399).unwrap();
281 assert_eq!(
282 format!("{:?}", offset.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap()),
283 "2012-03-04T05:06:07+23:59:59"
284 );
285 }
286
287 #[test]
288 fn test_parse_offset() {
289 let offset = FixedOffset::from_str("-0500").unwrap();
290 assert_eq!(offset.local_minus_utc, -5 * 3600);
291 let offset = FixedOffset::from_str("-08:00").unwrap();
292 assert_eq!(offset.local_minus_utc, -8 * 3600);
293 let offset = FixedOffset::from_str("+06:30").unwrap();
294 assert_eq!(offset.local_minus_utc, (6 * 3600) + 1800);
295 }
296}