/rust/registry/src/index.crates.io-1949cf8c6b5b557f/chrono-0.4.42/src/offset/fixed.rs
Line | Count | Source |
1 | | // This is a part of Chrono. |
2 | | // See README.md and LICENSE.txt for details. |
3 | | |
4 | | //! The time zone which has a fixed offset from UTC. |
5 | | |
6 | | use core::fmt; |
7 | | use core::str::FromStr; |
8 | | |
9 | | #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] |
10 | | use rkyv::{Archive, Deserialize, Serialize}; |
11 | | |
12 | | use super::{MappedLocalTime, Offset, TimeZone}; |
13 | | use crate::format::{OUT_OF_RANGE, ParseError, scan}; |
14 | | use crate::naive::{NaiveDate, NaiveDateTime}; |
15 | | |
16 | | /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. |
17 | | /// |
18 | | /// Using the [`TimeZone`](./trait.TimeZone.html) methods |
19 | | /// on a `FixedOffset` struct is the preferred way to construct |
20 | | /// `DateTime<FixedOffset>` instances. See the [`east_opt`](#method.east_opt) and |
21 | | /// [`west_opt`](#method.west_opt) methods for examples. |
22 | | #[derive(PartialEq, Eq, Hash, Copy, Clone)] |
23 | | #[cfg_attr( |
24 | | any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), |
25 | | derive(Archive, Deserialize, Serialize), |
26 | | archive(compare(PartialEq)), |
27 | | archive_attr(derive(Clone, Copy, PartialEq, Eq, Hash, Debug)) |
28 | | )] |
29 | | #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] |
30 | | pub struct FixedOffset { |
31 | | local_minus_utc: i32, |
32 | | } |
33 | | |
34 | | impl FixedOffset { |
35 | | /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference. |
36 | | /// The negative `secs` means the Western Hemisphere. |
37 | | /// |
38 | | /// Panics on the out-of-bound `secs`. |
39 | | #[deprecated(since = "0.4.23", note = "use `east_opt()` instead")] |
40 | | #[must_use] |
41 | 0 | pub fn east(secs: i32) -> FixedOffset { |
42 | 0 | FixedOffset::east_opt(secs).expect("FixedOffset::east out of bounds") |
43 | 0 | } |
44 | | |
45 | | /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference. |
46 | | /// The negative `secs` means the Western Hemisphere. |
47 | | /// |
48 | | /// Returns `None` on the out-of-bound `secs`. |
49 | | /// |
50 | | /// # Example |
51 | | /// |
52 | | /// ``` |
53 | | /// # #[cfg(feature = "alloc")] { |
54 | | /// use chrono::{FixedOffset, TimeZone}; |
55 | | /// let hour = 3600; |
56 | | /// let datetime = |
57 | | /// FixedOffset::east_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap(); |
58 | | /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00") |
59 | | /// # } |
60 | | /// ``` |
61 | | #[must_use] |
62 | 0 | pub const fn east_opt(secs: i32) -> Option<FixedOffset> { |
63 | 0 | if -86_400 < secs && secs < 86_400 { |
64 | 0 | Some(FixedOffset { local_minus_utc: secs }) |
65 | | } else { |
66 | 0 | None |
67 | | } |
68 | 0 | } |
69 | | |
70 | | /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference. |
71 | | /// The negative `secs` means the Eastern Hemisphere. |
72 | | /// |
73 | | /// Panics on the out-of-bound `secs`. |
74 | | #[deprecated(since = "0.4.23", note = "use `west_opt()` instead")] |
75 | | #[must_use] |
76 | 0 | pub fn west(secs: i32) -> FixedOffset { |
77 | 0 | FixedOffset::west_opt(secs).expect("FixedOffset::west out of bounds") |
78 | 0 | } |
79 | | |
80 | | /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference. |
81 | | /// The negative `secs` means the Eastern Hemisphere. |
82 | | /// |
83 | | /// Returns `None` on the out-of-bound `secs`. |
84 | | /// |
85 | | /// # Example |
86 | | /// |
87 | | /// ``` |
88 | | /// # #[cfg(feature = "alloc")] { |
89 | | /// use chrono::{FixedOffset, TimeZone}; |
90 | | /// let hour = 3600; |
91 | | /// let datetime = |
92 | | /// FixedOffset::west_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap(); |
93 | | /// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00") |
94 | | /// # } |
95 | | /// ``` |
96 | | #[must_use] |
97 | 0 | pub const fn west_opt(secs: i32) -> Option<FixedOffset> { |
98 | 0 | if -86_400 < secs && secs < 86_400 { |
99 | 0 | Some(FixedOffset { local_minus_utc: -secs }) |
100 | | } else { |
101 | 0 | None |
102 | | } |
103 | 0 | } |
104 | | |
105 | | /// Returns the number of seconds to add to convert from UTC to the local time. |
106 | | #[inline] |
107 | 0 | pub const fn local_minus_utc(&self) -> i32 { |
108 | 0 | self.local_minus_utc |
109 | 0 | } Unexecuted instantiation: <chrono::offset::fixed::FixedOffset>::local_minus_utc Unexecuted instantiation: <chrono::offset::fixed::FixedOffset>::local_minus_utc |
110 | | |
111 | | /// Returns the number of seconds to add to convert from the local time to UTC. |
112 | | #[inline] |
113 | 0 | pub const fn utc_minus_local(&self) -> i32 { |
114 | 0 | -self.local_minus_utc |
115 | 0 | } |
116 | | } |
117 | | |
118 | | /// Parsing a `str` into a `FixedOffset` uses the format [`%z`](crate::format::strftime). |
119 | | impl FromStr for FixedOffset { |
120 | | type Err = ParseError; |
121 | 0 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
122 | 0 | let (_, offset) = scan::timezone_offset(s, scan::colon_or_space, false, false, true)?; |
123 | 0 | Self::east_opt(offset).ok_or(OUT_OF_RANGE) |
124 | 0 | } |
125 | | } |
126 | | |
127 | | impl TimeZone for FixedOffset { |
128 | | type Offset = FixedOffset; |
129 | | |
130 | 0 | fn from_offset(offset: &FixedOffset) -> FixedOffset { |
131 | 0 | *offset |
132 | 0 | } |
133 | | |
134 | 0 | fn offset_from_local_date(&self, _local: &NaiveDate) -> MappedLocalTime<FixedOffset> { |
135 | 0 | MappedLocalTime::Single(*self) |
136 | 0 | } |
137 | 0 | fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> MappedLocalTime<FixedOffset> { |
138 | 0 | MappedLocalTime::Single(*self) |
139 | 0 | } |
140 | | |
141 | 0 | fn offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset { |
142 | 0 | *self |
143 | 0 | } |
144 | 0 | fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> FixedOffset { |
145 | 0 | *self |
146 | 0 | } |
147 | | } |
148 | | |
149 | | impl Offset for FixedOffset { |
150 | 0 | fn fix(&self) -> FixedOffset { |
151 | 0 | *self |
152 | 0 | } |
153 | | } |
154 | | |
155 | | impl fmt::Debug for FixedOffset { |
156 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
157 | 0 | let offset = self.local_minus_utc; |
158 | 0 | let (sign, offset) = if offset < 0 { ('-', -offset) } else { ('+', offset) }; |
159 | 0 | let sec = offset.rem_euclid(60); |
160 | 0 | let mins = offset.div_euclid(60); |
161 | 0 | let min = mins.rem_euclid(60); |
162 | 0 | let hour = mins.div_euclid(60); |
163 | 0 | if sec == 0 { |
164 | 0 | write!(f, "{sign}{hour:02}:{min:02}") |
165 | | } else { |
166 | 0 | write!(f, "{sign}{hour:02}:{min:02}:{sec:02}") |
167 | | } |
168 | 0 | } |
169 | | } |
170 | | |
171 | | impl fmt::Display for FixedOffset { |
172 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
173 | 0 | fmt::Debug::fmt(self, f) |
174 | 0 | } |
175 | | } |
176 | | |
177 | | #[cfg(all(feature = "arbitrary", feature = "std"))] |
178 | | impl arbitrary::Arbitrary<'_> for FixedOffset { |
179 | | fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<FixedOffset> { |
180 | | let secs = u.int_in_range(-86_399..=86_399)?; |
181 | | let fixed_offset = FixedOffset::east_opt(secs) |
182 | | .expect("Could not generate a valid chrono::FixedOffset. It looks like implementation of Arbitrary for FixedOffset is erroneous."); |
183 | | Ok(fixed_offset) |
184 | | } |
185 | | } |
186 | | |
187 | | #[cfg(test)] |
188 | | mod tests { |
189 | | use super::FixedOffset; |
190 | | use crate::offset::TimeZone; |
191 | | use std::str::FromStr; |
192 | | |
193 | | #[test] |
194 | | fn test_date_extreme_offset() { |
195 | | // starting from 0.3 we don't have an offset exceeding one day. |
196 | | // this makes everything easier! |
197 | | let offset = FixedOffset::east_opt(86399).unwrap(); |
198 | | assert_eq!( |
199 | | format!("{:?}", offset.with_ymd_and_hms(2012, 2, 29, 5, 6, 7).unwrap()), |
200 | | "2012-02-29T05:06:07+23:59:59" |
201 | | ); |
202 | | let offset = FixedOffset::east_opt(-86399).unwrap(); |
203 | | assert_eq!( |
204 | | format!("{:?}", offset.with_ymd_and_hms(2012, 2, 29, 5, 6, 7).unwrap()), |
205 | | "2012-02-29T05:06:07-23:59:59" |
206 | | ); |
207 | | let offset = FixedOffset::west_opt(86399).unwrap(); |
208 | | assert_eq!( |
209 | | format!("{:?}", offset.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap()), |
210 | | "2012-03-04T05:06:07-23:59:59" |
211 | | ); |
212 | | let offset = FixedOffset::west_opt(-86399).unwrap(); |
213 | | assert_eq!( |
214 | | format!("{:?}", offset.with_ymd_and_hms(2012, 3, 4, 5, 6, 7).unwrap()), |
215 | | "2012-03-04T05:06:07+23:59:59" |
216 | | ); |
217 | | } |
218 | | |
219 | | #[test] |
220 | | fn test_parse_offset() { |
221 | | let offset = FixedOffset::from_str("-0500").unwrap(); |
222 | | assert_eq!(offset.local_minus_utc, -5 * 3600); |
223 | | let offset = FixedOffset::from_str("-08:00").unwrap(); |
224 | | assert_eq!(offset.local_minus_utc, -8 * 3600); |
225 | | let offset = FixedOffset::from_str("+06:30").unwrap(); |
226 | | assert_eq!(offset.local_minus_utc, (6 * 3600) + 1800); |
227 | | } |
228 | | |
229 | | #[test] |
230 | | #[cfg(feature = "rkyv-validation")] |
231 | | fn test_rkyv_validation() { |
232 | | let offset = FixedOffset::from_str("-0500").unwrap(); |
233 | | let bytes = rkyv::to_bytes::<_, 4>(&offset).unwrap(); |
234 | | assert_eq!(rkyv::from_bytes::<FixedOffset>(&bytes).unwrap(), offset); |
235 | | } |
236 | | } |