Coverage Report

Created: 2025-12-12 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}