/rust/registry/src/index.crates.io-1949cf8c6b5b557f/time-0.3.14/src/utc_offset.rs
Line | Count | Source |
1 | | //! The [`UtcOffset`] struct and its associated `impl`s. |
2 | | |
3 | | use core::fmt; |
4 | | use core::ops::Neg; |
5 | | #[cfg(feature = "formatting")] |
6 | | use std::io; |
7 | | |
8 | | use crate::error; |
9 | | #[cfg(feature = "formatting")] |
10 | | use crate::formatting::Formattable; |
11 | | #[cfg(feature = "parsing")] |
12 | | use crate::parsing::Parsable; |
13 | | #[cfg(feature = "local-offset")] |
14 | | use crate::sys::local_offset_at; |
15 | | #[cfg(feature = "local-offset")] |
16 | | use crate::OffsetDateTime; |
17 | | |
18 | | /// An offset from UTC. |
19 | | /// |
20 | | /// This struct can store values up to ±23:59:59. If you need support outside this range, please |
21 | | /// file an issue with your use case. |
22 | | // All three components _must_ have the same sign. |
23 | | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] |
24 | | pub struct UtcOffset { |
25 | | #[allow(clippy::missing_docs_in_private_items)] |
26 | | hours: i8, |
27 | | #[allow(clippy::missing_docs_in_private_items)] |
28 | | minutes: i8, |
29 | | #[allow(clippy::missing_docs_in_private_items)] |
30 | | seconds: i8, |
31 | | } |
32 | | |
33 | | impl UtcOffset { |
34 | | /// A `UtcOffset` that is UTC. |
35 | | /// |
36 | | /// ```rust |
37 | | /// # use time::{UtcOffset, macros::offset}; |
38 | | /// assert_eq!(UtcOffset::UTC, offset!(UTC)); |
39 | | /// ``` |
40 | | pub const UTC: Self = Self::__from_hms_unchecked(0, 0, 0); |
41 | | |
42 | | // region: constructors |
43 | | /// Create a `UtcOffset` representing an offset of the hours, minutes, and seconds provided, the |
44 | | /// validity of which must be guaranteed by the caller. All three parameters must have the same |
45 | | /// sign. |
46 | | #[doc(hidden)] |
47 | 0 | pub const fn __from_hms_unchecked(hours: i8, minutes: i8, seconds: i8) -> Self { |
48 | 0 | if hours < 0 { |
49 | 0 | debug_assert!(minutes <= 0); |
50 | 0 | debug_assert!(seconds <= 0); |
51 | 0 | } else if hours > 0 { |
52 | 0 | debug_assert!(minutes >= 0); |
53 | 0 | debug_assert!(seconds >= 0); |
54 | 0 | } |
55 | 0 | if minutes < 0 { |
56 | 0 | debug_assert!(seconds <= 0); |
57 | 0 | } else if minutes > 0 { |
58 | 0 | debug_assert!(seconds >= 0); |
59 | 0 | } |
60 | 0 | debug_assert!(hours.unsigned_abs() < 24); |
61 | 0 | debug_assert!(minutes.unsigned_abs() < 60); |
62 | 0 | debug_assert!(seconds.unsigned_abs() < 60); |
63 | | |
64 | 0 | Self { |
65 | 0 | hours, |
66 | 0 | minutes, |
67 | 0 | seconds, |
68 | 0 | } |
69 | 0 | } |
70 | | |
71 | | /// Create a `UtcOffset` representing an offset by the number of hours, minutes, and seconds |
72 | | /// provided. |
73 | | /// |
74 | | /// The sign of all three components should match. If they do not, all smaller components will |
75 | | /// have their signs flipped. |
76 | | /// |
77 | | /// ```rust |
78 | | /// # use time::UtcOffset; |
79 | | /// assert_eq!(UtcOffset::from_hms(1, 2, 3)?.as_hms(), (1, 2, 3)); |
80 | | /// assert_eq!(UtcOffset::from_hms(1, -2, -3)?.as_hms(), (1, 2, 3)); |
81 | | /// # Ok::<_, time::Error>(()) |
82 | | /// ``` |
83 | 0 | pub const fn from_hms( |
84 | 0 | hours: i8, |
85 | 0 | mut minutes: i8, |
86 | 0 | mut seconds: i8, |
87 | 0 | ) -> Result<Self, error::ComponentRange> { |
88 | 0 | ensure_value_in_range!(hours in -23 => 23); |
89 | 0 | ensure_value_in_range!(minutes in -59 => 59); |
90 | 0 | ensure_value_in_range!(seconds in -59 => 59); |
91 | | |
92 | 0 | if (hours > 0 && minutes < 0) || (hours < 0 && minutes > 0) { |
93 | 0 | minutes *= -1; |
94 | 0 | } |
95 | 0 | if (hours > 0 && seconds < 0) |
96 | 0 | || (hours < 0 && seconds > 0) |
97 | 0 | || (minutes > 0 && seconds < 0) |
98 | 0 | || (minutes < 0 && seconds > 0) |
99 | 0 | { |
100 | 0 | seconds *= -1; |
101 | 0 | } |
102 | | |
103 | 0 | Ok(Self::__from_hms_unchecked(hours, minutes, seconds)) |
104 | 0 | } |
105 | | |
106 | | /// Create a `UtcOffset` representing an offset by the number of seconds provided. |
107 | | /// |
108 | | /// ```rust |
109 | | /// # use time::UtcOffset; |
110 | | /// assert_eq!(UtcOffset::from_whole_seconds(3_723)?.as_hms(), (1, 2, 3)); |
111 | | /// # Ok::<_, time::Error>(()) |
112 | | /// ``` |
113 | 0 | pub const fn from_whole_seconds(seconds: i32) -> Result<Self, error::ComponentRange> { |
114 | 0 | ensure_value_in_range!(seconds in -86_399 => 86_399); |
115 | | |
116 | 0 | Ok(Self::__from_hms_unchecked( |
117 | 0 | (seconds / 3_600) as _, |
118 | 0 | ((seconds / 60) % 60) as _, |
119 | 0 | (seconds % 60) as _, |
120 | 0 | )) |
121 | 0 | } |
122 | | // endregion constructors |
123 | | |
124 | | // region: getters |
125 | | /// Obtain the UTC offset as its hours, minutes, and seconds. The sign of all three components |
126 | | /// will always match. A positive value indicates an offset to the east; a negative to the west. |
127 | | /// |
128 | | /// ```rust |
129 | | /// # use time::macros::offset; |
130 | | /// assert_eq!(offset!(+1:02:03).as_hms(), (1, 2, 3)); |
131 | | /// assert_eq!(offset!(-1:02:03).as_hms(), (-1, -2, -3)); |
132 | | /// ``` |
133 | 0 | pub const fn as_hms(self) -> (i8, i8, i8) { |
134 | 0 | (self.hours, self.minutes, self.seconds) |
135 | 0 | } |
136 | | |
137 | | /// Obtain the number of whole hours the offset is from UTC. A positive value indicates an |
138 | | /// offset to the east; a negative to the west. |
139 | | /// |
140 | | /// ```rust |
141 | | /// # use time::macros::offset; |
142 | | /// assert_eq!(offset!(+1:02:03).whole_hours(), 1); |
143 | | /// assert_eq!(offset!(-1:02:03).whole_hours(), -1); |
144 | | /// ``` |
145 | 0 | pub const fn whole_hours(self) -> i8 { |
146 | 0 | self.hours |
147 | 0 | } |
148 | | |
149 | | /// Obtain the number of whole minutes the offset is from UTC. A positive value indicates an |
150 | | /// offset to the east; a negative to the west. |
151 | | /// |
152 | | /// ```rust |
153 | | /// # use time::macros::offset; |
154 | | /// assert_eq!(offset!(+1:02:03).whole_minutes(), 62); |
155 | | /// assert_eq!(offset!(-1:02:03).whole_minutes(), -62); |
156 | | /// ``` |
157 | 0 | pub const fn whole_minutes(self) -> i16 { |
158 | 0 | self.hours as i16 * 60 + self.minutes as i16 |
159 | 0 | } |
160 | | |
161 | | /// Obtain the number of minutes past the hour the offset is from UTC. A positive value |
162 | | /// indicates an offset to the east; a negative to the west. |
163 | | /// |
164 | | /// ```rust |
165 | | /// # use time::macros::offset; |
166 | | /// assert_eq!(offset!(+1:02:03).minutes_past_hour(), 2); |
167 | | /// assert_eq!(offset!(-1:02:03).minutes_past_hour(), -2); |
168 | | /// ``` |
169 | 0 | pub const fn minutes_past_hour(self) -> i8 { |
170 | 0 | self.minutes |
171 | 0 | } |
172 | | |
173 | | /// Obtain the number of whole seconds the offset is from UTC. A positive value indicates an |
174 | | /// offset to the east; a negative to the west. |
175 | | /// |
176 | | /// ```rust |
177 | | /// # use time::macros::offset; |
178 | | /// assert_eq!(offset!(+1:02:03).whole_seconds(), 3723); |
179 | | /// assert_eq!(offset!(-1:02:03).whole_seconds(), -3723); |
180 | | /// ``` |
181 | | // This may be useful for anyone manually implementing arithmetic, as it |
182 | | // would let them construct a `Duration` directly. |
183 | 0 | pub const fn whole_seconds(self) -> i32 { |
184 | 0 | self.hours as i32 * 3_600 + self.minutes as i32 * 60 + self.seconds as i32 |
185 | 0 | } |
186 | | |
187 | | /// Obtain the number of seconds past the minute the offset is from UTC. A positive value |
188 | | /// indicates an offset to the east; a negative to the west. |
189 | | /// |
190 | | /// ```rust |
191 | | /// # use time::macros::offset; |
192 | | /// assert_eq!(offset!(+1:02:03).seconds_past_minute(), 3); |
193 | | /// assert_eq!(offset!(-1:02:03).seconds_past_minute(), -3); |
194 | | /// ``` |
195 | 0 | pub const fn seconds_past_minute(self) -> i8 { |
196 | 0 | self.seconds |
197 | 0 | } |
198 | | // endregion getters |
199 | | |
200 | | // region: is_{sign} |
201 | | /// Check if the offset is exactly UTC. |
202 | | /// |
203 | | /// |
204 | | /// ```rust |
205 | | /// # use time::macros::offset; |
206 | | /// assert!(!offset!(+1:02:03).is_utc()); |
207 | | /// assert!(!offset!(-1:02:03).is_utc()); |
208 | | /// assert!(offset!(UTC).is_utc()); |
209 | | /// ``` |
210 | 0 | pub const fn is_utc(self) -> bool { |
211 | 0 | self.hours == 0 && self.minutes == 0 && self.seconds == 0 |
212 | 0 | } |
213 | | |
214 | | /// Check if the offset is positive, or east of UTC. |
215 | | /// |
216 | | /// ```rust |
217 | | /// # use time::macros::offset; |
218 | | /// assert!(offset!(+1:02:03).is_positive()); |
219 | | /// assert!(!offset!(-1:02:03).is_positive()); |
220 | | /// assert!(!offset!(UTC).is_positive()); |
221 | | /// ``` |
222 | 0 | pub const fn is_positive(self) -> bool { |
223 | 0 | self.hours > 0 || self.minutes > 0 || self.seconds > 0 |
224 | 0 | } |
225 | | |
226 | | /// Check if the offset is negative, or west of UTC. |
227 | | /// |
228 | | /// ```rust |
229 | | /// # use time::macros::offset; |
230 | | /// assert!(!offset!(+1:02:03).is_negative()); |
231 | | /// assert!(offset!(-1:02:03).is_negative()); |
232 | | /// assert!(!offset!(UTC).is_negative()); |
233 | | /// ``` |
234 | 0 | pub const fn is_negative(self) -> bool { |
235 | 0 | self.hours < 0 || self.minutes < 0 || self.seconds < 0 |
236 | 0 | } |
237 | | // endregion is_{sign} |
238 | | |
239 | | // region: local offset |
240 | | /// Attempt to obtain the system's UTC offset at a known moment in time. If the offset cannot be |
241 | | /// determined, an error is returned. |
242 | | /// |
243 | | /// ```rust |
244 | | /// # use time::{UtcOffset, OffsetDateTime}; |
245 | | /// let local_offset = UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH); |
246 | | /// # if false { |
247 | | /// assert!(local_offset.is_ok()); |
248 | | /// # } |
249 | | /// ``` |
250 | | #[cfg(feature = "local-offset")] |
251 | 0 | pub fn local_offset_at(datetime: OffsetDateTime) -> Result<Self, error::IndeterminateOffset> { |
252 | 0 | local_offset_at(datetime).ok_or(error::IndeterminateOffset) |
253 | 0 | } |
254 | | |
255 | | /// Attempt to obtain the system's current UTC offset. If the offset cannot be determined, an |
256 | | /// error is returned. |
257 | | /// |
258 | | /// ```rust |
259 | | /// # use time::UtcOffset; |
260 | | /// let local_offset = UtcOffset::current_local_offset(); |
261 | | /// # if false { |
262 | | /// assert!(local_offset.is_ok()); |
263 | | /// # } |
264 | | /// ``` |
265 | | #[cfg(feature = "local-offset")] |
266 | 0 | pub fn current_local_offset() -> Result<Self, error::IndeterminateOffset> { |
267 | 0 | let now = OffsetDateTime::now_utc(); |
268 | 0 | local_offset_at(now).ok_or(error::IndeterminateOffset) |
269 | 0 | } |
270 | | // endregion: local offset |
271 | | } |
272 | | |
273 | | // region: formatting & parsing |
274 | | #[cfg(feature = "formatting")] |
275 | | impl UtcOffset { |
276 | | /// Format the `UtcOffset` using the provided [format description](crate::format_description). |
277 | 0 | pub fn format_into( |
278 | 0 | self, |
279 | 0 | output: &mut impl io::Write, |
280 | 0 | format: &(impl Formattable + ?Sized), |
281 | 0 | ) -> Result<usize, error::Format> { |
282 | 0 | format.format_into(output, None, None, Some(self)) |
283 | 0 | } |
284 | | |
285 | | /// Format the `UtcOffset` using the provided [format description](crate::format_description). |
286 | | /// |
287 | | /// ```rust |
288 | | /// # use time::{format_description, macros::offset}; |
289 | | /// let format = format_description::parse("[offset_hour sign:mandatory]:[offset_minute]")?; |
290 | | /// assert_eq!(offset!(+1).format(&format)?, "+01:00"); |
291 | | /// # Ok::<_, time::Error>(()) |
292 | | /// ``` |
293 | 0 | pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> { |
294 | 0 | format.format(None, None, Some(self)) |
295 | 0 | } |
296 | | } |
297 | | |
298 | | #[cfg(feature = "parsing")] |
299 | | impl UtcOffset { |
300 | | /// Parse a `UtcOffset` from the input using the provided [format |
301 | | /// description](crate::format_description). |
302 | | /// |
303 | | /// ```rust |
304 | | /// # use time::{format_description, macros::offset, UtcOffset}; |
305 | | /// let format = format_description::parse("[offset_hour]:[offset_minute]")?; |
306 | | /// assert_eq!(UtcOffset::parse("-03:42", &format)?, offset!(-3:42)); |
307 | | /// # Ok::<_, time::Error>(()) |
308 | | /// ``` |
309 | | pub fn parse( |
310 | | input: &str, |
311 | | description: &(impl Parsable + ?Sized), |
312 | | ) -> Result<Self, error::Parse> { |
313 | | description.parse_offset(input.as_bytes()) |
314 | | } |
315 | | } |
316 | | |
317 | | impl fmt::Display for UtcOffset { |
318 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
319 | 0 | write!( |
320 | 0 | f, |
321 | 0 | "{}{:02}:{:02}:{:02}", |
322 | 0 | if self.is_negative() { '-' } else { '+' }, |
323 | 0 | self.hours.abs(), |
324 | 0 | self.minutes.abs(), |
325 | 0 | self.seconds.abs() |
326 | | ) |
327 | 0 | } |
328 | | } |
329 | | // endregion formatting & parsing |
330 | | |
331 | | impl Neg for UtcOffset { |
332 | | type Output = Self; |
333 | | |
334 | 0 | fn neg(self) -> Self::Output { |
335 | 0 | Self::__from_hms_unchecked(-self.hours, -self.minutes, -self.seconds) |
336 | 0 | } |
337 | | } |