1 | //! The [`OffsetDateTime`] struct and its associated `impl`s. |
2 | |
3 | #[cfg (feature = "formatting" )] |
4 | use alloc::string::String; |
5 | use core::cmp::Ordering; |
6 | use core::fmt; |
7 | use core::hash::Hash; |
8 | use core::ops::{Add, AddAssign, Sub, SubAssign}; |
9 | use core::time::Duration as StdDuration; |
10 | #[cfg (feature = "formatting" )] |
11 | use std::io; |
12 | #[cfg (feature = "std" )] |
13 | use std::time::SystemTime; |
14 | |
15 | use deranged::RangedI64; |
16 | use num_conv::prelude::*; |
17 | use powerfmt::ext::FormatterExt as _; |
18 | use powerfmt::smart_display::{self, FormatterOptions, Metadata, SmartDisplay}; |
19 | use time_core::convert::*; |
20 | |
21 | use crate::date::{MAX_YEAR, MIN_YEAR}; |
22 | #[cfg (feature = "formatting" )] |
23 | use crate::formatting::Formattable; |
24 | use crate::internal_macros::{ |
25 | cascade, const_try, const_try_opt, div_floor, ensure_ranged, expect_opt, |
26 | }; |
27 | #[cfg (feature = "parsing" )] |
28 | use crate::parsing::Parsable; |
29 | use crate::{error, util, Date, Duration, Month, PrimitiveDateTime, Time, UtcOffset, Weekday}; |
30 | |
31 | /// The Julian day of the Unix epoch. |
32 | // Safety: `ordinal` is not zero. |
33 | #[allow (clippy::undocumented_unsafe_blocks)] |
34 | const UNIX_EPOCH_JULIAN_DAY: i32 = |
35 | unsafe { Date::__from_ordinal_date_unchecked(year:1970, ordinal:1) }.to_julian_day(); |
36 | |
37 | /// A [`PrimitiveDateTime`] with a [`UtcOffset`]. |
38 | /// |
39 | /// All comparisons are performed using the UTC time. |
40 | #[derive (Clone, Copy, Eq)] |
41 | pub struct OffsetDateTime { |
42 | local_date_time: PrimitiveDateTime, |
43 | offset: UtcOffset, |
44 | } |
45 | |
46 | impl PartialEq for OffsetDateTime { |
47 | fn eq(&self, other: &Self) -> bool { |
48 | self.to_offset_raw(offset:UtcOffset::UTC) == other.to_offset_raw(offset:UtcOffset::UTC) |
49 | } |
50 | } |
51 | |
52 | impl PartialOrd for OffsetDateTime { |
53 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
54 | Some(self.cmp(other)) |
55 | } |
56 | } |
57 | |
58 | impl Ord for OffsetDateTime { |
59 | fn cmp(&self, other: &Self) -> Ordering { |
60 | self.to_offset_raw(offset:UtcOffset::UTC) |
61 | .cmp(&other.to_offset_raw(offset:UtcOffset::UTC)) |
62 | } |
63 | } |
64 | |
65 | impl Hash for OffsetDateTime { |
66 | fn hash<H: core::hash::Hasher>(&self, state: &mut H) { |
67 | self.to_offset_raw(offset:UtcOffset::UTC).hash(state); |
68 | } |
69 | } |
70 | |
71 | impl OffsetDateTime { |
72 | /// Midnight, 1 January, 1970 (UTC). |
73 | /// |
74 | /// ```rust |
75 | /// # use time::OffsetDateTime; |
76 | /// # use time_macros::datetime; |
77 | /// assert_eq!(OffsetDateTime::UNIX_EPOCH, datetime!(1970-01-01 0:00 UTC)); |
78 | /// ``` |
79 | pub const UNIX_EPOCH: Self = Self::new_in_offset( |
80 | // Safety: `ordinal` is not zero. |
81 | unsafe { Date::__from_ordinal_date_unchecked(1970, 1) }, |
82 | Time::MIDNIGHT, |
83 | UtcOffset::UTC, |
84 | ); |
85 | |
86 | // region: now |
87 | /// Create a new `OffsetDateTime` with the current date and time in UTC. |
88 | /// |
89 | /// ```rust |
90 | /// # use time::OffsetDateTime; |
91 | /// # use time_macros::offset; |
92 | /// assert!(OffsetDateTime::now_utc().year() >= 2019); |
93 | /// assert_eq!(OffsetDateTime::now_utc().offset(), offset!(UTC)); |
94 | /// ``` |
95 | #[cfg (feature = "std" )] |
96 | pub fn now_utc() -> Self { |
97 | #[cfg (all( |
98 | target_family = "wasm" , |
99 | not(any(target_os = "emscripten" , target_os = "wasi" )), |
100 | feature = "wasm-bindgen" |
101 | ))] |
102 | { |
103 | js_sys::Date::new_0().into() |
104 | } |
105 | |
106 | #[cfg (not(all( |
107 | target_family = "wasm" , |
108 | not(any(target_os = "emscripten" , target_os = "wasi" )), |
109 | feature = "wasm-bindgen" |
110 | )))] |
111 | SystemTime::now().into() |
112 | } |
113 | |
114 | /// Attempt to create a new `OffsetDateTime` with the current date and time in the local offset. |
115 | /// If the offset cannot be determined, an error is returned. |
116 | /// |
117 | /// ```rust |
118 | /// # use time::OffsetDateTime; |
119 | /// # if false { |
120 | /// assert!(OffsetDateTime::now_local().is_ok()); |
121 | /// # } |
122 | /// ``` |
123 | #[cfg (feature = "local-offset" )] |
124 | pub fn now_local() -> Result<Self, error::IndeterminateOffset> { |
125 | let t = Self::now_utc(); |
126 | Ok(t.to_offset(UtcOffset::local_offset_at(t)?)) |
127 | } |
128 | // endregion now |
129 | |
130 | /// Create a new `OffsetDateTime` with the given [`Date`], [`Time`], and [`UtcOffset`]. |
131 | /// |
132 | /// ``` |
133 | /// # use time::{Date, Month, OffsetDateTime, Time, UtcOffset}; |
134 | /// # use time_macros::datetime; |
135 | /// let dt = OffsetDateTime::new_in_offset( |
136 | /// Date::from_calendar_date(2024, Month::January, 1)?, |
137 | /// Time::from_hms_nano(12, 59, 59, 500_000_000)?, |
138 | /// UtcOffset::from_hms(-5, 0, 0)?, |
139 | /// ); |
140 | /// assert_eq!(dt, datetime!(2024-01-01 12:59:59.5 -5)); |
141 | /// # Ok::<_, time::error::Error>(()) |
142 | /// ``` |
143 | pub const fn new_in_offset(date: Date, time: Time, offset: UtcOffset) -> Self { |
144 | Self { |
145 | local_date_time: date.with_time(time), |
146 | offset, |
147 | } |
148 | } |
149 | |
150 | /// Create a new `OffsetDateTime` with the given [`Date`] and [`Time`] in the UTC timezone. |
151 | /// |
152 | /// ``` |
153 | /// # use time::{Date, Month, OffsetDateTime, Time}; |
154 | /// # use time_macros::datetime; |
155 | /// let dt = OffsetDateTime::new_utc( |
156 | /// Date::from_calendar_date(2024, Month::January, 1)?, |
157 | /// Time::from_hms_nano(12, 59, 59, 500_000_000)?, |
158 | /// ); |
159 | /// assert_eq!(dt, datetime!(2024-01-01 12:59:59.5 UTC)); |
160 | /// # Ok::<_, time::error::Error>(()) |
161 | /// ``` |
162 | pub const fn new_utc(date: Date, time: Time) -> Self { |
163 | PrimitiveDateTime::new(date, time).assume_utc() |
164 | } |
165 | |
166 | /// Convert the `OffsetDateTime` from the current [`UtcOffset`] to the provided [`UtcOffset`]. |
167 | /// |
168 | /// ```rust |
169 | /// # use time_macros::{datetime, offset}; |
170 | /// assert_eq!( |
171 | /// datetime!(2000-01-01 0:00 UTC) |
172 | /// .to_offset(offset!(-1)) |
173 | /// .year(), |
174 | /// 1999, |
175 | /// ); |
176 | /// |
177 | /// // Let's see what time Sydney's new year's celebration is in New York and Los Angeles. |
178 | /// |
179 | /// // Construct midnight on new year's in Sydney. |
180 | /// let sydney = datetime!(2000-01-01 0:00 +11); |
181 | /// let new_york = sydney.to_offset(offset!(-5)); |
182 | /// let los_angeles = sydney.to_offset(offset!(-8)); |
183 | /// assert_eq!(sydney.hour(), 0); |
184 | /// assert_eq!(new_york.hour(), 8); |
185 | /// assert_eq!(los_angeles.hour(), 5); |
186 | /// ``` |
187 | /// |
188 | /// # Panics |
189 | /// |
190 | /// This method panics if the local date-time in the new offset is outside the supported range. |
191 | pub const fn to_offset(self, offset: UtcOffset) -> Self { |
192 | expect_opt!( |
193 | self.checked_to_offset(offset), |
194 | "local datetime out of valid range" |
195 | ) |
196 | } |
197 | |
198 | /// Convert the `OffsetDateTime` from the current [`UtcOffset`] to the provided [`UtcOffset`], |
199 | /// returning `None` if the date-time in the resulting offset is invalid. |
200 | /// |
201 | /// ```rust |
202 | /// # use time::PrimitiveDateTime; |
203 | /// # use time_macros::{datetime, offset}; |
204 | /// assert_eq!( |
205 | /// datetime!(2000-01-01 0:00 UTC) |
206 | /// .checked_to_offset(offset!(-1)) |
207 | /// .unwrap() |
208 | /// .year(), |
209 | /// 1999, |
210 | /// ); |
211 | /// assert_eq!( |
212 | /// PrimitiveDateTime::MAX |
213 | /// .assume_utc() |
214 | /// .checked_to_offset(offset!(+1)), |
215 | /// None, |
216 | /// ); |
217 | /// ``` |
218 | pub const fn checked_to_offset(self, offset: UtcOffset) -> Option<Self> { |
219 | if self.offset.whole_hours() == offset.whole_hours() |
220 | && self.offset.minutes_past_hour() == offset.minutes_past_hour() |
221 | && self.offset.seconds_past_minute() == offset.seconds_past_minute() |
222 | { |
223 | return Some(self.replace_offset(offset)); |
224 | } |
225 | |
226 | let (year, ordinal, time) = self.to_offset_raw(offset); |
227 | |
228 | if year > MAX_YEAR || year < MIN_YEAR { |
229 | return None; |
230 | } |
231 | |
232 | Some(Self::new_in_offset( |
233 | // Safety: `ordinal` is not zero. |
234 | unsafe { Date::__from_ordinal_date_unchecked(year, ordinal) }, |
235 | time, |
236 | offset, |
237 | )) |
238 | } |
239 | |
240 | /// Equivalent to `.to_offset(UtcOffset::UTC)`, but returning the year, ordinal, and time. This |
241 | /// avoids constructing an invalid [`Date`] if the new value is out of range. |
242 | pub(crate) const fn to_offset_raw(self, offset: UtcOffset) -> (i32, u16, Time) { |
243 | let from = self.offset; |
244 | let to = offset; |
245 | |
246 | // Fast path for when no conversion is necessary. |
247 | if from.whole_hours() == to.whole_hours() |
248 | && from.minutes_past_hour() == to.minutes_past_hour() |
249 | && from.seconds_past_minute() == to.seconds_past_minute() |
250 | { |
251 | return (self.year(), self.ordinal(), self.time()); |
252 | } |
253 | |
254 | let mut second = self.second() as i16 - from.seconds_past_minute() as i16 |
255 | + to.seconds_past_minute() as i16; |
256 | let mut minute = |
257 | self.minute() as i16 - from.minutes_past_hour() as i16 + to.minutes_past_hour() as i16; |
258 | let mut hour = self.hour() as i8 - from.whole_hours() + to.whole_hours(); |
259 | let (mut year, ordinal) = self.to_ordinal_date(); |
260 | let mut ordinal = ordinal as i16; |
261 | |
262 | // Cascade the values twice. This is needed because the values are adjusted twice above. |
263 | cascade!(second in 0..Second::per(Minute) as i16 => minute); |
264 | cascade!(second in 0..Second::per(Minute) as i16 => minute); |
265 | cascade!(minute in 0..Minute::per(Hour) as i16 => hour); |
266 | cascade!(minute in 0..Minute::per(Hour) as i16 => hour); |
267 | cascade!(hour in 0..Hour::per(Day) as i8 => ordinal); |
268 | cascade!(hour in 0..Hour::per(Day) as i8 => ordinal); |
269 | cascade!(ordinal => year); |
270 | |
271 | debug_assert!(ordinal > 0); |
272 | debug_assert!(ordinal <= util::days_in_year(year) as i16); |
273 | |
274 | ( |
275 | year, |
276 | ordinal as _, |
277 | // Safety: The cascades above ensure the values are in range. |
278 | unsafe { |
279 | Time::__from_hms_nanos_unchecked( |
280 | hour as _, |
281 | minute as _, |
282 | second as _, |
283 | self.nanosecond(), |
284 | ) |
285 | }, |
286 | ) |
287 | } |
288 | |
289 | // region: constructors |
290 | /// Create an `OffsetDateTime` from the provided Unix timestamp. Calling `.offset()` on the |
291 | /// resulting value is guaranteed to return UTC. |
292 | /// |
293 | /// ```rust |
294 | /// # use time::OffsetDateTime; |
295 | /// # use time_macros::datetime; |
296 | /// assert_eq!( |
297 | /// OffsetDateTime::from_unix_timestamp(0), |
298 | /// Ok(OffsetDateTime::UNIX_EPOCH), |
299 | /// ); |
300 | /// assert_eq!( |
301 | /// OffsetDateTime::from_unix_timestamp(1_546_300_800), |
302 | /// Ok(datetime!(2019-01-01 0:00 UTC)), |
303 | /// ); |
304 | /// ``` |
305 | /// |
306 | /// If you have a timestamp-nanosecond pair, you can use something along the lines of the |
307 | /// following: |
308 | /// |
309 | /// ```rust |
310 | /// # use time::{Duration, OffsetDateTime, ext::NumericalDuration}; |
311 | /// let (timestamp, nanos) = (1, 500_000_000); |
312 | /// assert_eq!( |
313 | /// OffsetDateTime::from_unix_timestamp(timestamp)? + Duration::nanoseconds(nanos), |
314 | /// OffsetDateTime::UNIX_EPOCH + 1.5.seconds() |
315 | /// ); |
316 | /// # Ok::<_, time::Error>(()) |
317 | /// ``` |
318 | pub const fn from_unix_timestamp(timestamp: i64) -> Result<Self, error::ComponentRange> { |
319 | type Timestamp = RangedI64< |
320 | { |
321 | OffsetDateTime::new_in_offset(Date::MIN, Time::MIDNIGHT, UtcOffset::UTC) |
322 | .unix_timestamp() |
323 | }, |
324 | { |
325 | OffsetDateTime::new_in_offset(Date::MAX, Time::MAX, UtcOffset::UTC).unix_timestamp() |
326 | }, |
327 | >; |
328 | ensure_ranged!(Timestamp: timestamp); |
329 | |
330 | // Use the unchecked method here, as the input validity has already been verified. |
331 | let date = Date::from_julian_day_unchecked( |
332 | UNIX_EPOCH_JULIAN_DAY + div_floor!(timestamp, Second::per(Day) as i64) as i32, |
333 | ); |
334 | |
335 | let seconds_within_day = timestamp.rem_euclid(Second::per(Day) as _); |
336 | // Safety: All values are in range. |
337 | let time = unsafe { |
338 | Time::__from_hms_nanos_unchecked( |
339 | (seconds_within_day / Second::per(Hour) as i64) as _, |
340 | ((seconds_within_day % Second::per(Hour) as i64) / Minute::per(Hour) as i64) as _, |
341 | (seconds_within_day % Second::per(Minute) as i64) as _, |
342 | 0, |
343 | ) |
344 | }; |
345 | |
346 | Ok(Self::new_in_offset(date, time, UtcOffset::UTC)) |
347 | } |
348 | |
349 | /// Construct an `OffsetDateTime` from the provided Unix timestamp (in nanoseconds). Calling |
350 | /// `.offset()` on the resulting value is guaranteed to return UTC. |
351 | /// |
352 | /// ```rust |
353 | /// # use time::OffsetDateTime; |
354 | /// # use time_macros::datetime; |
355 | /// assert_eq!( |
356 | /// OffsetDateTime::from_unix_timestamp_nanos(0), |
357 | /// Ok(OffsetDateTime::UNIX_EPOCH), |
358 | /// ); |
359 | /// assert_eq!( |
360 | /// OffsetDateTime::from_unix_timestamp_nanos(1_546_300_800_000_000_000), |
361 | /// Ok(datetime!(2019-01-01 0:00 UTC)), |
362 | /// ); |
363 | /// ``` |
364 | pub const fn from_unix_timestamp_nanos(timestamp: i128) -> Result<Self, error::ComponentRange> { |
365 | let datetime = const_try!(Self::from_unix_timestamp(div_floor!( |
366 | timestamp, |
367 | Nanosecond::per(Second) as i128 |
368 | ) as i64)); |
369 | |
370 | Ok(Self::new_in_offset( |
371 | datetime.date(), |
372 | // Safety: `nanosecond` is in range due to `rem_euclid`. |
373 | unsafe { |
374 | Time::__from_hms_nanos_unchecked( |
375 | datetime.hour(), |
376 | datetime.minute(), |
377 | datetime.second(), |
378 | timestamp.rem_euclid(Nanosecond::per(Second) as _) as u32, |
379 | ) |
380 | }, |
381 | UtcOffset::UTC, |
382 | )) |
383 | } |
384 | // endregion constructors |
385 | |
386 | // region: getters |
387 | /// Get the [`UtcOffset`]. |
388 | /// |
389 | /// ```rust |
390 | /// # use time_macros::{datetime, offset}; |
391 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).offset(), offset!(UTC)); |
392 | /// assert_eq!(datetime!(2019-01-01 0:00 +1).offset(), offset!(+1)); |
393 | /// ``` |
394 | pub const fn offset(self) -> UtcOffset { |
395 | self.offset |
396 | } |
397 | |
398 | /// Get the [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time). |
399 | /// |
400 | /// ```rust |
401 | /// # use time_macros::datetime; |
402 | /// assert_eq!(datetime!(1970-01-01 0:00 UTC).unix_timestamp(), 0); |
403 | /// assert_eq!(datetime!(1970-01-01 0:00 -1).unix_timestamp(), 3_600); |
404 | /// ``` |
405 | pub const fn unix_timestamp(self) -> i64 { |
406 | let days = |
407 | (self.to_julian_day() as i64 - UNIX_EPOCH_JULIAN_DAY as i64) * Second::per(Day) as i64; |
408 | let hours = self.hour() as i64 * Second::per(Hour) as i64; |
409 | let minutes = self.minute() as i64 * Second::per(Minute) as i64; |
410 | let seconds = self.second() as i64; |
411 | let offset_seconds = self.offset.whole_seconds() as i64; |
412 | days + hours + minutes + seconds - offset_seconds |
413 | } |
414 | |
415 | /// Get the Unix timestamp in nanoseconds. |
416 | /// |
417 | /// ```rust |
418 | /// use time_macros::datetime; |
419 | /// assert_eq!(datetime!(1970-01-01 0:00 UTC).unix_timestamp_nanos(), 0); |
420 | /// assert_eq!( |
421 | /// datetime!(1970-01-01 0:00 -1).unix_timestamp_nanos(), |
422 | /// 3_600_000_000_000, |
423 | /// ); |
424 | /// ``` |
425 | pub const fn unix_timestamp_nanos(self) -> i128 { |
426 | self.unix_timestamp() as i128 * Nanosecond::per(Second) as i128 + self.nanosecond() as i128 |
427 | } |
428 | |
429 | /// Get the [`PrimitiveDateTime`] in the stored offset. |
430 | const fn date_time(self) -> PrimitiveDateTime { |
431 | self.local_date_time |
432 | } |
433 | |
434 | /// Get the [`Date`] in the stored offset. |
435 | /// |
436 | /// ```rust |
437 | /// # use time_macros::{date, datetime, offset}; |
438 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).date(), date!(2019-01-01)); |
439 | /// assert_eq!( |
440 | /// datetime!(2019-01-01 0:00 UTC) |
441 | /// .to_offset(offset!(-1)) |
442 | /// .date(), |
443 | /// date!(2018-12-31), |
444 | /// ); |
445 | /// ``` |
446 | pub const fn date(self) -> Date { |
447 | self.date_time().date() |
448 | } |
449 | |
450 | /// Get the [`Time`] in the stored offset. |
451 | /// |
452 | /// ```rust |
453 | /// # use time_macros::{datetime, offset, time}; |
454 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).time(), time!(0:00)); |
455 | /// assert_eq!( |
456 | /// datetime!(2019-01-01 0:00 UTC) |
457 | /// .to_offset(offset!(-1)) |
458 | /// .time(), |
459 | /// time!(23:00) |
460 | /// ); |
461 | /// ``` |
462 | pub const fn time(self) -> Time { |
463 | self.date_time().time() |
464 | } |
465 | |
466 | // region: date getters |
467 | /// Get the year of the date in the stored offset. |
468 | /// |
469 | /// ```rust |
470 | /// # use time_macros::{datetime, offset}; |
471 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).year(), 2019); |
472 | /// assert_eq!( |
473 | /// datetime!(2019-12-31 23:00 UTC) |
474 | /// .to_offset(offset!(+1)) |
475 | /// .year(), |
476 | /// 2020, |
477 | /// ); |
478 | /// assert_eq!(datetime!(2020-01-01 0:00 UTC).year(), 2020); |
479 | /// ``` |
480 | pub const fn year(self) -> i32 { |
481 | self.date().year() |
482 | } |
483 | |
484 | /// Get the month of the date in the stored offset. |
485 | /// |
486 | /// ```rust |
487 | /// # use time::Month; |
488 | /// # use time_macros::{datetime, offset}; |
489 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).month(), Month::January); |
490 | /// assert_eq!( |
491 | /// datetime!(2019-12-31 23:00 UTC) |
492 | /// .to_offset(offset!(+1)) |
493 | /// .month(), |
494 | /// Month::January, |
495 | /// ); |
496 | /// ``` |
497 | pub const fn month(self) -> Month { |
498 | self.date().month() |
499 | } |
500 | |
501 | /// Get the day of the date in the stored offset. |
502 | /// |
503 | /// The returned value will always be in the range `1..=31`. |
504 | /// |
505 | /// ```rust |
506 | /// # use time_macros::{datetime, offset}; |
507 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).day(), 1); |
508 | /// assert_eq!( |
509 | /// datetime!(2019-12-31 23:00 UTC) |
510 | /// .to_offset(offset!(+1)) |
511 | /// .day(), |
512 | /// 1, |
513 | /// ); |
514 | /// ``` |
515 | pub const fn day(self) -> u8 { |
516 | self.date().day() |
517 | } |
518 | |
519 | /// Get the day of the year of the date in the stored offset. |
520 | /// |
521 | /// The returned value will always be in the range `1..=366`. |
522 | /// |
523 | /// ```rust |
524 | /// # use time_macros::{datetime, offset}; |
525 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).ordinal(), 1); |
526 | /// assert_eq!( |
527 | /// datetime!(2019-12-31 23:00 UTC) |
528 | /// .to_offset(offset!(+1)) |
529 | /// .ordinal(), |
530 | /// 1, |
531 | /// ); |
532 | /// ``` |
533 | pub const fn ordinal(self) -> u16 { |
534 | self.date().ordinal() |
535 | } |
536 | |
537 | /// Get the ISO week number of the date in the stored offset. |
538 | /// |
539 | /// The returned value will always be in the range `1..=53`. |
540 | /// |
541 | /// ```rust |
542 | /// # use time_macros::datetime; |
543 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).iso_week(), 1); |
544 | /// assert_eq!(datetime!(2020-01-01 0:00 UTC).iso_week(), 1); |
545 | /// assert_eq!(datetime!(2020-12-31 0:00 UTC).iso_week(), 53); |
546 | /// assert_eq!(datetime!(2021-01-01 0:00 UTC).iso_week(), 53); |
547 | /// ``` |
548 | pub const fn iso_week(self) -> u8 { |
549 | self.date().iso_week() |
550 | } |
551 | |
552 | /// Get the week number where week 1 begins on the first Sunday. |
553 | /// |
554 | /// The returned value will always be in the range `0..=53`. |
555 | /// |
556 | /// ```rust |
557 | /// # use time_macros::datetime; |
558 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).sunday_based_week(), 0); |
559 | /// assert_eq!(datetime!(2020-01-01 0:00 UTC).sunday_based_week(), 0); |
560 | /// assert_eq!(datetime!(2020-12-31 0:00 UTC).sunday_based_week(), 52); |
561 | /// assert_eq!(datetime!(2021-01-01 0:00 UTC).sunday_based_week(), 0); |
562 | /// ``` |
563 | pub const fn sunday_based_week(self) -> u8 { |
564 | self.date().sunday_based_week() |
565 | } |
566 | |
567 | /// Get the week number where week 1 begins on the first Monday. |
568 | /// |
569 | /// The returned value will always be in the range `0..=53`. |
570 | /// |
571 | /// ```rust |
572 | /// # use time_macros::datetime; |
573 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).monday_based_week(), 0); |
574 | /// assert_eq!(datetime!(2020-01-01 0:00 UTC).monday_based_week(), 0); |
575 | /// assert_eq!(datetime!(2020-12-31 0:00 UTC).monday_based_week(), 52); |
576 | /// assert_eq!(datetime!(2021-01-01 0:00 UTC).monday_based_week(), 0); |
577 | /// ``` |
578 | pub const fn monday_based_week(self) -> u8 { |
579 | self.date().monday_based_week() |
580 | } |
581 | |
582 | /// Get the year, month, and day. |
583 | /// |
584 | /// ```rust |
585 | /// # use time::Month; |
586 | /// # use time_macros::datetime; |
587 | /// assert_eq!( |
588 | /// datetime!(2019-01-01 0:00 UTC).to_calendar_date(), |
589 | /// (2019, Month::January, 1) |
590 | /// ); |
591 | /// ``` |
592 | pub const fn to_calendar_date(self) -> (i32, Month, u8) { |
593 | self.date().to_calendar_date() |
594 | } |
595 | |
596 | /// Get the year and ordinal day number. |
597 | /// |
598 | /// ```rust |
599 | /// # use time_macros::datetime; |
600 | /// assert_eq!( |
601 | /// datetime!(2019-01-01 0:00 UTC).to_ordinal_date(), |
602 | /// (2019, 1) |
603 | /// ); |
604 | /// ``` |
605 | pub const fn to_ordinal_date(self) -> (i32, u16) { |
606 | self.date().to_ordinal_date() |
607 | } |
608 | |
609 | /// Get the ISO 8601 year, week number, and weekday. |
610 | /// |
611 | /// ```rust |
612 | /// # use time::Weekday::*; |
613 | /// # use time_macros::datetime; |
614 | /// assert_eq!( |
615 | /// datetime!(2019-01-01 0:00 UTC).to_iso_week_date(), |
616 | /// (2019, 1, Tuesday) |
617 | /// ); |
618 | /// assert_eq!( |
619 | /// datetime!(2019-10-04 0:00 UTC).to_iso_week_date(), |
620 | /// (2019, 40, Friday) |
621 | /// ); |
622 | /// assert_eq!( |
623 | /// datetime!(2020-01-01 0:00 UTC).to_iso_week_date(), |
624 | /// (2020, 1, Wednesday) |
625 | /// ); |
626 | /// assert_eq!( |
627 | /// datetime!(2020-12-31 0:00 UTC).to_iso_week_date(), |
628 | /// (2020, 53, Thursday) |
629 | /// ); |
630 | /// assert_eq!( |
631 | /// datetime!(2021-01-01 0:00 UTC).to_iso_week_date(), |
632 | /// (2020, 53, Friday) |
633 | /// ); |
634 | /// ``` |
635 | pub const fn to_iso_week_date(self) -> (i32, u8, Weekday) { |
636 | self.date().to_iso_week_date() |
637 | } |
638 | |
639 | /// Get the weekday of the date in the stored offset. |
640 | /// |
641 | /// ```rust |
642 | /// # use time::Weekday::*; |
643 | /// # use time_macros::datetime; |
644 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).weekday(), Tuesday); |
645 | /// assert_eq!(datetime!(2019-02-01 0:00 UTC).weekday(), Friday); |
646 | /// assert_eq!(datetime!(2019-03-01 0:00 UTC).weekday(), Friday); |
647 | /// ``` |
648 | pub const fn weekday(self) -> Weekday { |
649 | self.date().weekday() |
650 | } |
651 | |
652 | /// Get the Julian day for the date. The time is not taken into account for this calculation. |
653 | /// |
654 | /// The algorithm to perform this conversion is derived from one provided by Peter Baum; it is |
655 | /// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms). |
656 | /// |
657 | /// ```rust |
658 | /// # use time_macros::datetime; |
659 | /// assert_eq!(datetime!(-4713-11-24 0:00 UTC).to_julian_day(), 0); |
660 | /// assert_eq!(datetime!(2000-01-01 0:00 UTC).to_julian_day(), 2_451_545); |
661 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).to_julian_day(), 2_458_485); |
662 | /// assert_eq!(datetime!(2019-12-31 0:00 UTC).to_julian_day(), 2_458_849); |
663 | /// ``` |
664 | pub const fn to_julian_day(self) -> i32 { |
665 | self.date().to_julian_day() |
666 | } |
667 | // endregion date getters |
668 | |
669 | // region: time getters |
670 | /// Get the clock hour, minute, and second. |
671 | /// |
672 | /// ```rust |
673 | /// # use time_macros::datetime; |
674 | /// assert_eq!(datetime!(2020-01-01 0:00:00 UTC).to_hms(), (0, 0, 0)); |
675 | /// assert_eq!(datetime!(2020-01-01 23:59:59 UTC).to_hms(), (23, 59, 59)); |
676 | /// ``` |
677 | pub const fn to_hms(self) -> (u8, u8, u8) { |
678 | self.time().as_hms() |
679 | } |
680 | |
681 | /// Get the clock hour, minute, second, and millisecond. |
682 | /// |
683 | /// ```rust |
684 | /// # use time_macros::datetime; |
685 | /// assert_eq!( |
686 | /// datetime!(2020-01-01 0:00:00 UTC).to_hms_milli(), |
687 | /// (0, 0, 0, 0) |
688 | /// ); |
689 | /// assert_eq!( |
690 | /// datetime!(2020-01-01 23:59:59.999 UTC).to_hms_milli(), |
691 | /// (23, 59, 59, 999) |
692 | /// ); |
693 | /// ``` |
694 | pub const fn to_hms_milli(self) -> (u8, u8, u8, u16) { |
695 | self.time().as_hms_milli() |
696 | } |
697 | |
698 | /// Get the clock hour, minute, second, and microsecond. |
699 | /// |
700 | /// ```rust |
701 | /// # use time_macros::datetime; |
702 | /// assert_eq!( |
703 | /// datetime!(2020-01-01 0:00:00 UTC).to_hms_micro(), |
704 | /// (0, 0, 0, 0) |
705 | /// ); |
706 | /// assert_eq!( |
707 | /// datetime!(2020-01-01 23:59:59.999_999 UTC).to_hms_micro(), |
708 | /// (23, 59, 59, 999_999) |
709 | /// ); |
710 | /// ``` |
711 | pub const fn to_hms_micro(self) -> (u8, u8, u8, u32) { |
712 | self.time().as_hms_micro() |
713 | } |
714 | |
715 | /// Get the clock hour, minute, second, and nanosecond. |
716 | /// |
717 | /// ```rust |
718 | /// # use time_macros::datetime; |
719 | /// assert_eq!( |
720 | /// datetime!(2020-01-01 0:00:00 UTC).to_hms_nano(), |
721 | /// (0, 0, 0, 0) |
722 | /// ); |
723 | /// assert_eq!( |
724 | /// datetime!(2020-01-01 23:59:59.999_999_999 UTC).to_hms_nano(), |
725 | /// (23, 59, 59, 999_999_999) |
726 | /// ); |
727 | /// ``` |
728 | pub const fn to_hms_nano(self) -> (u8, u8, u8, u32) { |
729 | self.time().as_hms_nano() |
730 | } |
731 | |
732 | /// Get the clock hour in the stored offset. |
733 | /// |
734 | /// The returned value will always be in the range `0..24`. |
735 | /// |
736 | /// ```rust |
737 | /// # use time_macros::{datetime, offset}; |
738 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).hour(), 0); |
739 | /// assert_eq!( |
740 | /// datetime!(2019-01-01 23:59:59 UTC) |
741 | /// .to_offset(offset!(-2)) |
742 | /// .hour(), |
743 | /// 21, |
744 | /// ); |
745 | /// ``` |
746 | pub const fn hour(self) -> u8 { |
747 | self.time().hour() |
748 | } |
749 | |
750 | /// Get the minute within the hour in the stored offset. |
751 | /// |
752 | /// The returned value will always be in the range `0..60`. |
753 | /// |
754 | /// ```rust |
755 | /// # use time_macros::{datetime, offset}; |
756 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).minute(), 0); |
757 | /// assert_eq!( |
758 | /// datetime!(2019-01-01 23:59:59 UTC) |
759 | /// .to_offset(offset!(+0:30)) |
760 | /// .minute(), |
761 | /// 29, |
762 | /// ); |
763 | /// ``` |
764 | pub const fn minute(self) -> u8 { |
765 | self.time().minute() |
766 | } |
767 | |
768 | /// Get the second within the minute in the stored offset. |
769 | /// |
770 | /// The returned value will always be in the range `0..60`. |
771 | /// |
772 | /// ```rust |
773 | /// # use time_macros::{datetime, offset}; |
774 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).second(), 0); |
775 | /// assert_eq!( |
776 | /// datetime!(2019-01-01 23:59:59 UTC) |
777 | /// .to_offset(offset!(+0:00:30)) |
778 | /// .second(), |
779 | /// 29, |
780 | /// ); |
781 | /// ``` |
782 | pub const fn second(self) -> u8 { |
783 | self.time().second() |
784 | } |
785 | |
786 | // Because a `UtcOffset` is limited in resolution to one second, any subsecond value will not |
787 | // change when adjusting for the offset. |
788 | |
789 | /// Get the milliseconds within the second in the stored offset. |
790 | /// |
791 | /// The returned value will always be in the range `0..1_000`. |
792 | /// |
793 | /// ```rust |
794 | /// # use time_macros::datetime; |
795 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).millisecond(), 0); |
796 | /// assert_eq!(datetime!(2019-01-01 23:59:59.999 UTC).millisecond(), 999); |
797 | /// ``` |
798 | pub const fn millisecond(self) -> u16 { |
799 | self.time().millisecond() |
800 | } |
801 | |
802 | /// Get the microseconds within the second in the stored offset. |
803 | /// |
804 | /// The returned value will always be in the range `0..1_000_000`. |
805 | /// |
806 | /// ```rust |
807 | /// # use time_macros::datetime; |
808 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).microsecond(), 0); |
809 | /// assert_eq!( |
810 | /// datetime!(2019-01-01 23:59:59.999_999 UTC).microsecond(), |
811 | /// 999_999, |
812 | /// ); |
813 | /// ``` |
814 | pub const fn microsecond(self) -> u32 { |
815 | self.time().microsecond() |
816 | } |
817 | |
818 | /// Get the nanoseconds within the second in the stored offset. |
819 | /// |
820 | /// The returned value will always be in the range `0..1_000_000_000`. |
821 | /// |
822 | /// ```rust |
823 | /// # use time_macros::datetime; |
824 | /// assert_eq!(datetime!(2019-01-01 0:00 UTC).nanosecond(), 0); |
825 | /// assert_eq!( |
826 | /// datetime!(2019-01-01 23:59:59.999_999_999 UTC).nanosecond(), |
827 | /// 999_999_999, |
828 | /// ); |
829 | /// ``` |
830 | pub const fn nanosecond(self) -> u32 { |
831 | self.time().nanosecond() |
832 | } |
833 | // endregion time getters |
834 | // endregion getters |
835 | |
836 | // region: checked arithmetic |
837 | /// Computes `self + duration`, returning `None` if an overflow occurred. |
838 | /// |
839 | /// ``` |
840 | /// # use time::{Date, ext::NumericalDuration}; |
841 | /// # use time_macros::{datetime, offset}; |
842 | /// let datetime = Date::MIN.midnight().assume_offset(offset!(+10)); |
843 | /// assert_eq!(datetime.checked_add((-2).days()), None); |
844 | /// |
845 | /// let datetime = Date::MAX.midnight().assume_offset(offset!(+10)); |
846 | /// assert_eq!(datetime.checked_add(2.days()), None); |
847 | /// |
848 | /// assert_eq!( |
849 | /// datetime!(2019-11-25 15:30 +10).checked_add(27.hours()), |
850 | /// Some(datetime!(2019-11-26 18:30 +10)) |
851 | /// ); |
852 | /// ``` |
853 | pub const fn checked_add(self, duration: Duration) -> Option<Self> { |
854 | Some(const_try_opt!(self.date_time().checked_add(duration)).assume_offset(self.offset())) |
855 | } |
856 | |
857 | /// Computes `self - duration`, returning `None` if an overflow occurred. |
858 | /// |
859 | /// ``` |
860 | /// # use time::{Date, ext::NumericalDuration}; |
861 | /// # use time_macros::{datetime, offset}; |
862 | /// let datetime = Date::MIN.midnight().assume_offset(offset!(+10)); |
863 | /// assert_eq!(datetime.checked_sub(2.days()), None); |
864 | /// |
865 | /// let datetime = Date::MAX.midnight().assume_offset(offset!(+10)); |
866 | /// assert_eq!(datetime.checked_sub((-2).days()), None); |
867 | /// |
868 | /// assert_eq!( |
869 | /// datetime!(2019-11-25 15:30 +10).checked_sub(27.hours()), |
870 | /// Some(datetime!(2019-11-24 12:30 +10)) |
871 | /// ); |
872 | /// ``` |
873 | pub const fn checked_sub(self, duration: Duration) -> Option<Self> { |
874 | Some(const_try_opt!(self.date_time().checked_sub(duration)).assume_offset(self.offset())) |
875 | } |
876 | // endregion: checked arithmetic |
877 | |
878 | // region: saturating arithmetic |
879 | /// Computes `self + duration`, saturating value on overflow. |
880 | /// |
881 | /// ``` |
882 | /// # use time::ext::NumericalDuration; |
883 | /// # use time_macros::datetime; |
884 | /// assert_eq!( |
885 | #[cfg_attr ( |
886 | feature = "large-dates" , |
887 | doc = " datetime!(-999999-01-01 0:00 +10).saturating_add((-2).days())," |
888 | )] |
889 | #[cfg_attr (feature = "large-dates" , doc = " datetime!(-999999-01-01 0:00 +10)" )] |
890 | #[cfg_attr ( |
891 | not(feature = "large-dates" ), |
892 | doc = " datetime!(-9999-01-01 0:00 +10).saturating_add((-2).days())," |
893 | )] |
894 | #[cfg_attr ( |
895 | not(feature = "large-dates" ), |
896 | doc = " datetime!(-9999-01-01 0:00 +10)" |
897 | )] |
898 | /// ); |
899 | /// |
900 | /// assert_eq!( |
901 | #[cfg_attr ( |
902 | feature = "large-dates" , |
903 | doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10).saturating_add(2.days())," |
904 | )] |
905 | #[cfg_attr ( |
906 | feature = "large-dates" , |
907 | doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10)" |
908 | )] |
909 | #[cfg_attr ( |
910 | not(feature = "large-dates" ), |
911 | doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10).saturating_add(2.days())," |
912 | )] |
913 | #[cfg_attr ( |
914 | not(feature = "large-dates" ), |
915 | doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10)" |
916 | )] |
917 | /// ); |
918 | /// |
919 | /// assert_eq!( |
920 | /// datetime!(2019-11-25 15:30 +10).saturating_add(27.hours()), |
921 | /// datetime!(2019-11-26 18:30 +10) |
922 | /// ); |
923 | /// ``` |
924 | pub const fn saturating_add(self, duration: Duration) -> Self { |
925 | if let Some(datetime) = self.checked_add(duration) { |
926 | datetime |
927 | } else if duration.is_negative() { |
928 | PrimitiveDateTime::MIN.assume_offset(self.offset()) |
929 | } else { |
930 | PrimitiveDateTime::MAX.assume_offset(self.offset()) |
931 | } |
932 | } |
933 | |
934 | /// Computes `self - duration`, saturating value on overflow. |
935 | /// |
936 | /// ``` |
937 | /// # use time::ext::NumericalDuration; |
938 | /// # use time_macros::datetime; |
939 | /// assert_eq!( |
940 | #[cfg_attr ( |
941 | feature = "large-dates" , |
942 | doc = " datetime!(-999999-01-01 0:00 +10).saturating_sub(2.days())," |
943 | )] |
944 | #[cfg_attr (feature = "large-dates" , doc = " datetime!(-999999-01-01 0:00 +10)" )] |
945 | #[cfg_attr ( |
946 | not(feature = "large-dates" ), |
947 | doc = " datetime!(-9999-01-01 0:00 +10).saturating_sub(2.days())," |
948 | )] |
949 | #[cfg_attr ( |
950 | not(feature = "large-dates" ), |
951 | doc = " datetime!(-9999-01-01 0:00 +10)" |
952 | )] |
953 | /// ); |
954 | /// |
955 | /// assert_eq!( |
956 | #[cfg_attr ( |
957 | feature = "large-dates" , |
958 | doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10).saturating_sub((-2).days())," |
959 | )] |
960 | #[cfg_attr ( |
961 | feature = "large-dates" , |
962 | doc = " datetime!(+999999-12-31 23:59:59.999_999_999 +10)" |
963 | )] |
964 | #[cfg_attr ( |
965 | not(feature = "large-dates" ), |
966 | doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10).saturating_sub((-2).days())," |
967 | )] |
968 | #[cfg_attr ( |
969 | not(feature = "large-dates" ), |
970 | doc = " datetime!(+9999-12-31 23:59:59.999_999_999 +10)" |
971 | )] |
972 | /// ); |
973 | /// |
974 | /// assert_eq!( |
975 | /// datetime!(2019-11-25 15:30 +10).saturating_sub(27.hours()), |
976 | /// datetime!(2019-11-24 12:30 +10) |
977 | /// ); |
978 | /// ``` |
979 | pub const fn saturating_sub(self, duration: Duration) -> Self { |
980 | if let Some(datetime) = self.checked_sub(duration) { |
981 | datetime |
982 | } else if duration.is_negative() { |
983 | PrimitiveDateTime::MAX.assume_offset(self.offset()) |
984 | } else { |
985 | PrimitiveDateTime::MIN.assume_offset(self.offset()) |
986 | } |
987 | } |
988 | // endregion: saturating arithmetic |
989 | } |
990 | |
991 | // region: replacement |
992 | /// Methods that replace part of the `OffsetDateTime`. |
993 | impl OffsetDateTime { |
994 | /// Replace the time, which is assumed to be in the stored offset. The date and offset |
995 | /// components are unchanged. |
996 | /// |
997 | /// ```rust |
998 | /// # use time_macros::{datetime, time}; |
999 | /// assert_eq!( |
1000 | /// datetime!(2020-01-01 5:00 UTC).replace_time(time!(12:00)), |
1001 | /// datetime!(2020-01-01 12:00 UTC) |
1002 | /// ); |
1003 | /// assert_eq!( |
1004 | /// datetime!(2020-01-01 12:00 -5).replace_time(time!(7:00)), |
1005 | /// datetime!(2020-01-01 7:00 -5) |
1006 | /// ); |
1007 | /// assert_eq!( |
1008 | /// datetime!(2020-01-01 0:00 +1).replace_time(time!(12:00)), |
1009 | /// datetime!(2020-01-01 12:00 +1) |
1010 | /// ); |
1011 | /// ``` |
1012 | #[must_use = "This method does not mutate the original `OffsetDateTime`." ] |
1013 | pub const fn replace_time(self, time: Time) -> Self { |
1014 | Self::new_in_offset(self.date(), time, self.offset()) |
1015 | } |
1016 | |
1017 | /// Replace the date, which is assumed to be in the stored offset. The time and offset |
1018 | /// components are unchanged. |
1019 | /// |
1020 | /// ```rust |
1021 | /// # use time_macros::{datetime, date}; |
1022 | /// assert_eq!( |
1023 | /// datetime!(2020-01-01 12:00 UTC).replace_date(date!(2020-01-30)), |
1024 | /// datetime!(2020-01-30 12:00 UTC) |
1025 | /// ); |
1026 | /// assert_eq!( |
1027 | /// datetime!(2020-01-01 0:00 +1).replace_date(date!(2020-01-30)), |
1028 | /// datetime!(2020-01-30 0:00 +1) |
1029 | /// ); |
1030 | /// ``` |
1031 | #[must_use = "This method does not mutate the original `OffsetDateTime`." ] |
1032 | pub const fn replace_date(self, date: Date) -> Self { |
1033 | Self::new_in_offset(date, self.time(), self.offset()) |
1034 | } |
1035 | |
1036 | /// Replace the date and time, which are assumed to be in the stored offset. The offset |
1037 | /// component remains unchanged. |
1038 | /// |
1039 | /// ```rust |
1040 | /// # use time_macros::datetime; |
1041 | /// assert_eq!( |
1042 | /// datetime!(2020-01-01 12:00 UTC).replace_date_time(datetime!(2020-01-30 16:00)), |
1043 | /// datetime!(2020-01-30 16:00 UTC) |
1044 | /// ); |
1045 | /// assert_eq!( |
1046 | /// datetime!(2020-01-01 12:00 +1).replace_date_time(datetime!(2020-01-30 0:00)), |
1047 | /// datetime!(2020-01-30 0:00 +1) |
1048 | /// ); |
1049 | /// ``` |
1050 | #[must_use = "This method does not mutate the original `OffsetDateTime`." ] |
1051 | pub const fn replace_date_time(self, date_time: PrimitiveDateTime) -> Self { |
1052 | date_time.assume_offset(self.offset()) |
1053 | } |
1054 | |
1055 | /// Replace the offset. The date and time components remain unchanged. |
1056 | /// |
1057 | /// ```rust |
1058 | /// # use time_macros::{datetime, offset}; |
1059 | /// assert_eq!( |
1060 | /// datetime!(2020-01-01 0:00 UTC).replace_offset(offset!(-5)), |
1061 | /// datetime!(2020-01-01 0:00 -5) |
1062 | /// ); |
1063 | /// ``` |
1064 | #[must_use = "This method does not mutate the original `OffsetDateTime`." ] |
1065 | pub const fn replace_offset(self, offset: UtcOffset) -> Self { |
1066 | self.date_time().assume_offset(offset) |
1067 | } |
1068 | |
1069 | /// Replace the year. The month and day will be unchanged. |
1070 | /// |
1071 | /// ```rust |
1072 | /// # use time_macros::datetime; |
1073 | /// assert_eq!( |
1074 | /// datetime!(2022-02-18 12:00 +01).replace_year(2019), |
1075 | /// Ok(datetime!(2019-02-18 12:00 +01)) |
1076 | /// ); |
1077 | /// assert!(datetime!(2022-02-18 12:00 +01).replace_year(-1_000_000_000).is_err()); // -1_000_000_000 isn't a valid year |
1078 | /// assert!(datetime!(2022-02-18 12:00 +01).replace_year(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid year |
1079 | /// ``` |
1080 | #[must_use = "This method does not mutate the original `OffsetDateTime`." ] |
1081 | pub const fn replace_year(self, year: i32) -> Result<Self, error::ComponentRange> { |
1082 | Ok(const_try!(self.date_time().replace_year(year)).assume_offset(self.offset())) |
1083 | } |
1084 | |
1085 | /// Replace the month of the year. |
1086 | /// |
1087 | /// ```rust |
1088 | /// # use time_macros::datetime; |
1089 | /// # use time::Month; |
1090 | /// assert_eq!( |
1091 | /// datetime!(2022-02-18 12:00 +01).replace_month(Month::January), |
1092 | /// Ok(datetime!(2022-01-18 12:00 +01)) |
1093 | /// ); |
1094 | /// assert!(datetime!(2022-01-30 12:00 +01).replace_month(Month::February).is_err()); // 30 isn't a valid day in February |
1095 | /// ``` |
1096 | #[must_use = "This method does not mutate the original `OffsetDateTime`." ] |
1097 | pub const fn replace_month(self, month: Month) -> Result<Self, error::ComponentRange> { |
1098 | Ok(const_try!(self.date_time().replace_month(month)).assume_offset(self.offset())) |
1099 | } |
1100 | |
1101 | /// Replace the day of the month. |
1102 | /// |
1103 | /// ```rust |
1104 | /// # use time_macros::datetime; |
1105 | /// assert_eq!( |
1106 | /// datetime!(2022-02-18 12:00 +01).replace_day(1), |
1107 | /// Ok(datetime!(2022-02-01 12:00 +01)) |
1108 | /// ); |
1109 | /// assert!(datetime!(2022-02-18 12:00 +01).replace_day(0).is_err()); // 00 isn't a valid day |
1110 | /// assert!(datetime!(2022-02-18 12:00 +01).replace_day(30).is_err()); // 30 isn't a valid day in February |
1111 | /// ``` |
1112 | #[must_use = "This method does not mutate the original `OffsetDateTime`." ] |
1113 | pub const fn replace_day(self, day: u8) -> Result<Self, error::ComponentRange> { |
1114 | Ok(const_try!(self.date_time().replace_day(day)).assume_offset(self.offset())) |
1115 | } |
1116 | |
1117 | /// Replace the day of the year. |
1118 | /// |
1119 | /// ```rust |
1120 | /// # use time_macros::datetime; |
1121 | /// assert_eq!(datetime!(2022-049 12:00 +01).replace_ordinal(1), Ok(datetime!(2022-001 12:00 +01))); |
1122 | /// assert!(datetime!(2022-049 12:00 +01).replace_ordinal(0).is_err()); // 0 isn't a valid ordinal |
1123 | /// assert!(datetime!(2022-049 12:00 +01).replace_ordinal(366).is_err()); // 2022 isn't a leap year |
1124 | /// ``` |
1125 | #[must_use = "This method does not mutate the original `OffsetDateTime`." ] |
1126 | pub const fn replace_ordinal(self, ordinal: u16) -> Result<Self, error::ComponentRange> { |
1127 | Ok(const_try!(self.date_time().replace_ordinal(ordinal)).assume_offset(self.offset())) |
1128 | } |
1129 | |
1130 | /// Replace the clock hour. |
1131 | /// |
1132 | /// ```rust |
1133 | /// # use time_macros::datetime; |
1134 | /// assert_eq!( |
1135 | /// datetime!(2022-02-18 01:02:03.004_005_006 +01).replace_hour(7), |
1136 | /// Ok(datetime!(2022-02-18 07:02:03.004_005_006 +01)) |
1137 | /// ); |
1138 | /// assert!(datetime!(2022-02-18 01:02:03.004_005_006 +01).replace_hour(24).is_err()); // 24 isn't a valid hour |
1139 | /// ``` |
1140 | #[must_use = "This method does not mutate the original `OffsetDateTime`." ] |
1141 | pub const fn replace_hour(self, hour: u8) -> Result<Self, error::ComponentRange> { |
1142 | Ok(const_try!(self.date_time().replace_hour(hour)).assume_offset(self.offset())) |
1143 | } |
1144 | |
1145 | /// Replace the minutes within the hour. |
1146 | /// |
1147 | /// ```rust |
1148 | /// # use time_macros::datetime; |
1149 | /// assert_eq!( |
1150 | /// datetime!(2022-02-18 01:02:03.004_005_006 +01).replace_minute(7), |
1151 | /// Ok(datetime!(2022-02-18 01:07:03.004_005_006 +01)) |
1152 | /// ); |
1153 | /// assert!(datetime!(2022-02-18 01:02:03.004_005_006 +01).replace_minute(60).is_err()); // 60 isn't a valid minute |
1154 | /// ``` |
1155 | #[must_use = "This method does not mutate the original `OffsetDateTime`." ] |
1156 | pub const fn replace_minute(self, minute: u8) -> Result<Self, error::ComponentRange> { |
1157 | Ok(const_try!(self.date_time().replace_minute(minute)).assume_offset(self.offset())) |
1158 | } |
1159 | |
1160 | /// Replace the seconds within the minute. |
1161 | /// |
1162 | /// ```rust |
1163 | /// # use time_macros::datetime; |
1164 | /// assert_eq!( |
1165 | /// datetime!(2022-02-18 01:02:03.004_005_006 +01).replace_second(7), |
1166 | /// Ok(datetime!(2022-02-18 01:02:07.004_005_006 +01)) |
1167 | /// ); |
1168 | /// assert!(datetime!(2022-02-18 01:02:03.004_005_006 +01).replace_second(60).is_err()); // 60 isn't a valid second |
1169 | /// ``` |
1170 | #[must_use = "This method does not mutate the original `OffsetDateTime`." ] |
1171 | pub const fn replace_second(self, second: u8) -> Result<Self, error::ComponentRange> { |
1172 | Ok(const_try!(self.date_time().replace_second(second)).assume_offset(self.offset())) |
1173 | } |
1174 | |
1175 | /// Replace the milliseconds within the second. |
1176 | /// |
1177 | /// ```rust |
1178 | /// # use time_macros::datetime; |
1179 | /// assert_eq!( |
1180 | /// datetime!(2022-02-18 01:02:03.004_005_006 +01).replace_millisecond(7), |
1181 | /// Ok(datetime!(2022-02-18 01:02:03.007 +01)) |
1182 | /// ); |
1183 | /// assert!(datetime!(2022-02-18 01:02:03.004_005_006 +01).replace_millisecond(1_000).is_err()); // 1_000 isn't a valid millisecond |
1184 | /// ``` |
1185 | #[must_use = "This method does not mutate the original `OffsetDateTime`." ] |
1186 | pub const fn replace_millisecond( |
1187 | self, |
1188 | millisecond: u16, |
1189 | ) -> Result<Self, error::ComponentRange> { |
1190 | Ok( |
1191 | const_try!(self.date_time().replace_millisecond(millisecond)) |
1192 | .assume_offset(self.offset()), |
1193 | ) |
1194 | } |
1195 | |
1196 | /// Replace the microseconds within the second. |
1197 | /// |
1198 | /// ```rust |
1199 | /// # use time_macros::datetime; |
1200 | /// assert_eq!( |
1201 | /// datetime!(2022-02-18 01:02:03.004_005_006 +01).replace_microsecond(7_008), |
1202 | /// Ok(datetime!(2022-02-18 01:02:03.007_008 +01)) |
1203 | /// ); |
1204 | /// assert!(datetime!(2022-02-18 01:02:03.004_005_006 +01).replace_microsecond(1_000_000).is_err()); // 1_000_000 isn't a valid microsecond |
1205 | /// ``` |
1206 | #[must_use = "This method does not mutate the original `OffsetDateTime`." ] |
1207 | pub const fn replace_microsecond( |
1208 | self, |
1209 | microsecond: u32, |
1210 | ) -> Result<Self, error::ComponentRange> { |
1211 | Ok( |
1212 | const_try!(self.date_time().replace_microsecond(microsecond)) |
1213 | .assume_offset(self.offset()), |
1214 | ) |
1215 | } |
1216 | |
1217 | /// Replace the nanoseconds within the second. |
1218 | /// |
1219 | /// ```rust |
1220 | /// # use time_macros::datetime; |
1221 | /// assert_eq!( |
1222 | /// datetime!(2022-02-18 01:02:03.004_005_006 +01).replace_nanosecond(7_008_009), |
1223 | /// Ok(datetime!(2022-02-18 01:02:03.007_008_009 +01)) |
1224 | /// ); |
1225 | /// assert!(datetime!(2022-02-18 01:02:03.004_005_006 +01).replace_nanosecond(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid nanosecond |
1226 | /// ``` |
1227 | #[must_use = "This method does not mutate the original `OffsetDateTime`." ] |
1228 | pub const fn replace_nanosecond(self, nanosecond: u32) -> Result<Self, error::ComponentRange> { |
1229 | Ok( |
1230 | const_try!(self.date_time().replace_nanosecond(nanosecond)) |
1231 | .assume_offset(self.offset()), |
1232 | ) |
1233 | } |
1234 | } |
1235 | // endregion replacement |
1236 | |
1237 | // region: formatting & parsing |
1238 | #[cfg (feature = "formatting" )] |
1239 | impl OffsetDateTime { |
1240 | /// Format the `OffsetDateTime` using the provided [format |
1241 | /// description](crate::format_description). |
1242 | pub fn format_into( |
1243 | self, |
1244 | output: &mut impl io::Write, |
1245 | format: &(impl Formattable + ?Sized), |
1246 | ) -> Result<usize, error::Format> { |
1247 | format.format_into( |
1248 | output, |
1249 | Some(self.date()), |
1250 | Some(self.time()), |
1251 | Some(self.offset()), |
1252 | ) |
1253 | } |
1254 | |
1255 | /// Format the `OffsetDateTime` using the provided [format |
1256 | /// description](crate::format_description). |
1257 | /// |
1258 | /// ```rust |
1259 | /// # use time::format_description; |
1260 | /// # use time_macros::datetime; |
1261 | /// let format = format_description::parse( |
1262 | /// "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \ |
1263 | /// sign:mandatory]:[offset_minute]:[offset_second]" , |
1264 | /// )?; |
1265 | /// assert_eq!( |
1266 | /// datetime!(2020-01-02 03:04:05 +06:07:08).format(&format)?, |
1267 | /// "2020-01-02 03:04:05 +06:07:08" |
1268 | /// ); |
1269 | /// # Ok::<_, time::Error>(()) |
1270 | /// ``` |
1271 | pub fn format(self, format: &(impl Formattable + ?Sized)) -> Result<String, error::Format> { |
1272 | format.format(Some(self.date()), Some(self.time()), Some(self.offset())) |
1273 | } |
1274 | } |
1275 | |
1276 | #[cfg (feature = "parsing" )] |
1277 | impl OffsetDateTime { |
1278 | /// Parse an `OffsetDateTime` from the input using the provided [format |
1279 | /// description](crate::format_description). |
1280 | /// |
1281 | /// ```rust |
1282 | /// # use time::OffsetDateTime; |
1283 | /// # use time_macros::{datetime, format_description}; |
1284 | /// let format = format_description!( |
1285 | /// "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \ |
1286 | /// sign:mandatory]:[offset_minute]:[offset_second]" |
1287 | /// ); |
1288 | /// assert_eq!( |
1289 | /// OffsetDateTime::parse("2020-01-02 03:04:05 +06:07:08", &format)?, |
1290 | /// datetime!(2020-01-02 03:04:05 +06:07:08) |
1291 | /// ); |
1292 | /// # Ok::<_, time::Error>(()) |
1293 | /// ``` |
1294 | pub fn parse( |
1295 | input: &str, |
1296 | description: &(impl Parsable + ?Sized), |
1297 | ) -> Result<Self, error::Parse> { |
1298 | description.parse_offset_date_time(input.as_bytes()) |
1299 | } |
1300 | |
1301 | /// A helper method to check if the `OffsetDateTime` is a valid representation of a leap second. |
1302 | /// Leap seconds, when parsed, are represented as the preceding nanosecond. However, leap |
1303 | /// seconds can only occur as the last second of a month UTC. |
1304 | #[cfg (feature = "parsing" )] |
1305 | pub(crate) const fn is_valid_leap_second_stand_in(self) -> bool { |
1306 | // This comparison doesn't need to be adjusted for the stored offset, so check it first for |
1307 | // speed. |
1308 | if self.nanosecond() != 999_999_999 { |
1309 | return false; |
1310 | } |
1311 | |
1312 | let (year, ordinal, time) = self.to_offset_raw(UtcOffset::UTC); |
1313 | let Ok(date) = Date::from_ordinal_date(year, ordinal) else { |
1314 | return false; |
1315 | }; |
1316 | |
1317 | time.hour() == 23 |
1318 | && time.minute() == 59 |
1319 | && time.second() == 59 |
1320 | && date.day() == date.month().length(year) |
1321 | } |
1322 | } |
1323 | |
1324 | impl SmartDisplay for OffsetDateTime { |
1325 | type Metadata = (); |
1326 | |
1327 | fn metadata(&self, _: FormatterOptions) -> Metadata<Self> { |
1328 | let width: usize = |
1329 | smart_display::padded_width_of!(self.date(), " " , self.time(), " " , self.offset()); |
1330 | Metadata::new(width, self, ()) |
1331 | } |
1332 | |
1333 | fn fmt_with_metadata( |
1334 | &self, |
1335 | f: &mut fmt::Formatter<'_>, |
1336 | metadata: Metadata<Self>, |
1337 | ) -> fmt::Result { |
1338 | f.pad_with_width( |
1339 | metadata.unpadded_width(), |
1340 | args:format_args!(" {} {} {}" , self.date(), self.time(), self.offset()), |
1341 | ) |
1342 | } |
1343 | } |
1344 | |
1345 | impl fmt::Display for OffsetDateTime { |
1346 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1347 | SmartDisplay::fmt(self, f) |
1348 | } |
1349 | } |
1350 | |
1351 | impl fmt::Debug for OffsetDateTime { |
1352 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1353 | fmt::Display::fmt(self, f) |
1354 | } |
1355 | } |
1356 | // endregion formatting & parsing |
1357 | |
1358 | // region: trait impls |
1359 | impl Add<Duration> for OffsetDateTime { |
1360 | type Output = Self; |
1361 | |
1362 | /// # Panics |
1363 | /// |
1364 | /// This may panic if an overflow occurs. |
1365 | fn add(self, duration: Duration) -> Self::Output { |
1366 | self.checked_add(duration) |
1367 | .expect(msg:"resulting value is out of range" ) |
1368 | } |
1369 | } |
1370 | |
1371 | impl Add<StdDuration> for OffsetDateTime { |
1372 | type Output = Self; |
1373 | |
1374 | /// # Panics |
1375 | /// |
1376 | /// This may panic if an overflow occurs. |
1377 | fn add(self, duration: StdDuration) -> Self::Output { |
1378 | let (is_next_day: bool, time: Time) = self.time().adjusting_add_std(duration); |
1379 | |
1380 | Self::new_in_offset( |
1381 | date:if is_next_day { |
1382 | (self.date() + duration) |
1383 | .next_day() |
1384 | .expect(msg:"resulting value is out of range" ) |
1385 | } else { |
1386 | self.date() + duration |
1387 | }, |
1388 | time, |
1389 | self.offset, |
1390 | ) |
1391 | } |
1392 | } |
1393 | |
1394 | impl AddAssign<Duration> for OffsetDateTime { |
1395 | /// # Panics |
1396 | /// |
1397 | /// This may panic if an overflow occurs. |
1398 | fn add_assign(&mut self, rhs: Duration) { |
1399 | *self = *self + rhs; |
1400 | } |
1401 | } |
1402 | |
1403 | impl AddAssign<StdDuration> for OffsetDateTime { |
1404 | /// # Panics |
1405 | /// |
1406 | /// This may panic if an overflow occurs. |
1407 | fn add_assign(&mut self, rhs: StdDuration) { |
1408 | *self = *self + rhs; |
1409 | } |
1410 | } |
1411 | |
1412 | impl Sub<Duration> for OffsetDateTime { |
1413 | type Output = Self; |
1414 | |
1415 | /// # Panics |
1416 | /// |
1417 | /// This may panic if an overflow occurs. |
1418 | fn sub(self, rhs: Duration) -> Self::Output { |
1419 | self.checked_sub(rhs) |
1420 | .expect(msg:"resulting value is out of range" ) |
1421 | } |
1422 | } |
1423 | |
1424 | impl Sub<StdDuration> for OffsetDateTime { |
1425 | type Output = Self; |
1426 | |
1427 | /// # Panics |
1428 | /// |
1429 | /// This may panic if an overflow occurs. |
1430 | fn sub(self, duration: StdDuration) -> Self::Output { |
1431 | let (is_previous_day: bool, time: Time) = self.time().adjusting_sub_std(duration); |
1432 | |
1433 | Self::new_in_offset( |
1434 | date:if is_previous_day { |
1435 | (self.date() - duration) |
1436 | .previous_day() |
1437 | .expect(msg:"resulting value is out of range" ) |
1438 | } else { |
1439 | self.date() - duration |
1440 | }, |
1441 | time, |
1442 | self.offset, |
1443 | ) |
1444 | } |
1445 | } |
1446 | |
1447 | impl SubAssign<Duration> for OffsetDateTime { |
1448 | /// # Panics |
1449 | /// |
1450 | /// This may panic if an overflow occurs. |
1451 | fn sub_assign(&mut self, rhs: Duration) { |
1452 | *self = *self - rhs; |
1453 | } |
1454 | } |
1455 | |
1456 | impl SubAssign<StdDuration> for OffsetDateTime { |
1457 | /// # Panics |
1458 | /// |
1459 | /// This may panic if an overflow occurs. |
1460 | fn sub_assign(&mut self, rhs: StdDuration) { |
1461 | *self = *self - rhs; |
1462 | } |
1463 | } |
1464 | |
1465 | impl Sub for OffsetDateTime { |
1466 | type Output = Duration; |
1467 | |
1468 | /// # Panics |
1469 | /// |
1470 | /// This may panic if an overflow occurs. |
1471 | fn sub(self, rhs: Self) -> Self::Output { |
1472 | let base: Duration = self.date_time() - rhs.date_time(); |
1473 | let adjustment: Duration = Duration::seconds( |
1474 | (self.offset.whole_seconds() - rhs.offset.whole_seconds()).extend::<i64>(), |
1475 | ); |
1476 | base - adjustment |
1477 | } |
1478 | } |
1479 | |
1480 | #[cfg (feature = "std" )] |
1481 | impl Sub<SystemTime> for OffsetDateTime { |
1482 | type Output = Duration; |
1483 | |
1484 | /// # Panics |
1485 | /// |
1486 | /// This may panic if an overflow occurs. |
1487 | fn sub(self, rhs: SystemTime) -> Self::Output { |
1488 | self - Self::from(rhs) |
1489 | } |
1490 | } |
1491 | |
1492 | #[cfg (feature = "std" )] |
1493 | impl Sub<OffsetDateTime> for SystemTime { |
1494 | type Output = Duration; |
1495 | |
1496 | /// # Panics |
1497 | /// |
1498 | /// This may panic if an overflow occurs. |
1499 | fn sub(self, rhs: OffsetDateTime) -> Self::Output { |
1500 | OffsetDateTime::from(self) - rhs |
1501 | } |
1502 | } |
1503 | |
1504 | #[cfg (feature = "std" )] |
1505 | impl Add<Duration> for SystemTime { |
1506 | type Output = Self; |
1507 | |
1508 | fn add(self, duration: Duration) -> Self::Output { |
1509 | if duration.is_zero() { |
1510 | self |
1511 | } else if duration.is_positive() { |
1512 | self + duration.unsigned_abs() |
1513 | } else { |
1514 | debug_assert!(duration.is_negative()); |
1515 | self - duration.unsigned_abs() |
1516 | } |
1517 | } |
1518 | } |
1519 | |
1520 | crate::internal_macros::impl_add_assign!(SystemTime: #[cfg (feature = "std" )] Duration); |
1521 | |
1522 | #[cfg (feature = "std" )] |
1523 | impl Sub<Duration> for SystemTime { |
1524 | type Output = Self; |
1525 | |
1526 | fn sub(self, duration: Duration) -> Self::Output { |
1527 | (OffsetDateTime::from(self) - duration).into() |
1528 | } |
1529 | } |
1530 | |
1531 | crate::internal_macros::impl_sub_assign!(SystemTime: #[cfg (feature = "std" )] Duration); |
1532 | |
1533 | #[cfg (feature = "std" )] |
1534 | impl PartialEq<SystemTime> for OffsetDateTime { |
1535 | fn eq(&self, rhs: &SystemTime) -> bool { |
1536 | self == &Self::from(*rhs) |
1537 | } |
1538 | } |
1539 | |
1540 | #[cfg (feature = "std" )] |
1541 | impl PartialEq<OffsetDateTime> for SystemTime { |
1542 | fn eq(&self, rhs: &OffsetDateTime) -> bool { |
1543 | &OffsetDateTime::from(*self) == rhs |
1544 | } |
1545 | } |
1546 | |
1547 | #[cfg (feature = "std" )] |
1548 | impl PartialOrd<SystemTime> for OffsetDateTime { |
1549 | fn partial_cmp(&self, other: &SystemTime) -> Option<Ordering> { |
1550 | self.partial_cmp(&Self::from(*other)) |
1551 | } |
1552 | } |
1553 | |
1554 | #[cfg (feature = "std" )] |
1555 | impl PartialOrd<OffsetDateTime> for SystemTime { |
1556 | fn partial_cmp(&self, other: &OffsetDateTime) -> Option<Ordering> { |
1557 | OffsetDateTime::from(*self).partial_cmp(other) |
1558 | } |
1559 | } |
1560 | |
1561 | #[cfg (feature = "std" )] |
1562 | impl From<SystemTime> for OffsetDateTime { |
1563 | fn from(system_time: SystemTime) -> Self { |
1564 | match system_time.duration_since(earlier:SystemTime::UNIX_EPOCH) { |
1565 | Ok(duration: Duration) => Self::UNIX_EPOCH + duration, |
1566 | Err(err: SystemTimeError) => Self::UNIX_EPOCH - err.duration(), |
1567 | } |
1568 | } |
1569 | } |
1570 | |
1571 | #[cfg (feature = "std" )] |
1572 | impl From<OffsetDateTime> for SystemTime { |
1573 | fn from(datetime: OffsetDateTime) -> Self { |
1574 | let duration: Duration = datetime - OffsetDateTime::UNIX_EPOCH; |
1575 | |
1576 | if duration.is_zero() { |
1577 | Self::UNIX_EPOCH |
1578 | } else if duration.is_positive() { |
1579 | Self::UNIX_EPOCH + duration.unsigned_abs() |
1580 | } else { |
1581 | debug_assert!(duration.is_negative()); |
1582 | Self::UNIX_EPOCH - duration.unsigned_abs() |
1583 | } |
1584 | } |
1585 | } |
1586 | |
1587 | #[cfg (all( |
1588 | target_family = "wasm" , |
1589 | not(any(target_os = "emscripten" , target_os = "wasi" )), |
1590 | feature = "wasm-bindgen" |
1591 | ))] |
1592 | impl From<js_sys::Date> for OffsetDateTime { |
1593 | /// # Panics |
1594 | /// |
1595 | /// This may panic if the timestamp can not be represented. |
1596 | fn from(js_date: js_sys::Date) -> Self { |
1597 | // get_time() returns milliseconds |
1598 | let timestamp_nanos = js_date.get_time() as i128 |
1599 | * Nanosecond::per(Millisecond).cast_signed().extend::<i128>(); |
1600 | Self::from_unix_timestamp_nanos(timestamp_nanos) |
1601 | .expect("invalid timestamp: Timestamp cannot fit in range" ) |
1602 | } |
1603 | } |
1604 | |
1605 | #[cfg (all( |
1606 | target_family = "wasm" , |
1607 | not(any(target_os = "emscripten" , target_os = "wasi" )), |
1608 | feature = "wasm-bindgen" |
1609 | ))] |
1610 | impl From<OffsetDateTime> for js_sys::Date { |
1611 | fn from(datetime: OffsetDateTime) -> Self { |
1612 | // new Date() takes milliseconds |
1613 | let timestamp = (datetime.unix_timestamp_nanos() |
1614 | / Nanosecond::per(Millisecond).cast_signed().extend::<i128>()) |
1615 | as f64; |
1616 | Self::new(×tamp.into()) |
1617 | } |
1618 | } |
1619 | // endregion trait impls |
1620 | |