jiff/zoned.rs
1use core::time::Duration as UnsignedDuration;
2
3use crate::{
4 civil::{
5 Date, DateTime, DateTimeRound, DateTimeWith, Era, ISOWeekDate, Time,
6 Weekday,
7 },
8 duration::{Duration, SDuration},
9 error::{err, Error, ErrorContext},
10 fmt::{
11 self,
12 temporal::{self, DEFAULT_DATETIME_PARSER},
13 },
14 tz::{AmbiguousOffset, Disambiguation, Offset, OffsetConflict, TimeZone},
15 util::{
16 rangeint::{RInto, TryRFrom},
17 t::{self, ZonedDayNanoseconds, C},
18 },
19 RoundMode, SignedDuration, Span, SpanRound, Timestamp, Unit,
20};
21
22/// A time zone aware instant in time.
23///
24/// A `Zoned` value can be thought of as the combination of following types,
25/// all rolled into one:
26///
27/// * A [`Timestamp`] for indicating the precise instant in time.
28/// * A [`DateTime`] for indicating the "civil" calendar date and clock time.
29/// * A [`TimeZone`] for indicating how to apply time zone transitions while
30/// performing arithmetic.
31///
32/// In particular, a `Zoned` is specifically designed for dealing with
33/// datetimes in a time zone aware manner. Here are some highlights:
34///
35/// * Arithmetic automatically adjusts for daylight saving time (DST), using
36/// the rules defined by [RFC 5545].
37/// * Creating new `Zoned` values from other `Zoned` values via [`Zoned::with`]
38/// by changing clock time (e.g., `02:30`) can do so without worrying that the
39/// time will be invalid due to DST transitions.
40/// * An approximate superset of the [`DateTime`] API is offered on `Zoned`,
41/// but where each of its operations take time zone into account when
42/// appropriate. For example, [`DateTime::start_of_day`] always returns a
43/// datetime set to midnight, but [`Zoned::start_of_day`] returns the first
44/// instant of a day, which might not be midnight if there is a time zone
45/// transition at midnight.
46/// * When using a `Zoned`, it is easy to switch between civil datetime (the
47/// day you see on the calendar and the time you see on the clock) and Unix
48/// time (a precise instant in time). Indeed, a `Zoned` can be losslessy
49/// converted to any other datetime type in this crate: [`Timestamp`],
50/// [`DateTime`], [`Date`] and [`Time`].
51/// * A `Zoned` value can be losslessly serialized and deserialized, via
52/// [serde], by adhering to [RFC 8536]. An example of a serialized zoned
53/// datetime is `2024-07-04T08:39:00-04:00[America/New_York]`.
54/// * Since a `Zoned` stores a [`TimeZone`] itself, multiple time zone aware
55/// operations can be chained together without repeatedly specifying the time
56/// zone.
57///
58/// [RFC 5545]: https://datatracker.ietf.org/doc/html/rfc5545
59/// [RFC 8536]: https://datatracker.ietf.org/doc/html/rfc8536
60/// [serde]: https://serde.rs/
61///
62/// # Parsing and printing
63///
64/// The `Zoned` type provides convenient trait implementations of
65/// [`std::str::FromStr`] and [`std::fmt::Display`]:
66///
67/// ```
68/// use jiff::Zoned;
69///
70/// let zdt: Zoned = "2024-06-19 15:22[America/New_York]".parse()?;
71/// // Notice that the second component and the offset have both been added.
72/// assert_eq!(zdt.to_string(), "2024-06-19T15:22:00-04:00[America/New_York]");
73///
74/// // While in the above case the datetime is unambiguous, in some cases, it
75/// // can be ambiguous. In these cases, an offset is required to correctly
76/// // roundtrip a zoned datetime. For example, on 2024-11-03 in New York, the
77/// // 1 o'clock hour was repeated twice, corresponding to the end of daylight
78/// // saving time.
79/// //
80/// // So because of the ambiguity, this time could be in offset -04 (the first
81/// // time 1 o'clock is on the clock) or it could be -05 (the second time
82/// // 1 o'clock is on the clock, corresponding to the end of DST).
83/// //
84/// // By default, parsing uses a "compatible" strategy for resolving all cases
85/// // of ambiguity: in forward transitions (gaps), the later time is selected.
86/// // And in backward transitions (folds), the earlier time is selected.
87/// let zdt: Zoned = "2024-11-03 01:30[America/New_York]".parse()?;
88/// // As we can see, since this was a fold, the earlier time was selected
89/// // because the -04 offset is the first time 1 o'clock appears on the clock.
90/// assert_eq!(zdt.to_string(), "2024-11-03T01:30:00-04:00[America/New_York]");
91/// // But if we changed the offset and re-serialized, the only thing that
92/// // changes is, indeed, the offset. This demonstrates that the offset is
93/// // key to ensuring lossless serialization.
94/// let zdt = zdt.with().offset(jiff::tz::offset(-5)).build()?;
95/// assert_eq!(zdt.to_string(), "2024-11-03T01:30:00-05:00[America/New_York]");
96///
97/// # Ok::<(), Box<dyn std::error::Error>>(())
98/// ```
99///
100/// A `Zoned` can also be parsed from just a time zone aware date (but the
101/// time zone annotation is still required). In this case, the time is set to
102/// midnight:
103///
104/// ```
105/// use jiff::Zoned;
106///
107/// let zdt: Zoned = "2024-06-19[America/New_York]".parse()?;
108/// assert_eq!(zdt.to_string(), "2024-06-19T00:00:00-04:00[America/New_York]");
109/// // ... although it isn't always midnight, in the case of a time zone
110/// // transition at midnight!
111/// let zdt: Zoned = "2015-10-18[America/Sao_Paulo]".parse()?;
112/// assert_eq!(zdt.to_string(), "2015-10-18T01:00:00-02:00[America/Sao_Paulo]");
113///
114/// # Ok::<(), Box<dyn std::error::Error>>(())
115/// ```
116///
117/// For more information on the specific format supported, see the
118/// [`fmt::temporal`](crate::fmt::temporal) module documentation.
119///
120/// # Leap seconds
121///
122/// Jiff does not support leap seconds. Jiff behaves as if they don't exist.
123/// The only exception is that if one parses a datetime with a second component
124/// of `60`, then it is automatically constrained to `59`:
125///
126/// ```
127/// use jiff::{civil::date, Zoned};
128///
129/// let zdt: Zoned = "2016-12-31 23:59:60[Australia/Tasmania]".parse()?;
130/// assert_eq!(zdt.datetime(), date(2016, 12, 31).at(23, 59, 59, 0));
131///
132/// # Ok::<(), Box<dyn std::error::Error>>(())
133/// ```
134///
135/// # Comparisons
136///
137/// The `Zoned` type provides both `Eq` and `Ord` trait implementations to
138/// facilitate easy comparisons. When a zoned datetime `zdt1` occurs before a
139/// zoned datetime `zdt2`, then `zdt1 < zdt2`. For example:
140///
141/// ```
142/// use jiff::civil::date;
143///
144/// let zdt1 = date(2024, 3, 11).at(1, 25, 15, 0).in_tz("America/New_York")?;
145/// let zdt2 = date(2025, 1, 31).at(0, 30, 0, 0).in_tz("America/New_York")?;
146/// assert!(zdt1 < zdt2);
147///
148/// # Ok::<(), Box<dyn std::error::Error>>(())
149/// ```
150///
151/// Note that `Zoned` comparisons only consider the precise instant in time.
152/// The civil datetime or even the time zone are completely ignored. So it's
153/// possible for a zoned datetime to be less than another even if it's civil
154/// datetime is bigger:
155///
156/// ```
157/// use jiff::civil::date;
158///
159/// let zdt1 = date(2024, 7, 4).at(12, 0, 0, 0).in_tz("America/New_York")?;
160/// let zdt2 = date(2024, 7, 4).at(11, 0, 0, 0).in_tz("America/Los_Angeles")?;
161/// assert!(zdt1 < zdt2);
162/// // But if we only compare civil datetime, the result is flipped:
163/// assert!(zdt1.datetime() > zdt2.datetime());
164///
165/// # Ok::<(), Box<dyn std::error::Error>>(())
166/// ```
167///
168/// The same applies for equality as well. Two `Zoned` values are equal, even
169/// if they have different time zones, when the instant in time is identical:
170///
171/// ```
172/// use jiff::civil::date;
173///
174/// let zdt1 = date(2024, 7, 4).at(12, 0, 0, 0).in_tz("America/New_York")?;
175/// let zdt2 = date(2024, 7, 4).at(9, 0, 0, 0).in_tz("America/Los_Angeles")?;
176/// assert_eq!(zdt1, zdt2);
177///
178/// # Ok::<(), Box<dyn std::error::Error>>(())
179/// ```
180///
181/// (Note that this is diifferent from
182/// [Temporal's `ZonedDateTime.equals`][temporal-equals] comparison, which will
183/// take time zone into account for equality. This is because `Eq` and `Ord`
184/// trait implementations must be consistent in Rust. If you need Temporal's
185/// behavior, then use `zdt1 == zdt2 && zdt1.time_zone() == zdt2.time_zone()`.)
186///
187/// [temporal-equals]: https://tc39.es/proposal-temporal/docs/zoneddatetime.html#equals
188///
189/// # Arithmetic
190///
191/// This type provides routines for adding and subtracting spans of time, as
192/// well as computing the span of time between two `Zoned` values. These
193/// operations take time zones into account.
194///
195/// For adding or subtracting spans of time, one can use any of the following
196/// routines:
197///
198/// * [`Zoned::checked_add`] or [`Zoned::checked_sub`] for checked
199/// arithmetic.
200/// * [`Zoned::saturating_add`] or [`Zoned::saturating_sub`] for
201/// saturating arithmetic.
202///
203/// Additionally, checked arithmetic is available via the `Add` and `Sub`
204/// trait implementations. When the result overflows, a panic occurs.
205///
206/// ```
207/// use jiff::{civil::date, ToSpan};
208///
209/// let start = date(2024, 2, 25).at(15, 45, 0, 0).in_tz("America/New_York")?;
210/// // `Zoned` doesn't implement `Copy`, so we use `&start` instead of `start`.
211/// let one_week_later = &start + 1.weeks();
212/// assert_eq!(one_week_later.datetime(), date(2024, 3, 3).at(15, 45, 0, 0));
213///
214/// # Ok::<(), Box<dyn std::error::Error>>(())
215/// ```
216///
217/// One can compute the span of time between two zoned datetimes using either
218/// [`Zoned::until`] or [`Zoned::since`]. It's also possible to subtract
219/// two `Zoned` values directly via a `Sub` trait implementation:
220///
221/// ```
222/// use jiff::{civil::date, ToSpan};
223///
224/// let zdt1 = date(2024, 5, 3).at(23, 30, 0, 0).in_tz("America/New_York")?;
225/// let zdt2 = date(2024, 2, 25).at(7, 0, 0, 0).in_tz("America/New_York")?;
226/// assert_eq!(&zdt1 - &zdt2, 1647.hours().minutes(30).fieldwise());
227///
228/// # Ok::<(), Box<dyn std::error::Error>>(())
229/// ```
230///
231/// The `until` and `since` APIs are polymorphic and allow re-balancing and
232/// rounding the span returned. For example, the default largest unit is hours
233/// (as exemplified above), but we can ask for bigger units:
234///
235/// ```
236/// use jiff::{civil::date, ToSpan, Unit};
237///
238/// let zdt1 = date(2024, 5, 3).at(23, 30, 0, 0).in_tz("America/New_York")?;
239/// let zdt2 = date(2024, 2, 25).at(7, 0, 0, 0).in_tz("America/New_York")?;
240/// assert_eq!(
241/// zdt1.since((Unit::Year, &zdt2))?,
242/// 2.months().days(7).hours(16).minutes(30).fieldwise(),
243/// );
244///
245/// # Ok::<(), Box<dyn std::error::Error>>(())
246/// ```
247///
248/// Or even round the span returned:
249///
250/// ```
251/// use jiff::{civil::date, RoundMode, ToSpan, Unit, ZonedDifference};
252///
253/// let zdt1 = date(2024, 5, 3).at(23, 30, 0, 0).in_tz("America/New_York")?;
254/// let zdt2 = date(2024, 2, 25).at(7, 0, 0, 0).in_tz("America/New_York")?;
255/// assert_eq!(
256/// zdt1.since(
257/// ZonedDifference::new(&zdt2)
258/// .smallest(Unit::Day)
259/// .largest(Unit::Year),
260/// )?,
261/// 2.months().days(7).fieldwise(),
262/// );
263/// // `ZonedDifference` uses truncation as a rounding mode by default,
264/// // but you can set the rounding mode to break ties away from zero:
265/// assert_eq!(
266/// zdt1.since(
267/// ZonedDifference::new(&zdt2)
268/// .smallest(Unit::Day)
269/// .largest(Unit::Year)
270/// .mode(RoundMode::HalfExpand),
271/// )?,
272/// // Rounds up to 8 days.
273/// 2.months().days(8).fieldwise(),
274/// );
275///
276/// # Ok::<(), Box<dyn std::error::Error>>(())
277/// ```
278///
279/// # Rounding
280///
281/// A `Zoned` can be rounded based on a [`ZonedRound`] configuration of
282/// smallest units, rounding increment and rounding mode. Here's an example
283/// showing how to round to the nearest third hour:
284///
285/// ```
286/// use jiff::{civil::date, Unit, ZonedRound};
287///
288/// let zdt = date(2024, 6, 19)
289/// .at(16, 27, 29, 999_999_999)
290/// .in_tz("America/New_York")?;
291/// assert_eq!(
292/// zdt.round(ZonedRound::new().smallest(Unit::Hour).increment(3))?,
293/// date(2024, 6, 19).at(15, 0, 0, 0).in_tz("America/New_York")?,
294/// );
295/// // Or alternatively, make use of the `From<(Unit, i64)> for ZonedRound`
296/// // trait implementation:
297/// assert_eq!(
298/// zdt.round((Unit::Hour, 3))?,
299/// date(2024, 6, 19).at(15, 0, 0, 0).in_tz("America/New_York")?,
300/// );
301///
302/// # Ok::<(), Box<dyn std::error::Error>>(())
303/// ```
304///
305/// See [`Zoned::round`] for more details.
306#[derive(Clone)]
307pub struct Zoned {
308 inner: ZonedInner,
309}
310
311/// The representation of a `Zoned`.
312///
313/// This uses 4 different things: a timestamp, a datetime, an offset and a
314/// time zone. This in turn makes `Zoned` a bit beefy (40 bytes on x86-64),
315/// but I think this is probably the right trade off. (At time of writing,
316/// 2024-07-04.)
317///
318/// Technically speaking, the only essential fields here are timestamp and time
319/// zone. The datetime and offset can both be unambiguously _computed_ from the
320/// combination of a timestamp and a time zone. Indeed, just the timestamp and
321/// the time zone was my initial representation. But as I developed the API of
322/// this type, it became clearer that we should probably store the datetime and
323/// offset as well.
324///
325/// The main issue here is that in order to compute the datetime from a
326/// timestamp and a time zone, you need to do two things:
327///
328/// 1. First, compute the offset. This means doing a binary search on the TZif
329/// data for the transition (or closest transition) matching the timestamp.
330/// 2. Second, use the offset (from UTC) to convert the timestamp into a civil
331/// datetime. This involves a "Unix time to Unix epoch days" conversion that
332/// requires some heavy arithmetic.
333///
334/// So if we don't store the datetime or offset, then we need to compute them
335/// any time we need them. And the Temporal design really pushes heavily in
336/// favor of treating the "instant in time" and "civil datetime" as two sides
337/// to the same coin. That means users are very encouraged to just use whatever
338/// they need. So if we are always computing the offset and datetime whenever
339/// we need them, we're potentially punishing users for working with civil
340/// datetimes. It just doesn't feel like the right trade-off.
341///
342/// Instead, my idea here is that, ultimately, `Zoned` is meant to provide
343/// a one-stop shop for "doing the right thing." Presenting that unified
344/// abstraction comes with costs. And that if we want to expose cheaper ways
345/// of performing at least some of the operations on `Zoned` by making fewer
346/// assumptions, then we should probably endeavor to do that by exposing a
347/// lower level API. I'm not sure what that would look like, so I think it
348/// should be driven by use cases.
349///
350/// Some other things I considered:
351///
352/// * Use `Zoned(Arc<ZonedInner>)` to make `Zoned` pointer-sized. But I didn't
353/// like this because it implies creating any new `Zoned` value requires an
354/// allocation. Since a `TimeZone` internally uses an `Arc`, all it requires
355/// today is a chunky memcpy and an atomic ref count increment.
356/// * Use `OnceLock` shenanigans for the datetime and offset fields. This would
357/// make `Zoned` even beefier and I wasn't totally clear how much this would
358/// save us. And it would impose some (probably small) cost on every datetime
359/// or offset access.
360/// * Use a radically different design that permits a `Zoned` to be `Copy`.
361/// I personally find it deeply annoying that `Zoned` is both the "main"
362/// datetime type in Jiff and also the only one that doesn't implement `Copy`.
363/// I explored some designs, but I couldn't figure out how to make it work in
364/// a satisfying way. The main issue here is `TimeZone`. A `TimeZone` is a huge
365/// chunk of data and the ergonomics of the `Zoned` API require being able to
366/// access a `TimeZone` without the caller providing it explicitly. So to me,
367/// the only real alternative here is to use some kind of integer handle into
368/// a global time zone database. But now you all of a sudden need to worry
369/// about synchronization for every time zone access and plausibly also garbage
370/// collection. And this also complicates matters for using custom time zone
371/// databases. So I ultimately came down on "Zoned is not Copy" as the least
372/// awful choice. *heavy sigh*
373#[derive(Clone)]
374struct ZonedInner {
375 timestamp: Timestamp,
376 datetime: DateTime,
377 offset: Offset,
378 time_zone: TimeZone,
379}
380
381impl Zoned {
382 /// Returns the current system time in this system's time zone.
383 ///
384 /// If the system's time zone could not be found, then [`TimeZone::UTC`]
385 /// is used instead. When this happens, a `WARN` level log message will
386 /// be emitted. (To see it, one will need to install a logger that is
387 /// compatible with the `log` crate and enable Jiff's `logging` Cargo
388 /// feature.)
389 ///
390 /// To create a `Zoned` value for the current time in a particular
391 /// time zone other than the system default time zone, use
392 /// `Timestamp::now().to_zoned(time_zone)`. In particular, using
393 /// [`Timestamp::now`] avoids the work required to fetch the system time
394 /// zone if you did `Zoned::now().with_time_zone(time_zone)`.
395 ///
396 /// # Panics
397 ///
398 /// This panics if the system clock is set to a time value outside of the
399 /// range `-009999-01-01T00:00:00Z..=9999-12-31T11:59:59.999999999Z`. The
400 /// justification here is that it is reasonable to expect the system clock
401 /// to be set to a somewhat sane, if imprecise, value.
402 ///
403 /// If you want to get the current Unix time fallibly, use
404 /// [`Zoned::try_from`] with a `std::time::SystemTime` as input.
405 ///
406 /// This may also panic when `SystemTime::now()` itself panics. The most
407 /// common context in which this happens is on the `wasm32-unknown-unknown`
408 /// target. If you're using that target in the context of the web (for
409 /// example, via `wasm-pack`), and you're an application, then you should
410 /// enable Jiff's `js` feature. This will automatically instruct Jiff in
411 /// this very specific circumstance to execute JavaScript code to determine
412 /// the current time from the web browser.
413 ///
414 /// # Example
415 ///
416 /// ```
417 /// use jiff::{Timestamp, Zoned};
418 ///
419 /// assert!(Zoned::now().timestamp() > Timestamp::UNIX_EPOCH);
420 /// ```
421 #[cfg(feature = "std")]
422 #[inline]
423 pub fn now() -> Zoned {
424 Zoned::try_from(crate::now::system_time())
425 .expect("system time is valid")
426 }
427
428 /// Creates a new `Zoned` value from a specific instant in a particular
429 /// time zone. The time zone determines how to render the instant in time
430 /// into civil time. (Also known as "clock," "wall," "local" or "naive"
431 /// time.)
432 ///
433 /// To create a new zoned datetime from another with a particular field
434 /// value, use the methods on [`ZonedWith`] via [`Zoned::with`].
435 ///
436 /// # Construction from civil time
437 ///
438 /// A `Zoned` value can also be created from a civil time via the following
439 /// methods:
440 ///
441 /// * [`DateTime::in_tz`] does a Time Zone Database lookup given a time
442 /// zone name string.
443 /// * [`DateTime::to_zoned`] accepts a `TimeZone`.
444 /// * [`Date::in_tz`] does a Time Zone Database lookup given a time zone
445 /// name string and attempts to use midnight as the clock time.
446 /// * [`Date::to_zoned`] accepts a `TimeZone` and attempts to use midnight
447 /// as the clock time.
448 ///
449 /// Whenever one is converting from civil time to a zoned
450 /// datetime, it is possible for the civil time to be ambiguous.
451 /// That is, it might be a clock reading that could refer to
452 /// multiple possible instants in time, or it might be a clock
453 /// reading that never exists. The above routines will use a
454 /// [`Disambiguation::Compatible`]
455 /// strategy to automatically resolve these corner cases.
456 ///
457 /// If one wants to control how ambiguity is resolved (including
458 /// by returning an error), use [`TimeZone::to_ambiguous_zoned`]
459 /// and select the desired strategy via a method on
460 /// [`AmbiguousZoned`](crate::tz::AmbiguousZoned).
461 ///
462 /// # Example: What was the civil time in Tasmania at the Unix epoch?
463 ///
464 /// ```
465 /// use jiff::{tz::TimeZone, Timestamp, Zoned};
466 ///
467 /// let tz = TimeZone::get("Australia/Tasmania")?;
468 /// let zdt = Zoned::new(Timestamp::UNIX_EPOCH, tz);
469 /// assert_eq!(
470 /// zdt.to_string(),
471 /// "1970-01-01T11:00:00+11:00[Australia/Tasmania]",
472 /// );
473 ///
474 /// # Ok::<(), Box<dyn std::error::Error>>(())
475 /// ```
476 ///
477 /// # Example: What was the civil time in New York when World War 1 ended?
478 ///
479 /// ```
480 /// use jiff::civil::date;
481 ///
482 /// let zdt1 = date(1918, 11, 11).at(11, 0, 0, 0).in_tz("Europe/Paris")?;
483 /// let zdt2 = zdt1.in_tz("America/New_York")?;
484 /// assert_eq!(
485 /// zdt2.to_string(),
486 /// "1918-11-11T06:00:00-05:00[America/New_York]",
487 /// );
488 ///
489 /// # Ok::<(), Box<dyn std::error::Error>>(())
490 /// ```
491 #[inline]
492 pub fn new(timestamp: Timestamp, time_zone: TimeZone) -> Zoned {
493 let offset = time_zone.to_offset(timestamp);
494 let datetime = offset.to_datetime(timestamp);
495 let inner = ZonedInner { timestamp, datetime, offset, time_zone };
496 Zoned { inner }
497 }
498
499 /// A crate internal constructor for building a `Zoned` from its
500 /// constituent parts.
501 ///
502 /// This should basically never be exposed, because it can be quite tricky
503 /// to get the parts correct.
504 ///
505 /// See `civil::DateTime::to_zoned` for a use case for this routine. (Why
506 /// do you think? Perf!)
507 #[inline]
508 pub(crate) fn from_parts(
509 timestamp: Timestamp,
510 time_zone: TimeZone,
511 offset: Offset,
512 datetime: DateTime,
513 ) -> Zoned {
514 let inner = ZonedInner { timestamp, datetime, offset, time_zone };
515 Zoned { inner }
516 }
517
518 /// Create a builder for constructing a new `DateTime` from the fields of
519 /// this datetime.
520 ///
521 /// See the methods on [`ZonedWith`] for the different ways one can set
522 /// the fields of a new `Zoned`.
523 ///
524 /// Note that this doesn't support changing the time zone. If you want a
525 /// `Zoned` value of the same instant but in a different time zone, use
526 /// [`Zoned::in_tz`] or [`Zoned::with_time_zone`]. If you want a `Zoned`
527 /// value of the same civil datetime (assuming it isn't ambiguous) but in
528 /// a different time zone, then use [`Zoned::datetime`] followed by
529 /// [`DateTime::in_tz`] or [`DateTime::to_zoned`].
530 ///
531 /// # Example
532 ///
533 /// The builder ensures one can chain together the individual components
534 /// of a zoned datetime without it failing at an intermediate step. For
535 /// example, if you had a date of `2024-10-31T00:00:00[America/New_York]`
536 /// and wanted to change both the day and the month, and each setting was
537 /// validated independent of the other, you would need to be careful to set
538 /// the day first and then the month. In some cases, you would need to set
539 /// the month first and then the day!
540 ///
541 /// But with the builder, you can set values in any order:
542 ///
543 /// ```
544 /// use jiff::civil::date;
545 ///
546 /// let zdt1 = date(2024, 10, 31).at(0, 0, 0, 0).in_tz("America/New_York")?;
547 /// let zdt2 = zdt1.with().month(11).day(30).build()?;
548 /// assert_eq!(
549 /// zdt2,
550 /// date(2024, 11, 30).at(0, 0, 0, 0).in_tz("America/New_York")?,
551 /// );
552 ///
553 /// let zdt1 = date(2024, 4, 30).at(0, 0, 0, 0).in_tz("America/New_York")?;
554 /// let zdt2 = zdt1.with().day(31).month(7).build()?;
555 /// assert_eq!(
556 /// zdt2,
557 /// date(2024, 7, 31).at(0, 0, 0, 0).in_tz("America/New_York")?,
558 /// );
559 ///
560 /// # Ok::<(), Box<dyn std::error::Error>>(())
561 /// ```
562 #[inline]
563 pub fn with(&self) -> ZonedWith {
564 ZonedWith::new(self.clone())
565 }
566
567 /// Return a new zoned datetime with precisely the same instant in a
568 /// different time zone.
569 ///
570 /// The zoned datetime returned is guaranteed to have an equivalent
571 /// [`Timestamp`]. However, its civil [`DateTime`] may be different.
572 ///
573 /// # Example: What was the civil time in New York when World War 1 ended?
574 ///
575 /// ```
576 /// use jiff::{civil::date, tz::TimeZone};
577 ///
578 /// let from = TimeZone::get("Europe/Paris")?;
579 /// let to = TimeZone::get("America/New_York")?;
580 /// let zdt1 = date(1918, 11, 11).at(11, 0, 0, 0).to_zoned(from)?;
581 /// // Switch zdt1 to a different time zone, but keeping the same instant
582 /// // in time. The civil time changes, but not the instant!
583 /// let zdt2 = zdt1.with_time_zone(to);
584 /// assert_eq!(
585 /// zdt2.to_string(),
586 /// "1918-11-11T06:00:00-05:00[America/New_York]",
587 /// );
588 ///
589 /// # Ok::<(), Box<dyn std::error::Error>>(())
590 /// ```
591 #[inline]
592 pub fn with_time_zone(&self, time_zone: TimeZone) -> Zoned {
593 Zoned::new(self.timestamp(), time_zone)
594 }
595
596 /// Return a new zoned datetime with precisely the same instant in a
597 /// different time zone.
598 ///
599 /// The zoned datetime returned is guaranteed to have an equivalent
600 /// [`Timestamp`]. However, its civil [`DateTime`] may be different.
601 ///
602 /// The name given is resolved to a [`TimeZone`] by using the default
603 /// [`TimeZoneDatabase`](crate::tz::TimeZoneDatabase) created by
604 /// [`tz::db`](crate::tz::db). Indeed, this is a convenience function for
605 /// [`DateTime::to_zoned`] where the time zone database lookup is done
606 /// automatically.
607 ///
608 /// # Errors
609 ///
610 /// This returns an error when the given time zone name could not be found
611 /// in the default time zone database.
612 ///
613 /// # Example: What was the civil time in New York when World War 1 ended?
614 ///
615 /// ```
616 /// use jiff::civil::date;
617 ///
618 /// let zdt1 = date(1918, 11, 11).at(11, 0, 0, 0).in_tz("Europe/Paris")?;
619 /// // Switch zdt1 to a different time zone, but keeping the same instant
620 /// // in time. The civil time changes, but not the instant!
621 /// let zdt2 = zdt1.in_tz("America/New_York")?;
622 /// assert_eq!(
623 /// zdt2.to_string(),
624 /// "1918-11-11T06:00:00-05:00[America/New_York]",
625 /// );
626 ///
627 /// # Ok::<(), Box<dyn std::error::Error>>(())
628 /// ```
629 #[inline]
630 pub fn in_tz(&self, name: &str) -> Result<Zoned, Error> {
631 let tz = crate::tz::db().get(name)?;
632 Ok(self.with_time_zone(tz))
633 }
634
635 /// Returns the time zone attached to this [`Zoned`] value.
636 ///
637 /// A time zone is more than just an offset. A time zone is a series of
638 /// rules for determining the civil time for a corresponding instant.
639 /// Indeed, a zoned datetime uses its time zone to perform zone-aware
640 /// arithmetic, rounding and serialization.
641 ///
642 /// # Example
643 ///
644 /// ```
645 /// use jiff::Zoned;
646 ///
647 /// let zdt: Zoned = "2024-07-03 14:31[america/new_york]".parse()?;
648 /// assert_eq!(zdt.time_zone().iana_name(), Some("America/New_York"));
649 ///
650 /// # Ok::<(), Box<dyn std::error::Error>>(())
651 /// ```
652 #[inline]
653 pub fn time_zone(&self) -> &TimeZone {
654 &self.inner.time_zone
655 }
656
657 /// Returns the year for this zoned datetime.
658 ///
659 /// The value returned is guaranteed to be in the range `-9999..=9999`.
660 ///
661 /// # Example
662 ///
663 /// ```
664 /// use jiff::civil::date;
665 ///
666 /// let zdt1 = date(2024, 3, 9).at(7, 30, 0, 0).in_tz("America/New_York")?;
667 /// assert_eq!(zdt1.year(), 2024);
668 ///
669 /// let zdt2 = date(-2024, 3, 9).at(7, 30, 0, 0).in_tz("America/New_York")?;
670 /// assert_eq!(zdt2.year(), -2024);
671 ///
672 /// let zdt3 = date(0, 3, 9).at(7, 30, 0, 0).in_tz("America/New_York")?;
673 /// assert_eq!(zdt3.year(), 0);
674 ///
675 /// # Ok::<(), Box<dyn std::error::Error>>(())
676 /// ```
677 #[inline]
678 pub fn year(&self) -> i16 {
679 self.date().year()
680 }
681
682 /// Returns the year and its era.
683 ///
684 /// This crate specifically allows years to be negative or `0`, where as
685 /// years written for the Gregorian calendar are always positive and
686 /// greater than `0`. In the Gregorian calendar, the era labels `BCE` and
687 /// `CE` are used to disambiguate between years less than or equal to `0`
688 /// and years greater than `0`, respectively.
689 ///
690 /// The crate is designed this way so that years in the latest era (that
691 /// is, `CE`) are aligned with years in this crate.
692 ///
693 /// The year returned is guaranteed to be in the range `1..=10000`.
694 ///
695 /// # Example
696 ///
697 /// ```
698 /// use jiff::civil::{Era, date};
699 ///
700 /// let zdt = date(2024, 10, 3).at(7, 30, 0, 0).in_tz("America/New_York")?;
701 /// assert_eq!(zdt.era_year(), (2024, Era::CE));
702 ///
703 /// let zdt = date(1, 10, 3).at(7, 30, 0, 0).in_tz("America/New_York")?;
704 /// assert_eq!(zdt.era_year(), (1, Era::CE));
705 ///
706 /// let zdt = date(0, 10, 3).at(7, 30, 0, 0).in_tz("America/New_York")?;
707 /// assert_eq!(zdt.era_year(), (1, Era::BCE));
708 ///
709 /// let zdt = date(-1, 10, 3).at(7, 30, 0, 0).in_tz("America/New_York")?;
710 /// assert_eq!(zdt.era_year(), (2, Era::BCE));
711 ///
712 /// let zdt = date(-10, 10, 3).at(7, 30, 0, 0).in_tz("America/New_York")?;
713 /// assert_eq!(zdt.era_year(), (11, Era::BCE));
714 ///
715 /// let zdt = date(-9_999, 10, 3).at(7, 30, 0, 0).in_tz("America/New_York")?;
716 /// assert_eq!(zdt.era_year(), (10_000, Era::BCE));
717 ///
718 /// # Ok::<(), Box<dyn std::error::Error>>(())
719 /// ```
720 #[inline]
721 pub fn era_year(&self) -> (i16, Era) {
722 self.date().era_year()
723 }
724
725 /// Returns the month for this zoned datetime.
726 ///
727 /// The value returned is guaranteed to be in the range `1..=12`.
728 ///
729 /// # Example
730 ///
731 /// ```
732 /// use jiff::civil::date;
733 ///
734 /// let zdt = date(2024, 3, 9).at(7, 30, 0, 0).in_tz("America/New_York")?;
735 /// assert_eq!(zdt.month(), 3);
736 ///
737 /// # Ok::<(), Box<dyn std::error::Error>>(())
738 /// ```
739 #[inline]
740 pub fn month(&self) -> i8 {
741 self.date().month()
742 }
743
744 /// Returns the day for this zoned datetime.
745 ///
746 /// The value returned is guaranteed to be in the range `1..=31`.
747 ///
748 /// # Example
749 ///
750 /// ```
751 /// use jiff::civil::date;
752 ///
753 /// let zdt = date(2024, 2, 29).at(7, 30, 0, 0).in_tz("America/New_York")?;
754 /// assert_eq!(zdt.day(), 29);
755 ///
756 /// # Ok::<(), Box<dyn std::error::Error>>(())
757 /// ```
758 #[inline]
759 pub fn day(&self) -> i8 {
760 self.date().day()
761 }
762
763 /// Returns the "hour" component of this zoned datetime.
764 ///
765 /// The value returned is guaranteed to be in the range `0..=23`.
766 ///
767 /// # Example
768 ///
769 /// ```
770 /// use jiff::civil::date;
771 ///
772 /// let zdt = date(2000, 1, 2)
773 /// .at(3, 4, 5, 123_456_789)
774 /// .in_tz("America/New_York")?;
775 /// assert_eq!(zdt.hour(), 3);
776 ///
777 /// # Ok::<(), Box<dyn std::error::Error>>(())
778 /// ```
779 #[inline]
780 pub fn hour(&self) -> i8 {
781 self.time().hour()
782 }
783
784 /// Returns the "minute" component of this zoned datetime.
785 ///
786 /// The value returned is guaranteed to be in the range `0..=59`.
787 ///
788 /// # Example
789 ///
790 /// ```
791 /// use jiff::civil::date;
792 ///
793 /// let zdt = date(2000, 1, 2)
794 /// .at(3, 4, 5, 123_456_789)
795 /// .in_tz("America/New_York")?;
796 /// assert_eq!(zdt.minute(), 4);
797 ///
798 /// # Ok::<(), Box<dyn std::error::Error>>(())
799 /// ```
800 #[inline]
801 pub fn minute(&self) -> i8 {
802 self.time().minute()
803 }
804
805 /// Returns the "second" component of this zoned datetime.
806 ///
807 /// The value returned is guaranteed to be in the range `0..=59`.
808 ///
809 /// # Example
810 ///
811 /// ```
812 /// use jiff::civil::date;
813 ///
814 /// let zdt = date(2000, 1, 2)
815 /// .at(3, 4, 5, 123_456_789)
816 /// .in_tz("America/New_York")?;
817 /// assert_eq!(zdt.second(), 5);
818 ///
819 /// # Ok::<(), Box<dyn std::error::Error>>(())
820 /// ```
821 #[inline]
822 pub fn second(&self) -> i8 {
823 self.time().second()
824 }
825
826 /// Returns the "millisecond" component of this zoned datetime.
827 ///
828 /// The value returned is guaranteed to be in the range `0..=999`.
829 ///
830 /// # Example
831 ///
832 /// ```
833 /// use jiff::civil::date;
834 ///
835 /// let zdt = date(2000, 1, 2)
836 /// .at(3, 4, 5, 123_456_789)
837 /// .in_tz("America/New_York")?;
838 /// assert_eq!(zdt.millisecond(), 123);
839 ///
840 /// # Ok::<(), Box<dyn std::error::Error>>(())
841 /// ```
842 #[inline]
843 pub fn millisecond(&self) -> i16 {
844 self.time().millisecond()
845 }
846
847 /// Returns the "microsecond" component of this zoned datetime.
848 ///
849 /// The value returned is guaranteed to be in the range `0..=999`.
850 ///
851 /// # Example
852 ///
853 /// ```
854 /// use jiff::civil::date;
855 ///
856 /// let zdt = date(2000, 1, 2)
857 /// .at(3, 4, 5, 123_456_789)
858 /// .in_tz("America/New_York")?;
859 /// assert_eq!(zdt.microsecond(), 456);
860 ///
861 /// # Ok::<(), Box<dyn std::error::Error>>(())
862 /// ```
863 #[inline]
864 pub fn microsecond(&self) -> i16 {
865 self.time().microsecond()
866 }
867
868 /// Returns the "nanosecond" component of this zoned datetime.
869 ///
870 /// The value returned is guaranteed to be in the range `0..=999`.
871 ///
872 /// # Example
873 ///
874 /// ```
875 /// use jiff::civil::date;
876 ///
877 /// let zdt = date(2000, 1, 2)
878 /// .at(3, 4, 5, 123_456_789)
879 /// .in_tz("America/New_York")?;
880 /// assert_eq!(zdt.nanosecond(), 789);
881 ///
882 /// # Ok::<(), Box<dyn std::error::Error>>(())
883 /// ```
884 #[inline]
885 pub fn nanosecond(&self) -> i16 {
886 self.time().nanosecond()
887 }
888
889 /// Returns the fractional nanosecond for this `Zoned` value.
890 ///
891 /// If you want to set this value on `Zoned`, then use
892 /// [`ZonedWith::subsec_nanosecond`] via [`Zoned::with`].
893 ///
894 /// The value returned is guaranteed to be in the range `0..=999_999_999`.
895 ///
896 /// Note that this returns the fractional second associated with the civil
897 /// time on this `Zoned` value. This is distinct from the fractional
898 /// second on the underlying timestamp. A timestamp, for example, may be
899 /// negative to indicate time before the Unix epoch. But a civil datetime
900 /// can only have a negative year, while the remaining values are all
901 /// semantically positive. See the examples below for how this can manifest
902 /// in practice.
903 ///
904 /// # Example
905 ///
906 /// This shows the relationship between constructing a `Zoned` value
907 /// with routines like `with().millisecond()` and accessing the entire
908 /// fractional part as a nanosecond:
909 ///
910 /// ```
911 /// use jiff::civil::date;
912 ///
913 /// let zdt1 = date(2000, 1, 2)
914 /// .at(3, 4, 5, 123_456_789)
915 /// .in_tz("America/New_York")?;
916 /// assert_eq!(zdt1.subsec_nanosecond(), 123_456_789);
917 ///
918 /// let zdt2 = zdt1.with().millisecond(333).build()?;
919 /// assert_eq!(zdt2.subsec_nanosecond(), 333_456_789);
920 ///
921 /// # Ok::<(), Box<dyn std::error::Error>>(())
922 /// ```
923 ///
924 /// # Example: nanoseconds from a timestamp
925 ///
926 /// This shows how the fractional nanosecond part of a `Zoned` value
927 /// manifests from a specific timestamp.
928 ///
929 /// ```
930 /// use jiff::Timestamp;
931 ///
932 /// // 1,234 nanoseconds after the Unix epoch.
933 /// let zdt = Timestamp::new(0, 1_234)?.in_tz("UTC")?;
934 /// assert_eq!(zdt.subsec_nanosecond(), 1_234);
935 /// // N.B. The timestamp's fractional second and the civil datetime's
936 /// // fractional second happen to be equal here:
937 /// assert_eq!(zdt.timestamp().subsec_nanosecond(), 1_234);
938 ///
939 /// # Ok::<(), Box<dyn std::error::Error>>(())
940 /// ```
941 ///
942 /// # Example: fractional seconds can differ between timestamps and civil time
943 ///
944 /// This shows how a timestamp can have a different fractional second
945 /// value than its corresponding `Zoned` value because of how the sign
946 /// is handled:
947 ///
948 /// ```
949 /// use jiff::{civil, Timestamp};
950 ///
951 /// // 1,234 nanoseconds before the Unix epoch.
952 /// let zdt = Timestamp::new(0, -1_234)?.in_tz("UTC")?;
953 /// // The timestamp's fractional second is what was given:
954 /// assert_eq!(zdt.timestamp().subsec_nanosecond(), -1_234);
955 /// // But the civil datetime's fractional second is equal to
956 /// // `1_000_000_000 - 1_234`. This is because civil datetimes
957 /// // represent times in strictly positive values, like it
958 /// // would read on a clock.
959 /// assert_eq!(zdt.subsec_nanosecond(), 999998766);
960 /// // Looking at the other components of the time value might help.
961 /// assert_eq!(zdt.hour(), 23);
962 /// assert_eq!(zdt.minute(), 59);
963 /// assert_eq!(zdt.second(), 59);
964 ///
965 /// # Ok::<(), Box<dyn std::error::Error>>(())
966 /// ```
967 #[inline]
968 pub fn subsec_nanosecond(&self) -> i32 {
969 self.time().subsec_nanosecond()
970 }
971
972 /// Returns the weekday corresponding to this zoned datetime.
973 ///
974 /// # Example
975 ///
976 /// ```
977 /// use jiff::civil::{Weekday, date};
978 ///
979 /// // The Unix epoch was on a Thursday.
980 /// let zdt = date(1970, 1, 1).at(7, 30, 0, 0).in_tz("America/New_York")?;
981 /// assert_eq!(zdt.weekday(), Weekday::Thursday);
982 /// // One can also get the weekday as an offset in a variety of schemes.
983 /// assert_eq!(zdt.weekday().to_monday_zero_offset(), 3);
984 /// assert_eq!(zdt.weekday().to_monday_one_offset(), 4);
985 /// assert_eq!(zdt.weekday().to_sunday_zero_offset(), 4);
986 /// assert_eq!(zdt.weekday().to_sunday_one_offset(), 5);
987 ///
988 /// # Ok::<(), Box<dyn std::error::Error>>(())
989 /// ```
990 #[inline]
991 pub fn weekday(&self) -> Weekday {
992 self.date().weekday()
993 }
994
995 /// Returns the ordinal day of the year that this zoned datetime resides
996 /// in.
997 ///
998 /// For leap years, this always returns a value in the range `1..=366`.
999 /// Otherwise, the value is in the range `1..=365`.
1000 ///
1001 /// # Example
1002 ///
1003 /// ```
1004 /// use jiff::civil::date;
1005 ///
1006 /// let zdt = date(2006, 8, 24).at(7, 30, 0, 0).in_tz("America/New_York")?;
1007 /// assert_eq!(zdt.day_of_year(), 236);
1008 ///
1009 /// let zdt = date(2023, 12, 31).at(7, 30, 0, 0).in_tz("America/New_York")?;
1010 /// assert_eq!(zdt.day_of_year(), 365);
1011 ///
1012 /// let zdt = date(2024, 12, 31).at(7, 30, 0, 0).in_tz("America/New_York")?;
1013 /// assert_eq!(zdt.day_of_year(), 366);
1014 ///
1015 /// # Ok::<(), Box<dyn std::error::Error>>(())
1016 /// ```
1017 #[inline]
1018 pub fn day_of_year(&self) -> i16 {
1019 self.date().day_of_year()
1020 }
1021
1022 /// Returns the ordinal day of the year that this zoned datetime resides
1023 /// in, but ignores leap years.
1024 ///
1025 /// That is, the range of possible values returned by this routine is
1026 /// `1..=365`, even if this date resides in a leap year. If this date is
1027 /// February 29, then this routine returns `None`.
1028 ///
1029 /// The value `365` always corresponds to the last day in the year,
1030 /// December 31, even for leap years.
1031 ///
1032 /// # Example
1033 ///
1034 /// ```
1035 /// use jiff::civil::date;
1036 ///
1037 /// let zdt = date(2006, 8, 24).at(7, 30, 0, 0).in_tz("America/New_York")?;
1038 /// assert_eq!(zdt.day_of_year_no_leap(), Some(236));
1039 ///
1040 /// let zdt = date(2023, 12, 31).at(7, 30, 0, 0).in_tz("America/New_York")?;
1041 /// assert_eq!(zdt.day_of_year_no_leap(), Some(365));
1042 ///
1043 /// let zdt = date(2024, 12, 31).at(7, 30, 0, 0).in_tz("America/New_York")?;
1044 /// assert_eq!(zdt.day_of_year_no_leap(), Some(365));
1045 ///
1046 /// let zdt = date(2024, 2, 29).at(7, 30, 0, 0).in_tz("America/New_York")?;
1047 /// assert_eq!(zdt.day_of_year_no_leap(), None);
1048 ///
1049 /// # Ok::<(), Box<dyn std::error::Error>>(())
1050 /// ```
1051 #[inline]
1052 pub fn day_of_year_no_leap(&self) -> Option<i16> {
1053 self.date().day_of_year_no_leap()
1054 }
1055
1056 /// Returns the beginning of the day, corresponding to `00:00:00` civil
1057 /// time, that this datetime resides in.
1058 ///
1059 /// While in nearly all cases the time returned will be `00:00:00`, it is
1060 /// possible for the time to be different from midnight if there is a time
1061 /// zone transition at midnight.
1062 ///
1063 /// # Example
1064 ///
1065 /// ```
1066 /// use jiff::{civil::date, Zoned};
1067 ///
1068 /// let zdt = date(2015, 10, 18).at(12, 0, 0, 0).in_tz("America/New_York")?;
1069 /// assert_eq!(
1070 /// zdt.start_of_day()?.to_string(),
1071 /// "2015-10-18T00:00:00-04:00[America/New_York]",
1072 /// );
1073 ///
1074 /// # Ok::<(), Box<dyn std::error::Error>>(())
1075 /// ```
1076 ///
1077 /// # Example: start of day may not be midnight
1078 ///
1079 /// In some time zones, gap transitions may begin at midnight. This implies
1080 /// that `00:xx:yy` does not exist on a clock in that time zone for that
1081 /// day.
1082 ///
1083 /// ```
1084 /// use jiff::{civil::date, Zoned};
1085 ///
1086 /// let zdt = date(2015, 10, 18).at(12, 0, 0, 0).in_tz("America/Sao_Paulo")?;
1087 /// assert_eq!(
1088 /// zdt.start_of_day()?.to_string(),
1089 /// // not midnight!
1090 /// "2015-10-18T01:00:00-02:00[America/Sao_Paulo]",
1091 /// );
1092 ///
1093 /// # Ok::<(), Box<dyn std::error::Error>>(())
1094 /// ```
1095 ///
1096 /// # Example: error because of overflow
1097 ///
1098 /// In some cases, it's possible for `Zoned` value to be able to represent
1099 /// an instant in time later in the day for a particular time zone, but not
1100 /// earlier in the day. This can only occur near the minimum datetime value
1101 /// supported by Jiff.
1102 ///
1103 /// ```
1104 /// use jiff::{civil::date, tz::{TimeZone, Offset}, Zoned};
1105 ///
1106 /// // While -9999-01-03T04:00:00+25:59:59 is representable as a Zoned
1107 /// // value, the start of the corresponding day is not!
1108 /// let tz = TimeZone::fixed(Offset::MAX);
1109 /// let zdt = date(-9999, 1, 3).at(4, 0, 0, 0).to_zoned(tz.clone())?;
1110 /// assert!(zdt.start_of_day().is_err());
1111 /// // The next day works fine since -9999-01-04T00:00:00+25:59:59 is
1112 /// // representable.
1113 /// let zdt = date(-9999, 1, 4).at(15, 0, 0, 0).to_zoned(tz)?;
1114 /// assert_eq!(
1115 /// zdt.start_of_day()?.datetime(),
1116 /// date(-9999, 1, 4).at(0, 0, 0, 0),
1117 /// );
1118 ///
1119 /// # Ok::<(), Box<dyn std::error::Error>>(())
1120 /// ```
1121 #[inline]
1122 pub fn start_of_day(&self) -> Result<Zoned, Error> {
1123 self.datetime().start_of_day().to_zoned(self.time_zone().clone())
1124 }
1125
1126 /// Returns the end of the day, corresponding to `23:59:59.999999999` civil
1127 /// time, that this datetime resides in.
1128 ///
1129 /// While in nearly all cases the time returned will be
1130 /// `23:59:59.999999999`, it is possible for the time to be different if
1131 /// there is a time zone transition covering that time.
1132 ///
1133 /// # Example
1134 ///
1135 /// ```
1136 /// use jiff::civil::date;
1137 ///
1138 /// let zdt = date(2024, 7, 3)
1139 /// .at(7, 30, 10, 123_456_789)
1140 /// .in_tz("America/New_York")?;
1141 /// assert_eq!(
1142 /// zdt.end_of_day()?,
1143 /// date(2024, 7, 3)
1144 /// .at(23, 59, 59, 999_999_999)
1145 /// .in_tz("America/New_York")?,
1146 /// );
1147 ///
1148 /// # Ok::<(), Box<dyn std::error::Error>>(())
1149 /// ```
1150 ///
1151 /// # Example: error because of overflow
1152 ///
1153 /// In some cases, it's possible for `Zoned` value to be able to represent
1154 /// an instant in time earlier in the day for a particular time zone, but
1155 /// not later in the day. This can only occur near the maximum datetime
1156 /// value supported by Jiff.
1157 ///
1158 /// ```
1159 /// use jiff::{civil::date, tz::{TimeZone, Offset}, Zoned};
1160 ///
1161 /// // While 9999-12-30T01:30-04 is representable as a Zoned
1162 /// // value, the start of the corresponding day is not!
1163 /// let tz = TimeZone::get("America/New_York")?;
1164 /// let zdt = date(9999, 12, 30).at(1, 30, 0, 0).to_zoned(tz.clone())?;
1165 /// assert!(zdt.end_of_day().is_err());
1166 /// // The previous day works fine since 9999-12-29T23:59:59.999999999-04
1167 /// // is representable.
1168 /// let zdt = date(9999, 12, 29).at(1, 30, 0, 0).to_zoned(tz.clone())?;
1169 /// assert_eq!(
1170 /// zdt.end_of_day()?,
1171 /// date(9999, 12, 29)
1172 /// .at(23, 59, 59, 999_999_999)
1173 /// .in_tz("America/New_York")?,
1174 /// );
1175 ///
1176 /// # Ok::<(), Box<dyn std::error::Error>>(())
1177 /// ```
1178 #[inline]
1179 pub fn end_of_day(&self) -> Result<Zoned, Error> {
1180 let end_of_civil_day = self.datetime().end_of_day();
1181 let ambts = self.time_zone().to_ambiguous_timestamp(end_of_civil_day);
1182 // I'm not sure if there are any real world cases where this matters,
1183 // but this is basically the reverse of `compatible`, so we write
1184 // it out ourselves. Basically, if the last civil datetime is in a
1185 // gap, then we want the earlier instant since the later instant must
1186 // necessarily be in the next day. And if the last civil datetime is
1187 // in a fold, then we want the later instant since both the earlier
1188 // and later instants are in the same calendar day and the later one
1189 // must be, well, later. In contrast, compatible mode takes the later
1190 // instant in a gap and the earlier instant in a fold. So we flip that
1191 // here.
1192 let offset = match ambts.offset() {
1193 AmbiguousOffset::Unambiguous { offset } => offset,
1194 AmbiguousOffset::Gap { after, .. } => after,
1195 AmbiguousOffset::Fold { after, .. } => after,
1196 };
1197 offset
1198 .to_timestamp(end_of_civil_day)
1199 .map(|ts| ts.to_zoned(self.time_zone().clone()))
1200 }
1201
1202 /// Returns the first date of the month that this zoned datetime resides
1203 /// in.
1204 ///
1205 /// In most cases, the time in the zoned datetime returned remains
1206 /// unchanged. In some cases, the time may change if the time
1207 /// on the previous date was unambiguous (always true, since a
1208 /// `Zoned` is a precise instant in time) and the same clock time
1209 /// on the returned zoned datetime is ambiguous. In this case, the
1210 /// [`Disambiguation::Compatible`]
1211 /// strategy will be used to turn it into a precise instant. If you want to
1212 /// use a different disambiguation strategy, then use [`Zoned::datetime`]
1213 /// to get the civil datetime, then use [`DateTime::first_of_month`],
1214 /// then use [`TimeZone::to_ambiguous_zoned`] and apply your preferred
1215 /// disambiguation strategy.
1216 ///
1217 /// # Example
1218 ///
1219 /// ```
1220 /// use jiff::civil::date;
1221 ///
1222 /// let zdt = date(2024, 2, 29).at(7, 30, 0, 0).in_tz("America/New_York")?;
1223 /// assert_eq!(
1224 /// zdt.first_of_month()?,
1225 /// date(2024, 2, 1).at(7, 30, 0, 0).in_tz("America/New_York")?,
1226 /// );
1227 ///
1228 /// # Ok::<(), Box<dyn std::error::Error>>(())
1229 /// ```
1230 #[inline]
1231 pub fn first_of_month(&self) -> Result<Zoned, Error> {
1232 self.datetime().first_of_month().to_zoned(self.time_zone().clone())
1233 }
1234
1235 /// Returns the last date of the month that this zoned datetime resides in.
1236 ///
1237 /// In most cases, the time in the zoned datetime returned remains
1238 /// unchanged. In some cases, the time may change if the time
1239 /// on the previous date was unambiguous (always true, since a
1240 /// `Zoned` is a precise instant in time) and the same clock time
1241 /// on the returned zoned datetime is ambiguous. In this case, the
1242 /// [`Disambiguation::Compatible`]
1243 /// strategy will be used to turn it into a precise instant. If you want to
1244 /// use a different disambiguation strategy, then use [`Zoned::datetime`]
1245 /// to get the civil datetime, then use [`DateTime::last_of_month`],
1246 /// then use [`TimeZone::to_ambiguous_zoned`] and apply your preferred
1247 /// disambiguation strategy.
1248 ///
1249 /// # Example
1250 ///
1251 /// ```
1252 /// use jiff::civil::date;
1253 ///
1254 /// let zdt = date(2024, 2, 5).at(7, 30, 0, 0).in_tz("America/New_York")?;
1255 /// assert_eq!(
1256 /// zdt.last_of_month()?,
1257 /// date(2024, 2, 29).at(7, 30, 0, 0).in_tz("America/New_York")?,
1258 /// );
1259 ///
1260 /// # Ok::<(), Box<dyn std::error::Error>>(())
1261 /// ```
1262 #[inline]
1263 pub fn last_of_month(&self) -> Result<Zoned, Error> {
1264 self.datetime().last_of_month().to_zoned(self.time_zone().clone())
1265 }
1266
1267 /// Returns the ordinal number of the last day in the month in which this
1268 /// zoned datetime resides.
1269 ///
1270 /// This is phrased as "the ordinal number of the last day" instead of "the
1271 /// number of days" because some months may be missing days due to time
1272 /// zone transitions. However, this is extraordinarily rare.
1273 ///
1274 /// This is guaranteed to always return one of the following values,
1275 /// depending on the year and the month: 28, 29, 30 or 31.
1276 ///
1277 /// # Example
1278 ///
1279 /// ```
1280 /// use jiff::civil::date;
1281 ///
1282 /// let zdt = date(2024, 2, 10).at(7, 30, 0, 0).in_tz("America/New_York")?;
1283 /// assert_eq!(zdt.days_in_month(), 29);
1284 ///
1285 /// let zdt = date(2023, 2, 10).at(7, 30, 0, 0).in_tz("America/New_York")?;
1286 /// assert_eq!(zdt.days_in_month(), 28);
1287 ///
1288 /// let zdt = date(2024, 8, 15).at(7, 30, 0, 0).in_tz("America/New_York")?;
1289 /// assert_eq!(zdt.days_in_month(), 31);
1290 ///
1291 /// # Ok::<(), Box<dyn std::error::Error>>(())
1292 /// ```
1293 ///
1294 /// # Example: count of days in month
1295 ///
1296 /// In `Pacific/Apia`, December 2011 did not have a December 30. Instead,
1297 /// the calendar [skipped from December 29 right to December 31][samoa].
1298 ///
1299 /// If you really do need the count of days in a month in a time zone
1300 /// aware fashion, then it's possible to achieve through arithmetic:
1301 ///
1302 /// ```
1303 /// use jiff::{civil::date, RoundMode, ToSpan, Unit, ZonedDifference};
1304 ///
1305 /// let first_of_month = date(2011, 12, 1).in_tz("Pacific/Apia")?;
1306 /// assert_eq!(first_of_month.days_in_month(), 31);
1307 /// let one_month_later = first_of_month.checked_add(1.month())?;
1308 ///
1309 /// let options = ZonedDifference::new(&one_month_later)
1310 /// .largest(Unit::Hour)
1311 /// .smallest(Unit::Hour)
1312 /// .mode(RoundMode::HalfExpand);
1313 /// let span = first_of_month.until(options)?;
1314 /// let days = ((span.get_hours() as f64) / 24.0).round() as i64;
1315 /// // Try the above in a different time zone, like America/New_York, and
1316 /// // you'll get 31 here.
1317 /// assert_eq!(days, 30);
1318 ///
1319 /// # Ok::<(), Box<dyn std::error::Error>>(())
1320 /// ```
1321 ///
1322 /// [samoa]: https://en.wikipedia.org/wiki/Time_in_Samoa#2011_time_zone_change
1323 #[inline]
1324 pub fn days_in_month(&self) -> i8 {
1325 self.date().days_in_month()
1326 }
1327
1328 /// Returns the first date of the year that this zoned datetime resides in.
1329 ///
1330 /// In most cases, the time in the zoned datetime returned remains
1331 /// unchanged. In some cases, the time may change if the time
1332 /// on the previous date was unambiguous (always true, since a
1333 /// `Zoned` is a precise instant in time) and the same clock time
1334 /// on the returned zoned datetime is ambiguous. In this case, the
1335 /// [`Disambiguation::Compatible`]
1336 /// strategy will be used to turn it into a precise instant. If you want to
1337 /// use a different disambiguation strategy, then use [`Zoned::datetime`]
1338 /// to get the civil datetime, then use [`DateTime::first_of_year`],
1339 /// then use [`TimeZone::to_ambiguous_zoned`] and apply your preferred
1340 /// disambiguation strategy.
1341 ///
1342 /// # Example
1343 ///
1344 /// ```
1345 /// use jiff::civil::date;
1346 ///
1347 /// let zdt = date(2024, 2, 29).at(7, 30, 0, 0).in_tz("America/New_York")?;
1348 /// assert_eq!(
1349 /// zdt.first_of_year()?,
1350 /// date(2024, 1, 1).at(7, 30, 0, 0).in_tz("America/New_York")?,
1351 /// );
1352 ///
1353 /// # Ok::<(), Box<dyn std::error::Error>>(())
1354 /// ```
1355 #[inline]
1356 pub fn first_of_year(&self) -> Result<Zoned, Error> {
1357 self.datetime().first_of_year().to_zoned(self.time_zone().clone())
1358 }
1359
1360 /// Returns the last date of the year that this zoned datetime resides in.
1361 ///
1362 /// In most cases, the time in the zoned datetime returned remains
1363 /// unchanged. In some cases, the time may change if the time
1364 /// on the previous date was unambiguous (always true, since a
1365 /// `Zoned` is a precise instant in time) and the same clock time
1366 /// on the returned zoned datetime is ambiguous. In this case, the
1367 /// [`Disambiguation::Compatible`]
1368 /// strategy will be used to turn it into a precise instant. If you want to
1369 /// use a different disambiguation strategy, then use [`Zoned::datetime`]
1370 /// to get the civil datetime, then use [`DateTime::last_of_year`],
1371 /// then use [`TimeZone::to_ambiguous_zoned`] and apply your preferred
1372 /// disambiguation strategy.
1373 ///
1374 /// # Example
1375 ///
1376 /// ```
1377 /// use jiff::civil::date;
1378 ///
1379 /// let zdt = date(2024, 2, 5).at(7, 30, 0, 0).in_tz("America/New_York")?;
1380 /// assert_eq!(
1381 /// zdt.last_of_year()?,
1382 /// date(2024, 12, 31).at(7, 30, 0, 0).in_tz("America/New_York")?,
1383 /// );
1384 ///
1385 /// # Ok::<(), Box<dyn std::error::Error>>(())
1386 /// ```
1387 #[inline]
1388 pub fn last_of_year(&self) -> Result<Zoned, Error> {
1389 self.datetime().last_of_year().to_zoned(self.time_zone().clone())
1390 }
1391
1392 /// Returns the ordinal number of the last day in the year in which this
1393 /// zoned datetime resides.
1394 ///
1395 /// This is phrased as "the ordinal number of the last day" instead of "the
1396 /// number of days" because some years may be missing days due to time
1397 /// zone transitions. However, this is extraordinarily rare.
1398 ///
1399 /// This is guaranteed to always return either `365` or `366`.
1400 ///
1401 /// # Example
1402 ///
1403 /// ```
1404 /// use jiff::civil::date;
1405 ///
1406 /// let zdt = date(2024, 7, 10).at(7, 30, 0, 0).in_tz("America/New_York")?;
1407 /// assert_eq!(zdt.days_in_year(), 366);
1408 ///
1409 /// let zdt = date(2023, 7, 10).at(7, 30, 0, 0).in_tz("America/New_York")?;
1410 /// assert_eq!(zdt.days_in_year(), 365);
1411 ///
1412 /// # Ok::<(), Box<dyn std::error::Error>>(())
1413 /// ```
1414 #[inline]
1415 pub fn days_in_year(&self) -> i16 {
1416 self.date().days_in_year()
1417 }
1418
1419 /// Returns true if and only if the year in which this zoned datetime
1420 /// resides is a leap year.
1421 ///
1422 /// # Example
1423 ///
1424 /// ```
1425 /// use jiff::civil::date;
1426 ///
1427 /// let zdt = date(2024, 1, 1).at(7, 30, 0, 0).in_tz("America/New_York")?;
1428 /// assert!(zdt.in_leap_year());
1429 ///
1430 /// let zdt = date(2023, 12, 31).at(7, 30, 0, 0).in_tz("America/New_York")?;
1431 /// assert!(!zdt.in_leap_year());
1432 ///
1433 /// # Ok::<(), Box<dyn std::error::Error>>(())
1434 /// ```
1435 #[inline]
1436 pub fn in_leap_year(&self) -> bool {
1437 self.date().in_leap_year()
1438 }
1439
1440 /// Returns the zoned datetime with a date immediately following this one.
1441 ///
1442 /// In most cases, the time in the zoned datetime returned remains
1443 /// unchanged. In some cases, the time may change if the time
1444 /// on the previous date was unambiguous (always true, since a
1445 /// `Zoned` is a precise instant in time) and the same clock time
1446 /// on the returned zoned datetime is ambiguous. In this case, the
1447 /// [`Disambiguation::Compatible`]
1448 /// strategy will be used to turn it into a precise instant. If you want to
1449 /// use a different disambiguation strategy, then use [`Zoned::datetime`]
1450 /// to get the civil datetime, then use [`DateTime::tomorrow`],
1451 /// then use [`TimeZone::to_ambiguous_zoned`] and apply your preferred
1452 /// disambiguation strategy.
1453 ///
1454 /// # Errors
1455 ///
1456 /// This returns an error when one day following this zoned datetime would
1457 /// exceed the maximum `Zoned` value.
1458 ///
1459 /// # Example
1460 ///
1461 /// ```
1462 /// use jiff::{civil::date, Timestamp};
1463 ///
1464 /// let zdt = date(2024, 2, 28).at(7, 30, 0, 0).in_tz("America/New_York")?;
1465 /// assert_eq!(
1466 /// zdt.tomorrow()?,
1467 /// date(2024, 2, 29).at(7, 30, 0, 0).in_tz("America/New_York")?,
1468 /// );
1469 ///
1470 /// // The max doesn't have a tomorrow.
1471 /// assert!(Timestamp::MAX.in_tz("America/New_York")?.tomorrow().is_err());
1472 ///
1473 /// # Ok::<(), Box<dyn std::error::Error>>(())
1474 /// ```
1475 ///
1476 /// # Example: ambiguous datetimes are automatically resolved
1477 ///
1478 /// ```
1479 /// use jiff::{civil::date, Timestamp};
1480 ///
1481 /// let zdt = date(2024, 3, 9).at(2, 30, 0, 0).in_tz("America/New_York")?;
1482 /// assert_eq!(
1483 /// zdt.tomorrow()?,
1484 /// date(2024, 3, 10).at(3, 30, 0, 0).in_tz("America/New_York")?,
1485 /// );
1486 ///
1487 /// # Ok::<(), Box<dyn std::error::Error>>(())
1488 /// ```
1489 #[inline]
1490 pub fn tomorrow(&self) -> Result<Zoned, Error> {
1491 self.datetime().tomorrow()?.to_zoned(self.time_zone().clone())
1492 }
1493
1494 /// Returns the zoned datetime with a date immediately preceding this one.
1495 ///
1496 /// In most cases, the time in the zoned datetime returned remains
1497 /// unchanged. In some cases, the time may change if the time
1498 /// on the previous date was unambiguous (always true, since a
1499 /// `Zoned` is a precise instant in time) and the same clock time
1500 /// on the returned zoned datetime is ambiguous. In this case, the
1501 /// [`Disambiguation::Compatible`]
1502 /// strategy will be used to turn it into a precise instant. If you want to
1503 /// use a different disambiguation strategy, then use [`Zoned::datetime`]
1504 /// to get the civil datetime, then use [`DateTime::yesterday`],
1505 /// then use [`TimeZone::to_ambiguous_zoned`] and apply your preferred
1506 /// disambiguation strategy.
1507 ///
1508 /// # Errors
1509 ///
1510 /// This returns an error when one day preceding this zoned datetime would
1511 /// be less than the minimum `Zoned` value.
1512 ///
1513 /// # Example
1514 ///
1515 /// ```
1516 /// use jiff::{civil::date, Timestamp};
1517 ///
1518 /// let zdt = date(2024, 3, 1).at(7, 30, 0, 0).in_tz("America/New_York")?;
1519 /// assert_eq!(
1520 /// zdt.yesterday()?,
1521 /// date(2024, 2, 29).at(7, 30, 0, 0).in_tz("America/New_York")?,
1522 /// );
1523 ///
1524 /// // The min doesn't have a yesterday.
1525 /// assert!(Timestamp::MIN.in_tz("America/New_York")?.yesterday().is_err());
1526 ///
1527 /// # Ok::<(), Box<dyn std::error::Error>>(())
1528 /// ```
1529 ///
1530 /// # Example: ambiguous datetimes are automatically resolved
1531 ///
1532 /// ```
1533 /// use jiff::{civil::date, Timestamp};
1534 ///
1535 /// let zdt = date(2024, 11, 4).at(1, 30, 0, 0).in_tz("America/New_York")?;
1536 /// assert_eq!(
1537 /// zdt.yesterday()?.to_string(),
1538 /// // Consistent with the "compatible" disambiguation strategy, the
1539 /// // "first" 1 o'clock hour is selected. You can tell this because
1540 /// // the offset is -04, which corresponds to DST time in New York.
1541 /// // The second 1 o'clock hour would have offset -05.
1542 /// "2024-11-03T01:30:00-04:00[America/New_York]",
1543 /// );
1544 ///
1545 /// # Ok::<(), Box<dyn std::error::Error>>(())
1546 /// ```
1547 #[inline]
1548 pub fn yesterday(&self) -> Result<Zoned, Error> {
1549 self.datetime().yesterday()?.to_zoned(self.time_zone().clone())
1550 }
1551
1552 /// Returns the "nth" weekday from the beginning or end of the month in
1553 /// which this zoned datetime resides.
1554 ///
1555 /// The `nth` parameter can be positive or negative. A positive value
1556 /// computes the "nth" weekday from the beginning of the month. A negative
1557 /// value computes the "nth" weekday from the end of the month. So for
1558 /// example, use `-1` to "find the last weekday" in this date's month.
1559 ///
1560 /// In most cases, the time in the zoned datetime returned remains
1561 /// unchanged. In some cases, the time may change if the time
1562 /// on the previous date was unambiguous (always true, since a
1563 /// `Zoned` is a precise instant in time) and the same clock time
1564 /// on the returned zoned datetime is ambiguous. In this case, the
1565 /// [`Disambiguation::Compatible`]
1566 /// strategy will be used to turn it into a precise instant. If you want to
1567 /// use a different disambiguation strategy, then use [`Zoned::datetime`]
1568 /// to get the civil datetime, then use [`DateTime::nth_weekday_of_month`],
1569 /// then use [`TimeZone::to_ambiguous_zoned`] and apply your preferred
1570 /// disambiguation strategy.
1571 ///
1572 /// # Errors
1573 ///
1574 /// This returns an error when `nth` is `0`, or if it is `5` or `-5` and
1575 /// there is no 5th weekday from the beginning or end of the month. This
1576 /// could also return an error if the corresponding datetime could not be
1577 /// represented as an instant for this `Zoned`'s time zone. (This can only
1578 /// happen close the boundaries of an [`Timestamp`].)
1579 ///
1580 /// # Example
1581 ///
1582 /// This shows how to get the nth weekday in a month, starting from the
1583 /// beginning of the month:
1584 ///
1585 /// ```
1586 /// use jiff::civil::{Weekday, date};
1587 ///
1588 /// let zdt = date(2017, 3, 1).at(7, 30, 0, 0).in_tz("America/New_York")?;
1589 /// let second_friday = zdt.nth_weekday_of_month(2, Weekday::Friday)?;
1590 /// assert_eq!(
1591 /// second_friday,
1592 /// date(2017, 3, 10).at(7, 30, 0, 0).in_tz("America/New_York")?,
1593 /// );
1594 ///
1595 /// # Ok::<(), Box<dyn std::error::Error>>(())
1596 /// ```
1597 ///
1598 /// This shows how to do the reverse of the above. That is, the nth _last_
1599 /// weekday in a month:
1600 ///
1601 /// ```
1602 /// use jiff::civil::{Weekday, date};
1603 ///
1604 /// let zdt = date(2024, 3, 1).at(7, 30, 0, 0).in_tz("America/New_York")?;
1605 /// let last_thursday = zdt.nth_weekday_of_month(-1, Weekday::Thursday)?;
1606 /// assert_eq!(
1607 /// last_thursday,
1608 /// date(2024, 3, 28).at(7, 30, 0, 0).in_tz("America/New_York")?,
1609 /// );
1610 ///
1611 /// let second_last_thursday = zdt.nth_weekday_of_month(
1612 /// -2,
1613 /// Weekday::Thursday,
1614 /// )?;
1615 /// assert_eq!(
1616 /// second_last_thursday,
1617 /// date(2024, 3, 21).at(7, 30, 0, 0).in_tz("America/New_York")?,
1618 /// );
1619 ///
1620 /// # Ok::<(), Box<dyn std::error::Error>>(())
1621 /// ```
1622 ///
1623 /// This routine can return an error if there isn't an `nth` weekday
1624 /// for this month. For example, March 2024 only has 4 Mondays:
1625 ///
1626 /// ```
1627 /// use jiff::civil::{Weekday, date};
1628 ///
1629 /// let zdt = date(2024, 3, 25).at(7, 30, 0, 0).in_tz("America/New_York")?;
1630 /// let fourth_monday = zdt.nth_weekday_of_month(4, Weekday::Monday)?;
1631 /// assert_eq!(
1632 /// fourth_monday,
1633 /// date(2024, 3, 25).at(7, 30, 0, 0).in_tz("America/New_York")?,
1634 /// );
1635 /// // There is no 5th Monday.
1636 /// assert!(zdt.nth_weekday_of_month(5, Weekday::Monday).is_err());
1637 /// // Same goes for counting backwards.
1638 /// assert!(zdt.nth_weekday_of_month(-5, Weekday::Monday).is_err());
1639 ///
1640 /// # Ok::<(), Box<dyn std::error::Error>>(())
1641 /// ```
1642 #[inline]
1643 pub fn nth_weekday_of_month(
1644 &self,
1645 nth: i8,
1646 weekday: Weekday,
1647 ) -> Result<Zoned, Error> {
1648 self.datetime()
1649 .nth_weekday_of_month(nth, weekday)?
1650 .to_zoned(self.time_zone().clone())
1651 }
1652
1653 /// Returns the "nth" weekday from this zoned datetime, not including
1654 /// itself.
1655 ///
1656 /// The `nth` parameter can be positive or negative. A positive value
1657 /// computes the "nth" weekday starting at the day after this date and
1658 /// going forwards in time. A negative value computes the "nth" weekday
1659 /// starting at the day before this date and going backwards in time.
1660 ///
1661 /// For example, if this zoned datetime's weekday is a Sunday and the first
1662 /// Sunday is asked for (that is, `zdt.nth_weekday(1, Weekday::Sunday)`),
1663 /// then the result is a week from this zoned datetime corresponding to the
1664 /// following Sunday.
1665 ///
1666 /// In most cases, the time in the zoned datetime returned remains
1667 /// unchanged. In some cases, the time may change if the time
1668 /// on the previous date was unambiguous (always true, since a
1669 /// `Zoned` is a precise instant in time) and the same clock time
1670 /// on the returned zoned datetime is ambiguous. In this case, the
1671 /// [`Disambiguation::Compatible`]
1672 /// strategy will be used to turn it into a precise instant. If you want to
1673 /// use a different disambiguation strategy, then use [`Zoned::datetime`]
1674 /// to get the civil datetime, then use [`DateTime::nth_weekday`],
1675 /// then use [`TimeZone::to_ambiguous_zoned`] and apply your preferred
1676 /// disambiguation strategy.
1677 ///
1678 /// # Errors
1679 ///
1680 /// This returns an error when `nth` is `0`, or if it would otherwise
1681 /// result in a date that overflows the minimum/maximum values of
1682 /// `Zoned`.
1683 ///
1684 /// # Example
1685 ///
1686 /// This example shows how to find the "nth" weekday going forwards in
1687 /// time:
1688 ///
1689 /// ```
1690 /// use jiff::civil::{Weekday, date};
1691 ///
1692 /// // Use a Sunday in March as our start date.
1693 /// let zdt = date(2024, 3, 10).at(7, 30, 0, 0).in_tz("America/New_York")?;
1694 /// assert_eq!(zdt.weekday(), Weekday::Sunday);
1695 ///
1696 /// // The first next Monday is tomorrow!
1697 /// let next_monday = zdt.nth_weekday(1, Weekday::Monday)?;
1698 /// assert_eq!(
1699 /// next_monday,
1700 /// date(2024, 3, 11).at(7, 30, 0, 0).in_tz("America/New_York")?,
1701 /// );
1702 ///
1703 /// // But the next Sunday is a week away, because this doesn't
1704 /// // include the current weekday.
1705 /// let next_sunday = zdt.nth_weekday(1, Weekday::Sunday)?;
1706 /// assert_eq!(
1707 /// next_sunday,
1708 /// date(2024, 3, 17).at(7, 30, 0, 0).in_tz("America/New_York")?,
1709 /// );
1710 ///
1711 /// // "not this Thursday, but next Thursday"
1712 /// let next_next_thursday = zdt.nth_weekday(2, Weekday::Thursday)?;
1713 /// assert_eq!(
1714 /// next_next_thursday,
1715 /// date(2024, 3, 21).at(7, 30, 0, 0).in_tz("America/New_York")?,
1716 /// );
1717 ///
1718 /// # Ok::<(), Box<dyn std::error::Error>>(())
1719 /// ```
1720 ///
1721 /// This example shows how to find the "nth" weekday going backwards in
1722 /// time:
1723 ///
1724 /// ```
1725 /// use jiff::civil::{Weekday, date};
1726 ///
1727 /// // Use a Sunday in March as our start date.
1728 /// let zdt = date(2024, 3, 10).at(7, 30, 0, 0).in_tz("America/New_York")?;
1729 /// assert_eq!(zdt.weekday(), Weekday::Sunday);
1730 ///
1731 /// // "last Saturday" was yesterday!
1732 /// let last_saturday = zdt.nth_weekday(-1, Weekday::Saturday)?;
1733 /// assert_eq!(
1734 /// last_saturday,
1735 /// date(2024, 3, 9).at(7, 30, 0, 0).in_tz("America/New_York")?,
1736 /// );
1737 ///
1738 /// // "last Sunday" was a week ago.
1739 /// let last_sunday = zdt.nth_weekday(-1, Weekday::Sunday)?;
1740 /// assert_eq!(
1741 /// last_sunday,
1742 /// date(2024, 3, 3).at(7, 30, 0, 0).in_tz("America/New_York")?,
1743 /// );
1744 ///
1745 /// // "not last Thursday, but the one before"
1746 /// let prev_prev_thursday = zdt.nth_weekday(-2, Weekday::Thursday)?;
1747 /// assert_eq!(
1748 /// prev_prev_thursday,
1749 /// date(2024, 2, 29).at(7, 30, 0, 0).in_tz("America/New_York")?,
1750 /// );
1751 ///
1752 /// # Ok::<(), Box<dyn std::error::Error>>(())
1753 /// ```
1754 ///
1755 /// This example shows that overflow results in an error in either
1756 /// direction:
1757 ///
1758 /// ```
1759 /// use jiff::{civil::Weekday, Timestamp};
1760 ///
1761 /// let zdt = Timestamp::MAX.in_tz("America/New_York")?;
1762 /// assert_eq!(zdt.weekday(), Weekday::Thursday);
1763 /// assert!(zdt.nth_weekday(1, Weekday::Saturday).is_err());
1764 ///
1765 /// let zdt = Timestamp::MIN.in_tz("America/New_York")?;
1766 /// assert_eq!(zdt.weekday(), Weekday::Monday);
1767 /// assert!(zdt.nth_weekday(-1, Weekday::Sunday).is_err());
1768 ///
1769 /// # Ok::<(), Box<dyn std::error::Error>>(())
1770 /// ```
1771 ///
1772 /// # Example: getting the start of the week
1773 ///
1774 /// Given a date, one can use `nth_weekday` to determine the start of the
1775 /// week in which the date resides in. This might vary based on whether
1776 /// the weeks start on Sunday or Monday. This example shows how to handle
1777 /// both.
1778 ///
1779 /// ```
1780 /// use jiff::civil::{Weekday, date};
1781 ///
1782 /// let zdt = date(2024, 3, 15).at(7, 30, 0, 0).in_tz("America/New_York")?;
1783 /// // For weeks starting with Sunday.
1784 /// let start_of_week = zdt.tomorrow()?.nth_weekday(-1, Weekday::Sunday)?;
1785 /// assert_eq!(
1786 /// start_of_week,
1787 /// date(2024, 3, 10).at(7, 30, 0, 0).in_tz("America/New_York")?,
1788 /// );
1789 /// // For weeks starting with Monday.
1790 /// let start_of_week = zdt.tomorrow()?.nth_weekday(-1, Weekday::Monday)?;
1791 /// assert_eq!(
1792 /// start_of_week,
1793 /// date(2024, 3, 11).at(7, 30, 0, 0).in_tz("America/New_York")?,
1794 /// );
1795 ///
1796 /// # Ok::<(), Box<dyn std::error::Error>>(())
1797 /// ```
1798 ///
1799 /// In the above example, we first get the date after the current one
1800 /// because `nth_weekday` does not consider itself when counting. This
1801 /// works as expected even at the boundaries of a week:
1802 ///
1803 /// ```
1804 /// use jiff::civil::{Time, Weekday, date};
1805 ///
1806 /// // The start of the week.
1807 /// let zdt = date(2024, 3, 10).at(0, 0, 0, 0).in_tz("America/New_York")?;
1808 /// let start_of_week = zdt.tomorrow()?.nth_weekday(-1, Weekday::Sunday)?;
1809 /// assert_eq!(
1810 /// start_of_week,
1811 /// date(2024, 3, 10).at(0, 0, 0, 0).in_tz("America/New_York")?,
1812 /// );
1813 /// // The end of the week.
1814 /// let zdt = date(2024, 3, 16)
1815 /// .at(23, 59, 59, 999_999_999)
1816 /// .in_tz("America/New_York")?;
1817 /// let start_of_week = zdt
1818 /// .tomorrow()?
1819 /// .nth_weekday(-1, Weekday::Sunday)?
1820 /// .with().time(Time::midnight()).build()?;
1821 /// assert_eq!(
1822 /// start_of_week,
1823 /// date(2024, 3, 10).at(0, 0, 0, 0).in_tz("America/New_York")?,
1824 /// );
1825 ///
1826 /// # Ok::<(), Box<dyn std::error::Error>>(())
1827 /// ```
1828 #[inline]
1829 pub fn nth_weekday(
1830 &self,
1831 nth: i32,
1832 weekday: Weekday,
1833 ) -> Result<Zoned, Error> {
1834 self.datetime()
1835 .nth_weekday(nth, weekday)?
1836 .to_zoned(self.time_zone().clone())
1837 }
1838
1839 /// Returns the precise instant in time referred to by this zoned datetime.
1840 ///
1841 /// # Example
1842 ///
1843 /// ```
1844 /// use jiff::civil::date;
1845 ///
1846 /// let zdt = date(2024, 3, 14).at(18, 45, 0, 0).in_tz("America/New_York")?;
1847 /// assert_eq!(zdt.timestamp().as_second(), 1_710_456_300);
1848 ///
1849 /// # Ok::<(), Box<dyn std::error::Error>>(())
1850 /// ```
1851 #[inline]
1852 pub fn timestamp(&self) -> Timestamp {
1853 self.inner.timestamp
1854 }
1855
1856 /// Returns the civil datetime component of this zoned datetime.
1857 ///
1858 /// # Example
1859 ///
1860 /// ```
1861 /// use jiff::civil::date;
1862 ///
1863 /// let zdt = date(2024, 3, 14).at(18, 45, 0, 0).in_tz("America/New_York")?;
1864 /// assert_eq!(zdt.datetime(), date(2024, 3, 14).at(18, 45, 0, 0));
1865 ///
1866 /// # Ok::<(), Box<dyn std::error::Error>>(())
1867 /// ```
1868 #[inline]
1869 pub fn datetime(&self) -> DateTime {
1870 self.inner.datetime
1871 }
1872
1873 /// Returns the civil date component of this zoned datetime.
1874 ///
1875 /// # Example
1876 ///
1877 /// ```
1878 /// use jiff::civil::date;
1879 ///
1880 /// let zdt = date(2024, 3, 14).at(18, 45, 0, 0).in_tz("America/New_York")?;
1881 /// assert_eq!(zdt.date(), date(2024, 3, 14));
1882 ///
1883 /// # Ok::<(), Box<dyn std::error::Error>>(())
1884 /// ```
1885 #[inline]
1886 pub fn date(&self) -> Date {
1887 self.datetime().date()
1888 }
1889
1890 /// Returns the civil time component of this zoned datetime.
1891 ///
1892 /// # Example
1893 ///
1894 /// ```
1895 /// use jiff::civil::{date, time};
1896 ///
1897 /// let zdt = date(2024, 3, 14).at(18, 45, 0, 0).in_tz("America/New_York")?;
1898 /// assert_eq!(zdt.time(), time(18, 45, 0, 0));
1899 ///
1900 /// # Ok::<(), Box<dyn std::error::Error>>(())
1901 /// ```
1902 #[inline]
1903 pub fn time(&self) -> Time {
1904 self.datetime().time()
1905 }
1906
1907 /// Construct a civil [ISO 8601 week date] from this zoned datetime.
1908 ///
1909 /// The [`ISOWeekDate`] type describes itself in more detail, but in
1910 /// brief, the ISO week date calendar system eschews months in favor of
1911 /// weeks.
1912 ///
1913 /// This routine is equivalent to
1914 /// [`ISOWeekDate::from_date(zdt.date())`](ISOWeekDate::from_date).
1915 ///
1916 /// [ISO 8601 week date]: https://en.wikipedia.org/wiki/ISO_week_date
1917 ///
1918 /// # Example
1919 ///
1920 /// This shows a number of examples demonstrating the conversion from a
1921 /// Gregorian date to an ISO 8601 week date:
1922 ///
1923 /// ```
1924 /// use jiff::civil::{Date, Time, Weekday, date};
1925 ///
1926 /// let zdt = date(1995, 1, 1).at(18, 45, 0, 0).in_tz("US/Eastern")?;
1927 /// let weekdate = zdt.iso_week_date();
1928 /// assert_eq!(weekdate.year(), 1994);
1929 /// assert_eq!(weekdate.week(), 52);
1930 /// assert_eq!(weekdate.weekday(), Weekday::Sunday);
1931 ///
1932 /// let zdt = date(1996, 12, 31).at(18, 45, 0, 0).in_tz("US/Eastern")?;
1933 /// let weekdate = zdt.iso_week_date();
1934 /// assert_eq!(weekdate.year(), 1997);
1935 /// assert_eq!(weekdate.week(), 1);
1936 /// assert_eq!(weekdate.weekday(), Weekday::Tuesday);
1937 ///
1938 /// let zdt = date(2019, 12, 30).at(18, 45, 0, 0).in_tz("US/Eastern")?;
1939 /// let weekdate = zdt.iso_week_date();
1940 /// assert_eq!(weekdate.year(), 2020);
1941 /// assert_eq!(weekdate.week(), 1);
1942 /// assert_eq!(weekdate.weekday(), Weekday::Monday);
1943 ///
1944 /// let zdt = date(2024, 3, 9).at(18, 45, 0, 0).in_tz("US/Eastern")?;
1945 /// let weekdate = zdt.iso_week_date();
1946 /// assert_eq!(weekdate.year(), 2024);
1947 /// assert_eq!(weekdate.week(), 10);
1948 /// assert_eq!(weekdate.weekday(), Weekday::Saturday);
1949 ///
1950 /// # Ok::<(), Box<dyn std::error::Error>>(())
1951 /// ```
1952 #[inline]
1953 pub fn iso_week_date(self) -> ISOWeekDate {
1954 self.date().iso_week_date()
1955 }
1956
1957 /// Returns the time zone offset of this zoned datetime.
1958 ///
1959 /// # Example
1960 ///
1961 /// ```
1962 /// use jiff::civil::date;
1963 ///
1964 /// let zdt = date(2024, 2, 14).at(18, 45, 0, 0).in_tz("America/New_York")?;
1965 /// // -05 because New York is in "standard" time at this point.
1966 /// assert_eq!(zdt.offset(), jiff::tz::offset(-5));
1967 ///
1968 /// let zdt = date(2024, 7, 14).at(18, 45, 0, 0).in_tz("America/New_York")?;
1969 /// // But we get -04 once "summer" or "daylight saving time" starts.
1970 /// assert_eq!(zdt.offset(), jiff::tz::offset(-4));
1971 ///
1972 /// # Ok::<(), Box<dyn std::error::Error>>(())
1973 /// ```
1974 #[inline]
1975 pub fn offset(&self) -> Offset {
1976 self.inner.offset
1977 }
1978
1979 /// Add the given span of time to this zoned datetime. If the sum would
1980 /// overflow the minimum or maximum zoned datetime values, then an error is
1981 /// returned.
1982 ///
1983 /// This operation accepts three different duration types: [`Span`],
1984 /// [`SignedDuration`] or [`std::time::Duration`]. This is achieved via
1985 /// `From` trait implementations for the [`ZonedArithmetic`] type.
1986 ///
1987 /// # Properties
1988 ///
1989 /// This routine is _not_ reversible because some additions may
1990 /// be ambiguous. For example, adding `1 month` to the zoned
1991 /// datetime `2024-03-31T00:00:00[America/New_York]` will produce
1992 /// `2024-04-30T00:00:00[America/New_York]` since April has
1993 /// only 30 days in a month. Moreover, subtracting `1 month`
1994 /// from `2024-04-30T00:00:00[America/New_York]` will produce
1995 /// `2024-03-30T00:00:00[America/New_York]`, which is not the date we
1996 /// started with.
1997 ///
1998 /// A similar argument applies for days, since with zoned datetimes,
1999 /// different days can be different lengths.
2000 ///
2001 /// If spans of time are limited to units of hours (or less), then this
2002 /// routine _is_ reversible. This also implies that all operations with a
2003 /// [`SignedDuration`] or a [`std::time::Duration`] are reversible.
2004 ///
2005 /// # Errors
2006 ///
2007 /// If the span added to this zoned datetime would result in a zoned
2008 /// datetime that exceeds the range of a `Zoned`, then this will return an
2009 /// error.
2010 ///
2011 /// # Example
2012 ///
2013 /// This shows a few examples of adding spans of time to various zoned
2014 /// datetimes. We make use of the [`ToSpan`](crate::ToSpan) trait for
2015 /// convenient creation of spans.
2016 ///
2017 /// ```
2018 /// use jiff::{civil::date, ToSpan};
2019 ///
2020 /// let zdt = date(1995, 12, 7)
2021 /// .at(3, 24, 30, 3_500)
2022 /// .in_tz("America/New_York")?;
2023 /// let got = zdt.checked_add(20.years().months(4).nanoseconds(500))?;
2024 /// assert_eq!(
2025 /// got,
2026 /// date(2016, 4, 7).at(3, 24, 30, 4_000).in_tz("America/New_York")?,
2027 /// );
2028 ///
2029 /// let zdt = date(2019, 1, 31).at(15, 30, 0, 0).in_tz("America/New_York")?;
2030 /// let got = zdt.checked_add(1.months())?;
2031 /// assert_eq!(
2032 /// got,
2033 /// date(2019, 2, 28).at(15, 30, 0, 0).in_tz("America/New_York")?,
2034 /// );
2035 ///
2036 /// # Ok::<(), Box<dyn std::error::Error>>(())
2037 /// ```
2038 ///
2039 /// # Example: available via addition operator
2040 ///
2041 /// This routine can be used via the `+` operator. Note though that if it
2042 /// fails, it will result in a panic. Note that we use `&zdt + ...` instead
2043 /// of `zdt + ...` since `Add` is implemented for `&Zoned` and not `Zoned`.
2044 /// This is because `Zoned` is not `Copy`.
2045 ///
2046 /// ```
2047 /// use jiff::{civil::date, ToSpan};
2048 ///
2049 /// let zdt = date(1995, 12, 7)
2050 /// .at(3, 24, 30, 3_500)
2051 /// .in_tz("America/New_York")?;
2052 /// let got = &zdt + 20.years().months(4).nanoseconds(500);
2053 /// assert_eq!(
2054 /// got,
2055 /// date(2016, 4, 7).at(3, 24, 30, 4_000).in_tz("America/New_York")?,
2056 /// );
2057 ///
2058 /// # Ok::<(), Box<dyn std::error::Error>>(())
2059 /// ```
2060 ///
2061 /// # Example: zone aware arithmetic
2062 ///
2063 /// This example demonstrates the difference between "add 1 day" and
2064 /// "add 24 hours." In the former case, 1 day might not correspond to 24
2065 /// hours if there is a time zone transition in the intervening period.
2066 /// However, adding 24 hours always means adding exactly 24 hours.
2067 ///
2068 /// ```
2069 /// use jiff::{civil::date, ToSpan};
2070 ///
2071 /// let zdt = date(2024, 3, 10).at(0, 0, 0, 0).in_tz("America/New_York")?;
2072 ///
2073 /// let one_day_later = zdt.checked_add(1.day())?;
2074 /// assert_eq!(
2075 /// one_day_later.to_string(),
2076 /// "2024-03-11T00:00:00-04:00[America/New_York]",
2077 /// );
2078 ///
2079 /// let twenty_four_hours_later = zdt.checked_add(24.hours())?;
2080 /// assert_eq!(
2081 /// twenty_four_hours_later.to_string(),
2082 /// "2024-03-11T01:00:00-04:00[America/New_York]",
2083 /// );
2084 ///
2085 /// # Ok::<(), Box<dyn std::error::Error>>(())
2086 /// ```
2087 ///
2088 /// # Example: automatic disambiguation
2089 ///
2090 /// This example demonstrates what happens when adding a span
2091 /// of time results in an ambiguous zoned datetime. Zone aware
2092 /// arithmetic uses automatic disambiguation corresponding to the
2093 /// [`Disambiguation::Compatible`]
2094 /// strategy for resolving an ambiguous datetime to a precise instant.
2095 /// For example, in the case below, there is a gap in the clocks for 1
2096 /// hour starting at `2024-03-10 02:00:00` in `America/New_York`. The
2097 /// "compatible" strategy chooses the later time in a gap:.
2098 ///
2099 /// ```
2100 /// use jiff::{civil::date, ToSpan};
2101 ///
2102 /// let zdt = date(2024, 3, 9).at(2, 30, 0, 0).in_tz("America/New_York")?;
2103 /// let one_day_later = zdt.checked_add(1.day())?;
2104 /// assert_eq!(
2105 /// one_day_later.to_string(),
2106 /// "2024-03-10T03:30:00-04:00[America/New_York]",
2107 /// );
2108 ///
2109 /// # Ok::<(), Box<dyn std::error::Error>>(())
2110 /// ```
2111 ///
2112 /// And this example demonstrates the "compatible" strategy when arithmetic
2113 /// results in an ambiguous datetime in a fold. In this case, we make use
2114 /// of the fact that the 1 o'clock hour was repeated on `2024-11-03`.
2115 ///
2116 /// ```
2117 /// use jiff::{civil::date, ToSpan};
2118 ///
2119 /// let zdt = date(2024, 11, 2).at(1, 30, 0, 0).in_tz("America/New_York")?;
2120 /// let one_day_later = zdt.checked_add(1.day())?;
2121 /// assert_eq!(
2122 /// one_day_later.to_string(),
2123 /// // This corresponds to the first iteration of the 1 o'clock hour,
2124 /// // i.e., when DST is still in effect. It's the earlier time.
2125 /// "2024-11-03T01:30:00-04:00[America/New_York]",
2126 /// );
2127 ///
2128 /// # Ok::<(), Box<dyn std::error::Error>>(())
2129 /// ```
2130 ///
2131 /// # Example: negative spans are supported
2132 ///
2133 /// ```
2134 /// use jiff::{civil::date, ToSpan};
2135 ///
2136 /// let zdt = date(2024, 3, 31)
2137 /// .at(19, 5, 59, 999_999_999)
2138 /// .in_tz("America/New_York")?;
2139 /// assert_eq!(
2140 /// zdt.checked_add(-1.months())?,
2141 /// date(2024, 2, 29).
2142 /// at(19, 5, 59, 999_999_999)
2143 /// .in_tz("America/New_York")?,
2144 /// );
2145 ///
2146 /// # Ok::<(), Box<dyn std::error::Error>>(())
2147 /// ```
2148 ///
2149 /// # Example: error on overflow
2150 ///
2151 /// ```
2152 /// use jiff::{civil::date, ToSpan};
2153 ///
2154 /// let zdt = date(2024, 3, 31).at(13, 13, 13, 13).in_tz("America/New_York")?;
2155 /// assert!(zdt.checked_add(9000.years()).is_err());
2156 /// assert!(zdt.checked_add(-19000.years()).is_err());
2157 ///
2158 /// # Ok::<(), Box<dyn std::error::Error>>(())
2159 /// ```
2160 ///
2161 /// # Example: adding absolute durations
2162 ///
2163 /// This shows how to add signed and unsigned absolute durations to a
2164 /// `Zoned`.
2165 ///
2166 /// ```
2167 /// use std::time::Duration;
2168 ///
2169 /// use jiff::{civil::date, SignedDuration};
2170 ///
2171 /// let zdt = date(2024, 2, 29).at(0, 0, 0, 0).in_tz("US/Eastern")?;
2172 ///
2173 /// let dur = SignedDuration::from_hours(25);
2174 /// assert_eq!(
2175 /// zdt.checked_add(dur)?,
2176 /// date(2024, 3, 1).at(1, 0, 0, 0).in_tz("US/Eastern")?,
2177 /// );
2178 /// assert_eq!(
2179 /// zdt.checked_add(-dur)?,
2180 /// date(2024, 2, 27).at(23, 0, 0, 0).in_tz("US/Eastern")?,
2181 /// );
2182 ///
2183 /// let dur = Duration::from_secs(25 * 60 * 60);
2184 /// assert_eq!(
2185 /// zdt.checked_add(dur)?,
2186 /// date(2024, 3, 1).at(1, 0, 0, 0).in_tz("US/Eastern")?,
2187 /// );
2188 /// // One cannot negate an unsigned duration,
2189 /// // but you can subtract it!
2190 /// assert_eq!(
2191 /// zdt.checked_sub(dur)?,
2192 /// date(2024, 2, 27).at(23, 0, 0, 0).in_tz("US/Eastern")?,
2193 /// );
2194 ///
2195 /// # Ok::<(), Box<dyn std::error::Error>>(())
2196 /// ```
2197 #[inline]
2198 pub fn checked_add<A: Into<ZonedArithmetic>>(
2199 &self,
2200 duration: A,
2201 ) -> Result<Zoned, Error> {
2202 let duration: ZonedArithmetic = duration.into();
2203 duration.checked_add(self)
2204 }
2205
2206 #[inline]
2207 fn checked_add_span(&self, span: Span) -> Result<Zoned, Error> {
2208 let span_calendar = span.only_calendar();
2209 // If our duration only consists of "time" (hours, minutes, etc), then
2210 // we can short-circuit and do timestamp math. This also avoids dealing
2211 // with ambiguity and time zone bullshit.
2212 if span_calendar.is_zero() {
2213 return self
2214 .timestamp()
2215 .checked_add(span)
2216 .map(|ts| ts.to_zoned(self.time_zone().clone()))
2217 .with_context(|| {
2218 err!(
2219 "failed to add span {span} to timestamp {timestamp} \
2220 from zoned datetime {zoned}",
2221 timestamp = self.timestamp(),
2222 zoned = self,
2223 )
2224 });
2225 }
2226 let span_time = span.only_time();
2227 let dt =
2228 self.datetime().checked_add(span_calendar).with_context(|| {
2229 err!(
2230 "failed to add span {span_calendar} to datetime {dt} \
2231 from zoned datetime {zoned}",
2232 dt = self.datetime(),
2233 zoned = self,
2234 )
2235 })?;
2236
2237 let tz = self.time_zone();
2238 let mut ts =
2239 tz.to_ambiguous_timestamp(dt).compatible().with_context(|| {
2240 err!(
2241 "failed to convert civil datetime {dt} to timestamp \
2242 with time zone {tz}",
2243 tz = self.time_zone().diagnostic_name(),
2244 )
2245 })?;
2246 ts = ts.checked_add(span_time).with_context(|| {
2247 err!(
2248 "failed to add span {span_time} to timestamp {ts} \
2249 (which was created from {dt})"
2250 )
2251 })?;
2252 Ok(ts.to_zoned(tz.clone()))
2253 }
2254
2255 #[inline]
2256 fn checked_add_duration(
2257 &self,
2258 duration: SignedDuration,
2259 ) -> Result<Zoned, Error> {
2260 self.timestamp()
2261 .checked_add(duration)
2262 .map(|ts| ts.to_zoned(self.time_zone().clone()))
2263 }
2264
2265 /// This routine is identical to [`Zoned::checked_add`] with the
2266 /// duration negated.
2267 ///
2268 /// # Errors
2269 ///
2270 /// This has the same error conditions as [`Zoned::checked_add`].
2271 ///
2272 /// # Example
2273 ///
2274 /// This routine can be used via the `-` operator. Note though that if it
2275 /// fails, it will result in a panic. Note that we use `&zdt - ...` instead
2276 /// of `zdt - ...` since `Sub` is implemented for `&Zoned` and not `Zoned`.
2277 /// This is because `Zoned` is not `Copy`.
2278 ///
2279 /// ```
2280 /// use std::time::Duration;
2281 ///
2282 /// use jiff::{civil::date, SignedDuration, ToSpan};
2283 ///
2284 /// let zdt = date(1995, 12, 7)
2285 /// .at(3, 24, 30, 3_500)
2286 /// .in_tz("America/New_York")?;
2287 /// let got = &zdt - 20.years().months(4).nanoseconds(500);
2288 /// assert_eq!(
2289 /// got,
2290 /// date(1975, 8, 7).at(3, 24, 30, 3_000).in_tz("America/New_York")?,
2291 /// );
2292 ///
2293 /// let dur = SignedDuration::new(24 * 60 * 60, 500);
2294 /// assert_eq!(
2295 /// &zdt - dur,
2296 /// date(1995, 12, 6).at(3, 24, 30, 3_000).in_tz("America/New_York")?,
2297 /// );
2298 ///
2299 /// let dur = Duration::new(24 * 60 * 60, 500);
2300 /// assert_eq!(
2301 /// &zdt - dur,
2302 /// date(1995, 12, 6).at(3, 24, 30, 3_000).in_tz("America/New_York")?,
2303 /// );
2304 ///
2305 /// # Ok::<(), Box<dyn std::error::Error>>(())
2306 /// ```
2307 #[inline]
2308 pub fn checked_sub<A: Into<ZonedArithmetic>>(
2309 &self,
2310 duration: A,
2311 ) -> Result<Zoned, Error> {
2312 let duration: ZonedArithmetic = duration.into();
2313 duration.checked_neg().and_then(|za| za.checked_add(self))
2314 }
2315
2316 /// This routine is identical to [`Zoned::checked_add`], except the
2317 /// result saturates on overflow. That is, instead of overflow, either
2318 /// [`Timestamp::MIN`] or [`Timestamp::MAX`] (in this `Zoned` value's time
2319 /// zone) is returned.
2320 ///
2321 /// # Properties
2322 ///
2323 /// The properties of this routine are identical to [`Zoned::checked_add`],
2324 /// except that if saturation occurs, then the result is not reversible.
2325 ///
2326 /// # Example
2327 ///
2328 /// ```
2329 /// use jiff::{civil::date, SignedDuration, Timestamp, ToSpan};
2330 ///
2331 /// let zdt = date(2024, 3, 31).at(13, 13, 13, 13).in_tz("America/New_York")?;
2332 /// assert_eq!(Timestamp::MAX, zdt.saturating_add(9000.years()).timestamp());
2333 /// assert_eq!(Timestamp::MIN, zdt.saturating_add(-19000.years()).timestamp());
2334 /// assert_eq!(Timestamp::MAX, zdt.saturating_add(SignedDuration::MAX).timestamp());
2335 /// assert_eq!(Timestamp::MIN, zdt.saturating_add(SignedDuration::MIN).timestamp());
2336 /// assert_eq!(Timestamp::MAX, zdt.saturating_add(std::time::Duration::MAX).timestamp());
2337 ///
2338 /// # Ok::<(), Box<dyn std::error::Error>>(())
2339 /// ```
2340 #[inline]
2341 pub fn saturating_add<A: Into<ZonedArithmetic>>(
2342 &self,
2343 duration: A,
2344 ) -> Zoned {
2345 let duration: ZonedArithmetic = duration.into();
2346 self.checked_add(duration).unwrap_or_else(|_| {
2347 let ts = if duration.is_negative() {
2348 Timestamp::MIN
2349 } else {
2350 Timestamp::MAX
2351 };
2352 ts.to_zoned(self.time_zone().clone())
2353 })
2354 }
2355
2356 /// This routine is identical to [`Zoned::saturating_add`] with the span
2357 /// parameter negated.
2358 ///
2359 /// # Example
2360 ///
2361 /// ```
2362 /// use jiff::{civil::date, SignedDuration, Timestamp, ToSpan};
2363 ///
2364 /// let zdt = date(2024, 3, 31).at(13, 13, 13, 13).in_tz("America/New_York")?;
2365 /// assert_eq!(Timestamp::MIN, zdt.saturating_sub(19000.years()).timestamp());
2366 /// assert_eq!(Timestamp::MAX, zdt.saturating_sub(-9000.years()).timestamp());
2367 /// assert_eq!(Timestamp::MIN, zdt.saturating_sub(SignedDuration::MAX).timestamp());
2368 /// assert_eq!(Timestamp::MAX, zdt.saturating_sub(SignedDuration::MIN).timestamp());
2369 /// assert_eq!(Timestamp::MIN, zdt.saturating_sub(std::time::Duration::MAX).timestamp());
2370 ///
2371 /// # Ok::<(), Box<dyn std::error::Error>>(())
2372 /// ```
2373 #[inline]
2374 pub fn saturating_sub<A: Into<ZonedArithmetic>>(
2375 &self,
2376 duration: A,
2377 ) -> Zoned {
2378 let duration: ZonedArithmetic = duration.into();
2379 let Ok(duration) = duration.checked_neg() else {
2380 return Timestamp::MIN.to_zoned(self.time_zone().clone());
2381 };
2382 self.saturating_add(duration)
2383 }
2384
2385 /// Returns a span representing the elapsed time from this zoned datetime
2386 /// until the given `other` zoned datetime.
2387 ///
2388 /// When `other` occurs before this datetime, then the span returned will
2389 /// be negative.
2390 ///
2391 /// Depending on the input provided, the span returned is rounded. It may
2392 /// also be balanced up to bigger units than the default. By default, the
2393 /// span returned is balanced such that the biggest possible unit is hours.
2394 /// This default is an API guarantee. Users can rely on the default not
2395 /// returning any calendar units in the default configuration.
2396 ///
2397 /// This operation is configured by providing a [`ZonedDifference`]
2398 /// value. Since this routine accepts anything that implements
2399 /// `Into<ZonedDifference>`, once can pass a `&Zoned` directly.
2400 /// One can also pass a `(Unit, &Zoned)`, where `Unit` is treated as
2401 /// [`ZonedDifference::largest`].
2402 ///
2403 /// # Properties
2404 ///
2405 /// It is guaranteed that if the returned span is subtracted from `other`,
2406 /// and if no rounding is requested, and if the largest unit requested
2407 /// is at most `Unit::Hour`, then the original zoned datetime will be
2408 /// returned.
2409 ///
2410 /// This routine is equivalent to `self.since(other).map(|span| -span)`
2411 /// if no rounding options are set. If rounding options are set, then
2412 /// it's equivalent to
2413 /// `self.since(other_without_rounding_options).map(|span| -span)`,
2414 /// followed by a call to [`Span::round`] with the appropriate rounding
2415 /// options set. This is because the negation of a span can result in
2416 /// different rounding results depending on the rounding mode.
2417 ///
2418 /// # Errors
2419 ///
2420 /// An error can occur in some cases when the requested configuration
2421 /// would result in a span that is beyond allowable limits. For example,
2422 /// the nanosecond component of a span cannot represent the span of
2423 /// time between the minimum and maximum zoned datetime supported by Jiff.
2424 /// Therefore, if one requests a span with its largest unit set to
2425 /// [`Unit::Nanosecond`], then it's possible for this routine to fail.
2426 ///
2427 /// An error can also occur if `ZonedDifference` is misconfigured. For
2428 /// example, if the smallest unit provided is bigger than the largest unit.
2429 ///
2430 /// An error can also occur if units greater than `Unit::Hour` are
2431 /// requested _and_ if the time zones in the provided zoned datetimes
2432 /// are distinct. (See [`TimeZone`]'s section on equality for details on
2433 /// how equality is determined.) This error occurs because the length of
2434 /// a day may vary depending on the time zone. To work around this
2435 /// restriction, convert one or both of the zoned datetimes into the same
2436 /// time zone.
2437 ///
2438 /// It is guaranteed that if one provides a datetime with the default
2439 /// [`ZonedDifference`] configuration, then this routine will never
2440 /// fail.
2441 ///
2442 /// # Example
2443 ///
2444 /// ```
2445 /// use jiff::{civil::date, ToSpan};
2446 ///
2447 /// let earlier = date(2006, 8, 24).at(22, 30, 0, 0).in_tz("America/New_York")?;
2448 /// let later = date(2019, 1, 31).at(21, 0, 0, 0).in_tz("America/New_York")?;
2449 /// assert_eq!(
2450 /// earlier.until(&later)?,
2451 /// 109_031.hours().minutes(30).fieldwise(),
2452 /// );
2453 ///
2454 /// // Flipping the dates is fine, but you'll get a negative span.
2455 /// assert_eq!(
2456 /// later.until(&earlier)?,
2457 /// -109_031.hours().minutes(30).fieldwise(),
2458 /// );
2459 ///
2460 /// # Ok::<(), Box<dyn std::error::Error>>(())
2461 /// ```
2462 ///
2463 /// # Example: using bigger units
2464 ///
2465 /// This example shows how to expand the span returned to bigger units.
2466 /// This makes use of a `From<(Unit, &Zoned)> for ZonedDifference`
2467 /// trait implementation.
2468 ///
2469 /// ```
2470 /// use jiff::{civil::date, Unit, ToSpan};
2471 ///
2472 /// let zdt1 = date(1995, 12, 07).at(3, 24, 30, 3500).in_tz("America/New_York")?;
2473 /// let zdt2 = date(2019, 01, 31).at(15, 30, 0, 0).in_tz("America/New_York")?;
2474 ///
2475 /// // The default limits durations to using "hours" as the biggest unit.
2476 /// let span = zdt1.until(&zdt2)?;
2477 /// assert_eq!(span.to_string(), "PT202956H5M29.9999965S");
2478 ///
2479 /// // But we can ask for units all the way up to years.
2480 /// let span = zdt1.until((Unit::Year, &zdt2))?;
2481 /// assert_eq!(format!("{span:#}"), "23y 1mo 24d 12h 5m 29s 999ms 996µs 500ns");
2482 /// # Ok::<(), Box<dyn std::error::Error>>(())
2483 /// ```
2484 ///
2485 /// # Example: rounding the result
2486 ///
2487 /// This shows how one might find the difference between two zoned
2488 /// datetimes and have the result rounded such that sub-seconds are
2489 /// removed.
2490 ///
2491 /// In this case, we need to hand-construct a [`ZonedDifference`]
2492 /// in order to gain full configurability.
2493 ///
2494 /// ```
2495 /// use jiff::{civil::date, Unit, ToSpan, ZonedDifference};
2496 ///
2497 /// let zdt1 = date(1995, 12, 07).at(3, 24, 30, 3500).in_tz("America/New_York")?;
2498 /// let zdt2 = date(2019, 01, 31).at(15, 30, 0, 0).in_tz("America/New_York")?;
2499 ///
2500 /// let span = zdt1.until(
2501 /// ZonedDifference::from(&zdt2).smallest(Unit::Second),
2502 /// )?;
2503 /// assert_eq!(format!("{span:#}"), "202956h 5m 29s");
2504 ///
2505 /// // We can combine smallest and largest units too!
2506 /// let span = zdt1.until(
2507 /// ZonedDifference::from(&zdt2)
2508 /// .smallest(Unit::Second)
2509 /// .largest(Unit::Year),
2510 /// )?;
2511 /// assert_eq!(span.to_string(), "P23Y1M24DT12H5M29S");
2512 ///
2513 /// # Ok::<(), Box<dyn std::error::Error>>(())
2514 /// ```
2515 ///
2516 /// # Example: units biggers than days inhibit reversibility
2517 ///
2518 /// If you ask for units bigger than hours, then adding the span returned
2519 /// to the `other` zoned datetime is not guaranteed to result in the
2520 /// original zoned datetime. For example:
2521 ///
2522 /// ```
2523 /// use jiff::{civil::date, Unit, ToSpan};
2524 ///
2525 /// let zdt1 = date(2024, 3, 2).at(0, 0, 0, 0).in_tz("America/New_York")?;
2526 /// let zdt2 = date(2024, 5, 1).at(0, 0, 0, 0).in_tz("America/New_York")?;
2527 ///
2528 /// let span = zdt1.until((Unit::Month, &zdt2))?;
2529 /// assert_eq!(span, 1.month().days(29).fieldwise());
2530 /// let maybe_original = zdt2.checked_sub(span)?;
2531 /// // Not the same as the original datetime!
2532 /// assert_eq!(
2533 /// maybe_original,
2534 /// date(2024, 3, 3).at(0, 0, 0, 0).in_tz("America/New_York")?,
2535 /// );
2536 ///
2537 /// // But in the default configuration, hours are always the biggest unit
2538 /// // and reversibility is guaranteed.
2539 /// let span = zdt1.until(&zdt2)?;
2540 /// assert_eq!(span.to_string(), "PT1439H");
2541 /// let is_original = zdt2.checked_sub(span)?;
2542 /// assert_eq!(is_original, zdt1);
2543 ///
2544 /// # Ok::<(), Box<dyn std::error::Error>>(())
2545 /// ```
2546 ///
2547 /// This occurs because spans are added as if by adding the biggest units
2548 /// first, and then the smaller units. Because months vary in length,
2549 /// their meaning can change depending on how the span is added. In this
2550 /// case, adding one month to `2024-03-02` corresponds to 31 days, but
2551 /// subtracting one month from `2024-05-01` corresponds to 30 days.
2552 #[inline]
2553 pub fn until<'a, A: Into<ZonedDifference<'a>>>(
2554 &self,
2555 other: A,
2556 ) -> Result<Span, Error> {
2557 let args: ZonedDifference = other.into();
2558 let span = args.until_with_largest_unit(self)?;
2559 if args.rounding_may_change_span() {
2560 span.round(args.round.relative(self))
2561 } else {
2562 Ok(span)
2563 }
2564 }
2565
2566 /// This routine is identical to [`Zoned::until`], but the order of the
2567 /// parameters is flipped.
2568 ///
2569 /// # Errors
2570 ///
2571 /// This has the same error conditions as [`Zoned::until`].
2572 ///
2573 /// # Example
2574 ///
2575 /// This routine can be used via the `-` operator. Since the default
2576 /// configuration is used and because a `Span` can represent the difference
2577 /// between any two possible zoned datetimes, it will never panic. Note
2578 /// that we use `&zdt1 - &zdt2` instead of `zdt1 - zdt2` since `Sub` is
2579 /// implemented for `&Zoned` and not `Zoned`. This is because `Zoned` is
2580 /// not `Copy`.
2581 ///
2582 /// ```
2583 /// use jiff::{civil::date, ToSpan};
2584 ///
2585 /// let earlier = date(2006, 8, 24).at(22, 30, 0, 0).in_tz("America/New_York")?;
2586 /// let later = date(2019, 1, 31).at(21, 0, 0, 0).in_tz("America/New_York")?;
2587 /// assert_eq!(&later - &earlier, 109_031.hours().minutes(30).fieldwise());
2588 ///
2589 /// # Ok::<(), Box<dyn std::error::Error>>(())
2590 /// ```
2591 #[inline]
2592 pub fn since<'a, A: Into<ZonedDifference<'a>>>(
2593 &self,
2594 other: A,
2595 ) -> Result<Span, Error> {
2596 let args: ZonedDifference = other.into();
2597 let span = -args.until_with_largest_unit(self)?;
2598 if args.rounding_may_change_span() {
2599 span.round(args.round.relative(self))
2600 } else {
2601 Ok(span)
2602 }
2603 }
2604
2605 /// Returns an absolute duration representing the elapsed time from this
2606 /// zoned datetime until the given `other` zoned datetime.
2607 ///
2608 /// When `other` occurs before this zoned datetime, then the duration
2609 /// returned will be negative.
2610 ///
2611 /// Unlike [`Zoned::until`], this always returns a duration
2612 /// corresponding to a 96-bit integer of nanoseconds between two
2613 /// zoned datetimes.
2614 ///
2615 /// # Fallibility
2616 ///
2617 /// This routine never panics or returns an error. Since there are no
2618 /// configuration options that can be incorrectly provided, no error is
2619 /// possible when calling this routine. In contrast, [`Zoned::until`]
2620 /// can return an error in some cases due to misconfiguration. But like
2621 /// this routine, [`Zoned::until`] never panics or returns an error in
2622 /// its default configuration.
2623 ///
2624 /// # When should I use this versus [`Zoned::until`]?
2625 ///
2626 /// See the type documentation for [`SignedDuration`] for the section on
2627 /// when one should use [`Span`] and when one should use `SignedDuration`.
2628 /// In short, use `Span` (and therefore `Timestamp::until`) unless you have
2629 /// a specific reason to do otherwise.
2630 ///
2631 /// # Example
2632 ///
2633 /// ```
2634 /// use jiff::{civil::date, SignedDuration};
2635 ///
2636 /// let earlier = date(2006, 8, 24).at(22, 30, 0, 0).in_tz("US/Eastern")?;
2637 /// let later = date(2019, 1, 31).at(21, 0, 0, 0).in_tz("US/Eastern")?;
2638 /// assert_eq!(
2639 /// earlier.duration_until(&later),
2640 /// SignedDuration::from_hours(109_031) + SignedDuration::from_mins(30),
2641 /// );
2642 ///
2643 /// // Flipping the dates is fine, but you'll get a negative span.
2644 /// assert_eq!(
2645 /// later.duration_until(&earlier),
2646 /// -SignedDuration::from_hours(109_031) + -SignedDuration::from_mins(30),
2647 /// );
2648 ///
2649 /// # Ok::<(), Box<dyn std::error::Error>>(())
2650 /// ```
2651 ///
2652 /// # Example: difference with [`Zoned::until`]
2653 ///
2654 /// The main difference between this routine and `Zoned::until` is that
2655 /// the latter can return units other than a 96-bit integer of nanoseconds.
2656 /// While a 96-bit integer of nanoseconds can be converted into other units
2657 /// like hours, this can only be done for uniform units. (Uniform units are
2658 /// units for which each individual unit always corresponds to the same
2659 /// elapsed time regardless of the datetime it is relative to.) This can't
2660 /// be done for units like years, months or days.
2661 ///
2662 /// ```
2663 /// use jiff::{civil::date, SignedDuration, Span, SpanRound, ToSpan, Unit};
2664 ///
2665 /// let zdt1 = date(2024, 3, 10).at(0, 0, 0, 0).in_tz("US/Eastern")?;
2666 /// let zdt2 = date(2024, 3, 11).at(0, 0, 0, 0).in_tz("US/Eastern")?;
2667 ///
2668 /// let span = zdt1.until((Unit::Day, &zdt2))?;
2669 /// assert_eq!(format!("{span:#}"), "1d");
2670 ///
2671 /// let duration = zdt1.duration_until(&zdt2);
2672 /// // This day was only 23 hours long!
2673 /// assert_eq!(duration, SignedDuration::from_hours(23));
2674 /// // There's no way to extract years, months or days from the signed
2675 /// // duration like one might extract hours (because every hour
2676 /// // is the same length). Instead, you actually have to convert
2677 /// // it to a span and then balance it by providing a relative date!
2678 /// let options = SpanRound::new().largest(Unit::Day).relative(&zdt1);
2679 /// let span = Span::try_from(duration)?.round(options)?;
2680 /// assert_eq!(format!("{span:#}"), "1d");
2681 ///
2682 /// # Ok::<(), Box<dyn std::error::Error>>(())
2683 /// ```
2684 ///
2685 /// # Example: getting an unsigned duration
2686 ///
2687 /// If you're looking to find the duration between two zoned datetimes as
2688 /// a [`std::time::Duration`], you'll need to use this method to get a
2689 /// [`SignedDuration`] and then convert it to a `std::time::Duration`:
2690 ///
2691 /// ```
2692 /// use std::time::Duration;
2693 ///
2694 /// use jiff::civil::date;
2695 ///
2696 /// let zdt1 = date(2024, 7, 1).at(0, 0, 0, 0).in_tz("US/Eastern")?;
2697 /// let zdt2 = date(2024, 8, 1).at(0, 0, 0, 0).in_tz("US/Eastern")?;
2698 /// let duration = Duration::try_from(zdt1.duration_until(&zdt2))?;
2699 /// assert_eq!(duration, Duration::from_secs(31 * 24 * 60 * 60));
2700 ///
2701 /// // Note that unsigned durations cannot represent all
2702 /// // possible differences! If the duration would be negative,
2703 /// // then the conversion fails:
2704 /// assert!(Duration::try_from(zdt2.duration_until(&zdt1)).is_err());
2705 ///
2706 /// # Ok::<(), Box<dyn std::error::Error>>(())
2707 /// ```
2708 #[inline]
2709 pub fn duration_until(&self, other: &Zoned) -> SignedDuration {
2710 SignedDuration::zoned_until(self, other)
2711 }
2712
2713 /// This routine is identical to [`Zoned::duration_until`], but the
2714 /// order of the parameters is flipped.
2715 ///
2716 /// # Example
2717 ///
2718 /// ```
2719 /// use jiff::{civil::date, SignedDuration};
2720 ///
2721 /// let earlier = date(2006, 8, 24).at(22, 30, 0, 0).in_tz("US/Eastern")?;
2722 /// let later = date(2019, 1, 31).at(21, 0, 0, 0).in_tz("US/Eastern")?;
2723 /// assert_eq!(
2724 /// later.duration_since(&earlier),
2725 /// SignedDuration::from_hours(109_031) + SignedDuration::from_mins(30),
2726 /// );
2727 ///
2728 /// # Ok::<(), Box<dyn std::error::Error>>(())
2729 /// ```
2730 #[inline]
2731 pub fn duration_since(&self, other: &Zoned) -> SignedDuration {
2732 SignedDuration::zoned_until(other, self)
2733 }
2734
2735 /// Rounds this zoned datetime according to the [`ZonedRound`]
2736 /// configuration given.
2737 ///
2738 /// The principal option is [`ZonedRound::smallest`], which allows one to
2739 /// configure the smallest units in the returned zoned datetime. Rounding
2740 /// is what determines whether that unit should keep its current value
2741 /// or whether it should be incremented. Moreover, the amount it should
2742 /// be incremented can be configured via [`ZonedRound::increment`].
2743 /// Finally, the rounding strategy itself can be configured via
2744 /// [`ZonedRound::mode`].
2745 ///
2746 /// Note that this routine is generic and accepts anything that
2747 /// implements `Into<ZonedRound>`. Some notable implementations are:
2748 ///
2749 /// * `From<Unit> for ZonedRound`, which will automatically create a
2750 /// `ZonedRound::new().smallest(unit)` from the unit provided.
2751 /// * `From<(Unit, i64)> for ZonedRound`, which will automatically
2752 /// create a `ZonedRound::new().smallest(unit).increment(number)` from
2753 /// the unit and increment provided.
2754 ///
2755 /// # Errors
2756 ///
2757 /// This returns an error if the smallest unit configured on the given
2758 /// [`ZonedRound`] is bigger than days. An error is also returned if
2759 /// the rounding increment is greater than 1 when the units are days.
2760 /// (Currently, rounding to the nearest week, month or year is not
2761 /// supported.)
2762 ///
2763 /// When the smallest unit is less than days, the rounding increment must
2764 /// divide evenly into the next highest unit after the smallest unit
2765 /// configured (and must not be equivalent to it). For example, if the
2766 /// smallest unit is [`Unit::Nanosecond`], then *some* of the valid values
2767 /// for the rounding increment are `1`, `2`, `4`, `5`, `100` and `500`.
2768 /// Namely, any integer that divides evenly into `1,000` nanoseconds since
2769 /// there are `1,000` nanoseconds in the next highest unit (microseconds).
2770 ///
2771 /// This can also return an error in some cases where rounding would
2772 /// require arithmetic that exceeds the maximum zoned datetime value.
2773 ///
2774 /// # Example
2775 ///
2776 /// This is a basic example that demonstrates rounding a zoned datetime
2777 /// to the nearest day. This also demonstrates calling this method with
2778 /// the smallest unit directly, instead of constructing a `ZonedRound`
2779 /// manually.
2780 ///
2781 /// ```
2782 /// use jiff::{civil::date, Unit};
2783 ///
2784 /// // rounds up
2785 /// let zdt = date(2024, 6, 19).at(15, 0, 0, 0).in_tz("America/New_York")?;
2786 /// assert_eq!(
2787 /// zdt.round(Unit::Day)?,
2788 /// date(2024, 6, 20).at(0, 0, 0, 0).in_tz("America/New_York")?,
2789 /// );
2790 ///
2791 /// // rounds down
2792 /// let zdt = date(2024, 6, 19).at(10, 0, 0, 0).in_tz("America/New_York")?;
2793 /// assert_eq!(
2794 /// zdt.round(Unit::Day)?,
2795 /// date(2024, 6, 19).at(0, 0, 0, 0).in_tz("America/New_York")?,
2796 /// );
2797 ///
2798 /// # Ok::<(), Box<dyn std::error::Error>>(())
2799 /// ```
2800 ///
2801 /// # Example: changing the rounding mode
2802 ///
2803 /// The default rounding mode is [`RoundMode::HalfExpand`], which
2804 /// breaks ties by rounding away from zero. But other modes like
2805 /// [`RoundMode::Trunc`] can be used too:
2806 ///
2807 /// ```
2808 /// use jiff::{civil::date, RoundMode, Unit, Zoned, ZonedRound};
2809 ///
2810 /// let zdt = date(2024, 6, 19).at(15, 0, 0, 0).in_tz("America/New_York")?;
2811 /// assert_eq!(
2812 /// zdt.round(Unit::Day)?,
2813 /// date(2024, 6, 20).at(0, 0, 0, 0).in_tz("America/New_York")?,
2814 /// );
2815 /// // The default will round up to the next day for any time past noon (as
2816 /// // shown above), but using truncation rounding will always round down.
2817 /// assert_eq!(
2818 /// zdt.round(
2819 /// ZonedRound::new().smallest(Unit::Day).mode(RoundMode::Trunc),
2820 /// )?,
2821 /// date(2024, 6, 19).at(0, 0, 0, 0).in_tz("America/New_York")?,
2822 /// );
2823 ///
2824 /// # Ok::<(), Box<dyn std::error::Error>>(())
2825 /// ```
2826 ///
2827 /// # Example: rounding to the nearest 5 minute increment
2828 ///
2829 /// ```
2830 /// use jiff::{civil::date, Unit};
2831 ///
2832 /// // rounds down
2833 /// let zdt = date(2024, 6, 19)
2834 /// .at(15, 27, 29, 999_999_999)
2835 /// .in_tz("America/New_York")?;
2836 /// assert_eq!(
2837 /// zdt.round((Unit::Minute, 5))?,
2838 /// date(2024, 6, 19).at(15, 25, 0, 0).in_tz("America/New_York")?,
2839 /// );
2840 /// // rounds up
2841 /// let zdt = date(2024, 6, 19)
2842 /// .at(15, 27, 30, 0)
2843 /// .in_tz("America/New_York")?;
2844 /// assert_eq!(
2845 /// zdt.round((Unit::Minute, 5))?,
2846 /// date(2024, 6, 19).at(15, 30, 0, 0).in_tz("America/New_York")?,
2847 /// );
2848 ///
2849 /// # Ok::<(), Box<dyn std::error::Error>>(())
2850 /// ```
2851 ///
2852 /// # Example: behavior near time zone transitions
2853 ///
2854 /// When rounding this zoned datetime near time zone transitions (such as
2855 /// DST), the "sensible" thing is done by default. Namely, rounding will
2856 /// jump to the closest instant, even if the change in civil clock time is
2857 /// large. For example, when rounding up into a gap, the civil clock time
2858 /// will jump over the gap, but the corresponding change in the instant is
2859 /// as one might expect:
2860 ///
2861 /// ```
2862 /// use jiff::{Unit, Zoned};
2863 ///
2864 /// let zdt1: Zoned = "2024-03-10T01:59:00-05[America/New_York]".parse()?;
2865 /// let zdt2 = zdt1.round(Unit::Hour)?;
2866 /// assert_eq!(
2867 /// zdt2.to_string(),
2868 /// "2024-03-10T03:00:00-04:00[America/New_York]",
2869 /// );
2870 ///
2871 /// # Ok::<(), Box<dyn std::error::Error>>(())
2872 /// ```
2873 ///
2874 /// Similarly, when rounding inside a fold, rounding will respect whether
2875 /// it's the first or second time the clock has repeated the hour. For the
2876 /// DST transition in New York on `2024-11-03` from offset `-04` to `-05`,
2877 /// here is an example that rounds the first 1 o'clock hour:
2878 ///
2879 /// ```
2880 /// use jiff::{Unit, Zoned};
2881 ///
2882 /// let zdt1: Zoned = "2024-11-03T01:59:01-04[America/New_York]".parse()?;
2883 /// let zdt2 = zdt1.round(Unit::Minute)?;
2884 /// assert_eq!(
2885 /// zdt2.to_string(),
2886 /// "2024-11-03T01:59:00-04:00[America/New_York]",
2887 /// );
2888 ///
2889 /// # Ok::<(), Box<dyn std::error::Error>>(())
2890 /// ```
2891 ///
2892 /// And now the second 1 o'clock hour. Notice how the rounded result stays
2893 /// in the second 1 o'clock hour.
2894 ///
2895 /// ```
2896 /// use jiff::{Unit, Zoned};
2897 ///
2898 /// let zdt1: Zoned = "2024-11-03T01:59:01-05[America/New_York]".parse()?;
2899 /// let zdt2 = zdt1.round(Unit::Minute)?;
2900 /// assert_eq!(
2901 /// zdt2.to_string(),
2902 /// "2024-11-03T01:59:00-05:00[America/New_York]",
2903 /// );
2904 ///
2905 /// # Ok::<(), Box<dyn std::error::Error>>(())
2906 /// ```
2907 ///
2908 /// # Example: overflow error
2909 ///
2910 /// This example demonstrates that it's possible for this operation to
2911 /// result in an error from zoned datetime arithmetic overflow.
2912 ///
2913 /// ```
2914 /// use jiff::{Timestamp, Unit};
2915 ///
2916 /// let zdt = Timestamp::MAX.in_tz("America/New_York")?;
2917 /// assert!(zdt.round(Unit::Day).is_err());
2918 ///
2919 /// # Ok::<(), Box<dyn std::error::Error>>(())
2920 /// ```
2921 ///
2922 /// This occurs because rounding to the nearest day for the maximum
2923 /// timestamp would result in rounding up to the next day. But the next day
2924 /// is greater than the maximum, and so this returns an error.
2925 #[inline]
2926 pub fn round<R: Into<ZonedRound>>(
2927 &self,
2928 options: R,
2929 ) -> Result<Zoned, Error> {
2930 let options: ZonedRound = options.into();
2931 options.round(self)
2932 }
2933
2934 /*
2935 /// Return an iterator of periodic zoned datetimes determined by the given
2936 /// span.
2937 ///
2938 /// The given span may be negative, in which case, the iterator will move
2939 /// backwards through time. The iterator won't stop until either the span
2940 /// itself overflows, or it would otherwise exceed the minimum or maximum
2941 /// `Zoned` value.
2942 ///
2943 /// # Example: when to check a glucose monitor
2944 ///
2945 /// When my cat had diabetes, my veterinarian installed a glucose monitor
2946 /// and instructed me to scan it about every 5 hours. This example lists
2947 /// all of the times I need to scan it for the 2 days following its
2948 /// installation:
2949 ///
2950 /// ```
2951 /// use jiff::{civil::datetime, ToSpan};
2952 ///
2953 /// let start = datetime(2023, 7, 15, 16, 30, 0, 0).in_tz("America/New_York")?;
2954 /// let end = start.checked_add(2.days())?;
2955 /// let mut scan_times = vec![];
2956 /// for zdt in start.series(5.hours()).take_while(|zdt| zdt <= end) {
2957 /// scan_times.push(zdt.datetime());
2958 /// }
2959 /// assert_eq!(scan_times, vec![
2960 /// datetime(2023, 7, 15, 16, 30, 0, 0),
2961 /// datetime(2023, 7, 15, 21, 30, 0, 0),
2962 /// datetime(2023, 7, 16, 2, 30, 0, 0),
2963 /// datetime(2023, 7, 16, 7, 30, 0, 0),
2964 /// datetime(2023, 7, 16, 12, 30, 0, 0),
2965 /// datetime(2023, 7, 16, 17, 30, 0, 0),
2966 /// datetime(2023, 7, 16, 22, 30, 0, 0),
2967 /// datetime(2023, 7, 17, 3, 30, 0, 0),
2968 /// datetime(2023, 7, 17, 8, 30, 0, 0),
2969 /// datetime(2023, 7, 17, 13, 30, 0, 0),
2970 /// ]);
2971 ///
2972 /// # Ok::<(), Box<dyn std::error::Error>>(())
2973 /// ```
2974 ///
2975 /// # Example
2976 ///
2977 /// BREADCRUMBS: Maybe just remove ZonedSeries for now..?
2978 ///
2979 /// ```
2980 /// use jiff::{civil::date, ToSpan};
2981 ///
2982 /// let zdt = date(2011, 12, 28).in_tz("Pacific/Apia")?;
2983 /// let mut it = zdt.series(1.day());
2984 /// assert_eq!(it.next(), Some(date(2011, 12, 28).in_tz("Pacific/Apia")?));
2985 /// assert_eq!(it.next(), Some(date(2011, 12, 29).in_tz("Pacific/Apia")?));
2986 /// assert_eq!(it.next(), Some(date(2011, 12, 30).in_tz("Pacific/Apia")?));
2987 /// assert_eq!(it.next(), Some(date(2011, 12, 31).in_tz("Pacific/Apia")?));
2988 /// assert_eq!(it.next(), Some(date(2012, 01, 01).in_tz("Pacific/Apia")?));
2989 ///
2990 /// # Ok::<(), Box<dyn std::error::Error>>(())
2991 /// ```
2992 #[inline]
2993 pub fn series(self, period: Span) -> ZonedSeries {
2994 ZonedSeries { start: self, period, step: 0 }
2995 }
2996 */
2997
2998 #[inline]
2999 fn into_parts(self) -> (Timestamp, DateTime, Offset, TimeZone) {
3000 let inner = self.inner;
3001 let ZonedInner { timestamp, datetime, offset, time_zone } = inner;
3002 (timestamp, datetime, offset, time_zone)
3003 }
3004}
3005
3006/// Parsing and formatting using a "printf"-style API.
3007impl Zoned {
3008 /// Parses a zoned datetime in `input` matching the given `format`.
3009 ///
3010 /// The format string uses a "printf"-style API where conversion
3011 /// specifiers can be used as place holders to match components of
3012 /// a datetime. For details on the specifiers supported, see the
3013 /// [`fmt::strtime`] module documentation.
3014 ///
3015 /// # Warning
3016 ///
3017 /// The `strtime` module APIs do not require an IANA time zone identifier
3018 /// to parse a `Zoned`. If one is not used, then if you format a zoned
3019 /// datetime in a time zone like `America/New_York` and then parse it back
3020 /// again, the zoned datetime you get back will be a "fixed offset" zoned
3021 /// datetime. This in turn means it will not perform daylight saving time
3022 /// safe arithmetic.
3023 ///
3024 /// However, the `%Q` directive may be used to both format and parse an
3025 /// IANA time zone identifier. It is strongly recommended to use this
3026 /// directive whenever one is formatting or parsing `Zoned` values.
3027 ///
3028 /// # Errors
3029 ///
3030 /// This returns an error when parsing failed. This might happen because
3031 /// the format string itself was invalid, or because the input didn't match
3032 /// the format string.
3033 ///
3034 /// This also returns an error if there wasn't sufficient information to
3035 /// construct a zoned datetime. For example, if an offset wasn't parsed.
3036 ///
3037 /// # Example
3038 ///
3039 /// This example shows how to parse a zoned datetime:
3040 ///
3041 /// ```
3042 /// use jiff::Zoned;
3043 ///
3044 /// let zdt = Zoned::strptime("%F %H:%M %:Q", "2024-07-14 21:14 US/Eastern")?;
3045 /// assert_eq!(zdt.to_string(), "2024-07-14T21:14:00-04:00[US/Eastern]");
3046 ///
3047 /// # Ok::<(), Box<dyn std::error::Error>>(())
3048 /// ```
3049 #[inline]
3050 pub fn strptime(
3051 format: impl AsRef<[u8]>,
3052 input: impl AsRef<[u8]>,
3053 ) -> Result<Zoned, Error> {
3054 fmt::strtime::parse(format, input).and_then(|tm| tm.to_zoned())
3055 }
3056
3057 /// Formats this zoned datetime according to the given `format`.
3058 ///
3059 /// The format string uses a "printf"-style API where conversion
3060 /// specifiers can be used as place holders to format components of
3061 /// a datetime. For details on the specifiers supported, see the
3062 /// [`fmt::strtime`] module documentation.
3063 ///
3064 /// # Warning
3065 ///
3066 /// The `strtime` module APIs do not support parsing or formatting with
3067 /// IANA time zone identifiers. This means that if you format a zoned
3068 /// datetime in a time zone like `America/New_York` and then parse it back
3069 /// again, the zoned datetime you get back will be a "fixed offset" zoned
3070 /// datetime. This in turn means it will not perform daylight saving time
3071 /// safe arithmetic.
3072 ///
3073 /// The `strtime` modules APIs are useful for ad hoc formatting and
3074 /// parsing, but they shouldn't be used as an interchange format. For
3075 /// an interchange format, the default `std::fmt::Display` and
3076 /// `std::str::FromStr` trait implementations on `Zoned` are appropriate.
3077 ///
3078 /// # Errors and panics
3079 ///
3080 /// While this routine itself does not error or panic, using the value
3081 /// returned may result in a panic if formatting fails. See the
3082 /// documentation on [`fmt::strtime::Display`] for more information.
3083 ///
3084 /// To format in a way that surfaces errors without panicking, use either
3085 /// [`fmt::strtime::format`] or [`fmt::strtime::BrokenDownTime::format`].
3086 ///
3087 /// # Example
3088 ///
3089 /// While the output of the Unix `date` command is likely locale specific,
3090 /// this is what it looks like on my system:
3091 ///
3092 /// ```
3093 /// use jiff::civil::date;
3094 ///
3095 /// let zdt = date(2024, 7, 15).at(16, 24, 59, 0).in_tz("America/New_York")?;
3096 /// let string = zdt.strftime("%a %b %e %I:%M:%S %p %Z %Y").to_string();
3097 /// assert_eq!(string, "Mon Jul 15 04:24:59 PM EDT 2024");
3098 ///
3099 /// # Ok::<(), Box<dyn std::error::Error>>(())
3100 /// ```
3101 #[inline]
3102 pub fn strftime<'f, F: 'f + ?Sized + AsRef<[u8]>>(
3103 &self,
3104 format: &'f F,
3105 ) -> fmt::strtime::Display<'f> {
3106 fmt::strtime::Display { fmt: format.as_ref(), tm: self.into() }
3107 }
3108}
3109
3110impl Default for Zoned {
3111 #[inline]
3112 fn default() -> Zoned {
3113 Zoned::new(Timestamp::default(), TimeZone::UTC)
3114 }
3115}
3116
3117/// Converts a `Zoned` datetime into a human readable datetime string.
3118///
3119/// (This `Debug` representation currently emits the same string as the
3120/// `Display` representation, but this is not a guarantee.)
3121///
3122/// Options currently supported:
3123///
3124/// * [`std::fmt::Formatter::precision`] can be set to control the precision
3125/// of the fractional second component.
3126///
3127/// # Example
3128///
3129/// ```
3130/// use jiff::civil::date;
3131///
3132/// let zdt = date(2024, 6, 15).at(7, 0, 0, 123_000_000).in_tz("US/Eastern")?;
3133/// assert_eq!(
3134/// format!("{zdt:.6?}"),
3135/// "2024-06-15T07:00:00.123000-04:00[US/Eastern]",
3136/// );
3137/// // Precision values greater than 9 are clamped to 9.
3138/// assert_eq!(
3139/// format!("{zdt:.300?}"),
3140/// "2024-06-15T07:00:00.123000000-04:00[US/Eastern]",
3141/// );
3142/// // A precision of 0 implies the entire fractional
3143/// // component is always truncated.
3144/// assert_eq!(
3145/// format!("{zdt:.0?}"),
3146/// "2024-06-15T07:00:00-04:00[US/Eastern]",
3147/// );
3148///
3149/// # Ok::<(), Box<dyn std::error::Error>>(())
3150/// ```
3151impl core::fmt::Debug for Zoned {
3152 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
3153 core::fmt::Display::fmt(self, f)
3154 }
3155}
3156
3157/// Converts a `Zoned` datetime into a RFC 9557 compliant string.
3158///
3159/// Options currently supported:
3160///
3161/// * [`std::fmt::Formatter::precision`] can be set to control the precision
3162/// of the fractional second component.
3163///
3164/// # Example
3165///
3166/// ```
3167/// use jiff::civil::date;
3168///
3169/// let zdt = date(2024, 6, 15).at(7, 0, 0, 123_000_000).in_tz("US/Eastern")?;
3170/// assert_eq!(
3171/// format!("{zdt:.6}"),
3172/// "2024-06-15T07:00:00.123000-04:00[US/Eastern]",
3173/// );
3174/// // Precision values greater than 9 are clamped to 9.
3175/// assert_eq!(
3176/// format!("{zdt:.300}"),
3177/// "2024-06-15T07:00:00.123000000-04:00[US/Eastern]",
3178/// );
3179/// // A precision of 0 implies the entire fractional
3180/// // component is always truncated.
3181/// assert_eq!(
3182/// format!("{zdt:.0}"),
3183/// "2024-06-15T07:00:00-04:00[US/Eastern]",
3184/// );
3185///
3186/// # Ok::<(), Box<dyn std::error::Error>>(())
3187/// ```
3188impl core::fmt::Display for Zoned {
3189 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
3190 use crate::fmt::StdFmtWrite;
3191
3192 let precision =
3193 f.precision().map(|p| u8::try_from(p).unwrap_or(u8::MAX));
3194 temporal::DateTimePrinter::new()
3195 .precision(precision)
3196 .print_zoned(self, StdFmtWrite(f))
3197 .map_err(|_| core::fmt::Error)
3198 }
3199}
3200
3201/// Parses a zoned timestamp from the Temporal datetime format.
3202///
3203/// See the [`fmt::temporal`](crate::fmt::temporal) for more information on
3204/// the precise format.
3205///
3206/// Note that this is only enabled when the `std` feature
3207/// is enabled because it requires access to a global
3208/// [`TimeZoneDatabase`](crate::tz::TimeZoneDatabase).
3209impl core::str::FromStr for Zoned {
3210 type Err = Error;
3211
3212 fn from_str(string: &str) -> Result<Zoned, Error> {
3213 DEFAULT_DATETIME_PARSER.parse_zoned(string)
3214 }
3215}
3216
3217impl Eq for Zoned {}
3218
3219impl PartialEq for Zoned {
3220 #[inline]
3221 fn eq(&self, rhs: &Zoned) -> bool {
3222 self.timestamp().eq(&rhs.timestamp())
3223 }
3224}
3225
3226impl<'a> PartialEq<Zoned> for &'a Zoned {
3227 #[inline]
3228 fn eq(&self, rhs: &Zoned) -> bool {
3229 (**self).eq(rhs)
3230 }
3231}
3232
3233impl Ord for Zoned {
3234 #[inline]
3235 fn cmp(&self, rhs: &Zoned) -> core::cmp::Ordering {
3236 self.timestamp().cmp(&rhs.timestamp())
3237 }
3238}
3239
3240impl PartialOrd for Zoned {
3241 #[inline]
3242 fn partial_cmp(&self, rhs: &Zoned) -> Option<core::cmp::Ordering> {
3243 Some(self.cmp(rhs))
3244 }
3245}
3246
3247impl<'a> PartialOrd<Zoned> for &'a Zoned {
3248 #[inline]
3249 fn partial_cmp(&self, rhs: &Zoned) -> Option<core::cmp::Ordering> {
3250 (**self).partial_cmp(rhs)
3251 }
3252}
3253
3254impl core::hash::Hash for Zoned {
3255 #[inline]
3256 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
3257 self.timestamp().hash(state);
3258 }
3259}
3260
3261#[cfg(feature = "std")]
3262impl TryFrom<std::time::SystemTime> for Zoned {
3263 type Error = Error;
3264
3265 #[inline]
3266 fn try_from(system_time: std::time::SystemTime) -> Result<Zoned, Error> {
3267 let timestamp = Timestamp::try_from(system_time)?;
3268 Ok(Zoned::new(timestamp, TimeZone::system()))
3269 }
3270}
3271
3272#[cfg(feature = "std")]
3273impl From<Zoned> for std::time::SystemTime {
3274 #[inline]
3275 fn from(time: Zoned) -> std::time::SystemTime {
3276 time.timestamp().into()
3277 }
3278}
3279
3280/// Adds a span of time to a zoned datetime.
3281///
3282/// This uses checked arithmetic and panics on overflow. To handle overflow
3283/// without panics, use [`Zoned::checked_add`].
3284impl<'a> core::ops::Add<Span> for &'a Zoned {
3285 type Output = Zoned;
3286
3287 #[inline]
3288 fn add(self, rhs: Span) -> Zoned {
3289 self.checked_add(rhs)
3290 .expect("adding span to zoned datetime overflowed")
3291 }
3292}
3293
3294/// Adds a span of time to a zoned datetime in place.
3295///
3296/// This uses checked arithmetic and panics on overflow. To handle overflow
3297/// without panics, use [`Zoned::checked_add`].
3298impl core::ops::AddAssign<Span> for Zoned {
3299 #[inline]
3300 fn add_assign(&mut self, rhs: Span) {
3301 *self = &*self + rhs
3302 }
3303}
3304
3305/// Subtracts a span of time from a zoned datetime.
3306///
3307/// This uses checked arithmetic and panics on overflow. To handle overflow
3308/// without panics, use [`Zoned::checked_sub`].
3309impl<'a> core::ops::Sub<Span> for &'a Zoned {
3310 type Output = Zoned;
3311
3312 #[inline]
3313 fn sub(self, rhs: Span) -> Zoned {
3314 self.checked_sub(rhs)
3315 .expect("subtracting span from zoned datetime overflowed")
3316 }
3317}
3318
3319/// Subtracts a span of time from a zoned datetime in place.
3320///
3321/// This uses checked arithmetic and panics on overflow. To handle overflow
3322/// without panics, use [`Zoned::checked_sub`].
3323impl core::ops::SubAssign<Span> for Zoned {
3324 #[inline]
3325 fn sub_assign(&mut self, rhs: Span) {
3326 *self = &*self - rhs
3327 }
3328}
3329
3330/// Computes the span of time between two zoned datetimes.
3331///
3332/// This will return a negative span when the zoned datetime being subtracted
3333/// is greater.
3334///
3335/// Since this uses the default configuration for calculating a span between
3336/// two zoned datetimes (no rounding and largest units is hours), this will
3337/// never panic or fail in any way. It is guaranteed that the largest non-zero
3338/// unit in the `Span` returned will be hours.
3339///
3340/// To configure the largest unit or enable rounding, use [`Zoned::since`].
3341impl<'a> core::ops::Sub for &'a Zoned {
3342 type Output = Span;
3343
3344 #[inline]
3345 fn sub(self, rhs: &'a Zoned) -> Span {
3346 self.since(rhs).expect("since never fails when given Zoned")
3347 }
3348}
3349
3350/// Adds a signed duration of time to a zoned datetime.
3351///
3352/// This uses checked arithmetic and panics on overflow. To handle overflow
3353/// without panics, use [`Zoned::checked_add`].
3354impl<'a> core::ops::Add<SignedDuration> for &'a Zoned {
3355 type Output = Zoned;
3356
3357 #[inline]
3358 fn add(self, rhs: SignedDuration) -> Zoned {
3359 self.checked_add(rhs)
3360 .expect("adding signed duration to zoned datetime overflowed")
3361 }
3362}
3363
3364/// Adds a signed duration of time to a zoned datetime in place.
3365///
3366/// This uses checked arithmetic and panics on overflow. To handle overflow
3367/// without panics, use [`Zoned::checked_add`].
3368impl core::ops::AddAssign<SignedDuration> for Zoned {
3369 #[inline]
3370 fn add_assign(&mut self, rhs: SignedDuration) {
3371 *self = &*self + rhs
3372 }
3373}
3374
3375/// Subtracts a signed duration of time from a zoned datetime.
3376///
3377/// This uses checked arithmetic and panics on overflow. To handle overflow
3378/// without panics, use [`Zoned::checked_sub`].
3379impl<'a> core::ops::Sub<SignedDuration> for &'a Zoned {
3380 type Output = Zoned;
3381
3382 #[inline]
3383 fn sub(self, rhs: SignedDuration) -> Zoned {
3384 self.checked_sub(rhs).expect(
3385 "subtracting signed duration from zoned datetime overflowed",
3386 )
3387 }
3388}
3389
3390/// Subtracts a signed duration of time from a zoned datetime in place.
3391///
3392/// This uses checked arithmetic and panics on overflow. To handle overflow
3393/// without panics, use [`Zoned::checked_sub`].
3394impl core::ops::SubAssign<SignedDuration> for Zoned {
3395 #[inline]
3396 fn sub_assign(&mut self, rhs: SignedDuration) {
3397 *self = &*self - rhs
3398 }
3399}
3400
3401/// Adds an unsigned duration of time to a zoned datetime.
3402///
3403/// This uses checked arithmetic and panics on overflow. To handle overflow
3404/// without panics, use [`Zoned::checked_add`].
3405impl<'a> core::ops::Add<UnsignedDuration> for &'a Zoned {
3406 type Output = Zoned;
3407
3408 #[inline]
3409 fn add(self, rhs: UnsignedDuration) -> Zoned {
3410 self.checked_add(rhs)
3411 .expect("adding unsigned duration to zoned datetime overflowed")
3412 }
3413}
3414
3415/// Adds an unsigned duration of time to a zoned datetime in place.
3416///
3417/// This uses checked arithmetic and panics on overflow. To handle overflow
3418/// without panics, use [`Zoned::checked_add`].
3419impl core::ops::AddAssign<UnsignedDuration> for Zoned {
3420 #[inline]
3421 fn add_assign(&mut self, rhs: UnsignedDuration) {
3422 *self = &*self + rhs
3423 }
3424}
3425
3426/// Subtracts an unsigned duration of time from a zoned datetime.
3427///
3428/// This uses checked arithmetic and panics on overflow. To handle overflow
3429/// without panics, use [`Zoned::checked_sub`].
3430impl<'a> core::ops::Sub<UnsignedDuration> for &'a Zoned {
3431 type Output = Zoned;
3432
3433 #[inline]
3434 fn sub(self, rhs: UnsignedDuration) -> Zoned {
3435 self.checked_sub(rhs).expect(
3436 "subtracting unsigned duration from zoned datetime overflowed",
3437 )
3438 }
3439}
3440
3441/// Subtracts an unsigned duration of time from a zoned datetime in place.
3442///
3443/// This uses checked arithmetic and panics on overflow. To handle overflow
3444/// without panics, use [`Zoned::checked_sub`].
3445impl core::ops::SubAssign<UnsignedDuration> for Zoned {
3446 #[inline]
3447 fn sub_assign(&mut self, rhs: UnsignedDuration) {
3448 *self = &*self - rhs
3449 }
3450}
3451
3452#[cfg(feature = "serde")]
3453impl serde::Serialize for Zoned {
3454 #[inline]
3455 fn serialize<S: serde::Serializer>(
3456 &self,
3457 serializer: S,
3458 ) -> Result<S::Ok, S::Error> {
3459 serializer.collect_str(self)
3460 }
3461}
3462
3463#[cfg(feature = "serde")]
3464impl<'de> serde::Deserialize<'de> for Zoned {
3465 #[inline]
3466 fn deserialize<D: serde::Deserializer<'de>>(
3467 deserializer: D,
3468 ) -> Result<Zoned, D::Error> {
3469 use serde::de;
3470
3471 struct ZonedVisitor;
3472
3473 impl<'de> de::Visitor<'de> for ZonedVisitor {
3474 type Value = Zoned;
3475
3476 fn expecting(
3477 &self,
3478 f: &mut core::fmt::Formatter,
3479 ) -> core::fmt::Result {
3480 f.write_str("a zoned datetime string")
3481 }
3482
3483 #[inline]
3484 fn visit_bytes<E: de::Error>(
3485 self,
3486 value: &[u8],
3487 ) -> Result<Zoned, E> {
3488 DEFAULT_DATETIME_PARSER
3489 .parse_zoned(value)
3490 .map_err(de::Error::custom)
3491 }
3492
3493 #[inline]
3494 fn visit_str<E: de::Error>(self, value: &str) -> Result<Zoned, E> {
3495 self.visit_bytes(value.as_bytes())
3496 }
3497 }
3498
3499 deserializer.deserialize_str(ZonedVisitor)
3500 }
3501}
3502
3503#[cfg(test)]
3504impl quickcheck::Arbitrary for Zoned {
3505 fn arbitrary(g: &mut quickcheck::Gen) -> Zoned {
3506 let timestamp = Timestamp::arbitrary(g);
3507 let tz = TimeZone::UTC; // TODO: do something better here?
3508 Zoned::new(timestamp, tz)
3509 }
3510
3511 fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Self>> {
3512 let timestamp = self.timestamp();
3513 alloc::boxed::Box::new(
3514 timestamp
3515 .shrink()
3516 .map(|timestamp| Zoned::new(timestamp, TimeZone::UTC)),
3517 )
3518 }
3519}
3520
3521/*
3522/// An iterator over periodic zoned datetimes, created by [`Zoned::series`].
3523///
3524/// It is exhausted when the next value would exceed a [`Span`] or [`Zoned`]
3525/// value.
3526#[derive(Clone, Debug)]
3527pub struct ZonedSeries {
3528 start: Zoned,
3529 period: Span,
3530 step: i64,
3531}
3532
3533impl Iterator for ZonedSeries {
3534 type Item = Zoned;
3535
3536 #[inline]
3537 fn next(&mut self) -> Option<Zoned> {
3538 // let this = self.start.clone();
3539 // self.start = self.start.checked_add(self.period).ok()?;
3540 // Some(this)
3541 // This is how civil::DateTime series works. But this has a problem
3542 // for Zoned when there are time zone transitions that skip an entire
3543 // day. For example, Pacific/Api doesn't have a December 30, 2011.
3544 // For that case, the code above works better. But if you do it that
3545 // way, then you get the `jan31 + 1 month = feb28` and
3546 // `feb28 + 1 month = march28` problem. Where you would instead
3547 // expect jan31, feb28, mar31... I think.
3548 //
3549 // So I'm not quite sure how to resolve this particular conundrum.
3550 // And this is why ZonedSeries is currently not available.
3551 let span = self.period.checked_mul(self.step).ok()?;
3552 self.step = self.step.checked_add(1)?;
3553 let zdt = self.start.checked_add(span).ok()?;
3554 Some(zdt)
3555 }
3556}
3557*/
3558
3559/// Options for [`Timestamp::checked_add`] and [`Timestamp::checked_sub`].
3560///
3561/// This type provides a way to ergonomically add one of a few different
3562/// duration types to a [`Timestamp`].
3563///
3564/// The main way to construct values of this type is with its `From` trait
3565/// implementations:
3566///
3567/// * `From<Span> for ZonedArithmetic` adds (or subtracts) the given span
3568/// to the receiver timestamp.
3569/// * `From<SignedDuration> for ZonedArithmetic` adds (or subtracts)
3570/// the given signed duration to the receiver timestamp.
3571/// * `From<std::time::Duration> for ZonedArithmetic` adds (or subtracts)
3572/// the given unsigned duration to the receiver timestamp.
3573///
3574/// # Example
3575///
3576/// ```
3577/// use std::time::Duration;
3578///
3579/// use jiff::{SignedDuration, Timestamp, ToSpan};
3580///
3581/// let ts: Timestamp = "2024-02-28T00:00:00Z".parse()?;
3582/// assert_eq!(
3583/// ts.checked_add(48.hours())?,
3584/// "2024-03-01T00:00:00Z".parse()?,
3585/// );
3586/// assert_eq!(
3587/// ts.checked_add(SignedDuration::from_hours(48))?,
3588/// "2024-03-01T00:00:00Z".parse()?,
3589/// );
3590/// assert_eq!(
3591/// ts.checked_add(Duration::from_secs(48 * 60 * 60))?,
3592/// "2024-03-01T00:00:00Z".parse()?,
3593/// );
3594///
3595/// # Ok::<(), Box<dyn std::error::Error>>(())
3596/// ```
3597#[derive(Clone, Copy, Debug)]
3598pub struct ZonedArithmetic {
3599 duration: Duration,
3600}
3601
3602impl ZonedArithmetic {
3603 #[inline]
3604 fn checked_add(self, zdt: &Zoned) -> Result<Zoned, Error> {
3605 match self.duration.to_signed()? {
3606 SDuration::Span(span) => zdt.checked_add_span(span),
3607 SDuration::Absolute(sdur) => zdt.checked_add_duration(sdur),
3608 }
3609 }
3610
3611 #[inline]
3612 fn checked_neg(self) -> Result<ZonedArithmetic, Error> {
3613 let duration = self.duration.checked_neg()?;
3614 Ok(ZonedArithmetic { duration })
3615 }
3616
3617 #[inline]
3618 fn is_negative(&self) -> bool {
3619 self.duration.is_negative()
3620 }
3621}
3622
3623impl From<Span> for ZonedArithmetic {
3624 fn from(span: Span) -> ZonedArithmetic {
3625 let duration = Duration::from(span);
3626 ZonedArithmetic { duration }
3627 }
3628}
3629
3630impl From<SignedDuration> for ZonedArithmetic {
3631 fn from(sdur: SignedDuration) -> ZonedArithmetic {
3632 let duration = Duration::from(sdur);
3633 ZonedArithmetic { duration }
3634 }
3635}
3636
3637impl From<UnsignedDuration> for ZonedArithmetic {
3638 fn from(udur: UnsignedDuration) -> ZonedArithmetic {
3639 let duration = Duration::from(udur);
3640 ZonedArithmetic { duration }
3641 }
3642}
3643
3644impl<'a> From<&'a Span> for ZonedArithmetic {
3645 fn from(span: &'a Span) -> ZonedArithmetic {
3646 ZonedArithmetic::from(*span)
3647 }
3648}
3649
3650impl<'a> From<&'a SignedDuration> for ZonedArithmetic {
3651 fn from(sdur: &'a SignedDuration) -> ZonedArithmetic {
3652 ZonedArithmetic::from(*sdur)
3653 }
3654}
3655
3656impl<'a> From<&'a UnsignedDuration> for ZonedArithmetic {
3657 fn from(udur: &'a UnsignedDuration) -> ZonedArithmetic {
3658 ZonedArithmetic::from(*udur)
3659 }
3660}
3661
3662/// Options for [`Zoned::since`] and [`Zoned::until`].
3663///
3664/// This type provides a way to configure the calculation of spans between two
3665/// [`Zoned`] values. In particular, both `Zoned::since` and `Zoned::until`
3666/// accept anything that implements `Into<ZonedDifference>`. There are a few
3667/// key trait implementations that make this convenient:
3668///
3669/// * `From<&Zoned> for ZonedDifference` will construct a configuration
3670/// consisting of just the zoned datetime. So for example, `zdt1.since(zdt2)`
3671/// returns the span from `zdt2` to `zdt1`.
3672/// * `From<(Unit, &Zoned)>` is a convenient way to specify the largest units
3673/// that should be present on the span returned. By default, the largest units
3674/// are days. Using this trait implementation is equivalent to
3675/// `ZonedDifference::new(&zdt).largest(unit)`.
3676///
3677/// One can also provide a `ZonedDifference` value directly. Doing so
3678/// is necessary to use the rounding features of calculating a span. For
3679/// example, setting the smallest unit (defaults to [`Unit::Nanosecond`]), the
3680/// rounding mode (defaults to [`RoundMode::Trunc`]) and the rounding increment
3681/// (defaults to `1`). The defaults are selected such that no rounding occurs.
3682///
3683/// Rounding a span as part of calculating it is provided as a convenience.
3684/// Callers may choose to round the span as a distinct step via
3685/// [`Span::round`], but callers may need to provide a reference date
3686/// for rounding larger units. By coupling rounding with routines like
3687/// [`Zoned::since`], the reference date can be set automatically based on
3688/// the input to `Zoned::since`.
3689///
3690/// # Example
3691///
3692/// This example shows how to round a span between two zoned datetimes to the
3693/// nearest half-hour, with ties breaking away from zero.
3694///
3695/// ```
3696/// use jiff::{RoundMode, ToSpan, Unit, Zoned, ZonedDifference};
3697///
3698/// let zdt1 = "2024-03-15 08:14:00.123456789[America/New_York]".parse::<Zoned>()?;
3699/// let zdt2 = "2030-03-22 15:00[America/New_York]".parse::<Zoned>()?;
3700/// let span = zdt1.until(
3701/// ZonedDifference::new(&zdt2)
3702/// .smallest(Unit::Minute)
3703/// .largest(Unit::Year)
3704/// .mode(RoundMode::HalfExpand)
3705/// .increment(30),
3706/// )?;
3707/// assert_eq!(span, 6.years().days(7).hours(7).fieldwise());
3708///
3709/// # Ok::<(), Box<dyn std::error::Error>>(())
3710/// ```
3711#[derive(Clone, Copy, Debug)]
3712pub struct ZonedDifference<'a> {
3713 zoned: &'a Zoned,
3714 round: SpanRound<'static>,
3715}
3716
3717impl<'a> ZonedDifference<'a> {
3718 /// Create a new default configuration for computing the span between the
3719 /// given zoned datetime and some other zoned datetime (specified as the
3720 /// receiver in [`Zoned::since`] or [`Zoned::until`]).
3721 #[inline]
3722 pub fn new(zoned: &'a Zoned) -> ZonedDifference<'a> {
3723 // We use truncation rounding by default since it seems that's
3724 // what is generally expected when computing the difference between
3725 // datetimes.
3726 //
3727 // See: https://github.com/tc39/proposal-temporal/issues/1122
3728 let round = SpanRound::new().mode(RoundMode::Trunc);
3729 ZonedDifference { zoned, round }
3730 }
3731
3732 /// Set the smallest units allowed in the span returned.
3733 ///
3734 /// When a largest unit is not specified and the smallest unit is hours
3735 /// or greater, then the largest unit is automatically set to be equal to
3736 /// the smallest unit.
3737 ///
3738 /// # Errors
3739 ///
3740 /// The smallest units must be no greater than the largest units. If this
3741 /// is violated, then computing a span with this configuration will result
3742 /// in an error.
3743 ///
3744 /// # Example
3745 ///
3746 /// This shows how to round a span between two zoned datetimes to the
3747 /// nearest number of weeks.
3748 ///
3749 /// ```
3750 /// use jiff::{RoundMode, ToSpan, Unit, Zoned, ZonedDifference};
3751 ///
3752 /// let zdt1 = "2024-03-15 08:14[America/New_York]".parse::<Zoned>()?;
3753 /// let zdt2 = "2030-11-22 08:30[America/New_York]".parse::<Zoned>()?;
3754 /// let span = zdt1.until(
3755 /// ZonedDifference::new(&zdt2)
3756 /// .smallest(Unit::Week)
3757 /// .largest(Unit::Week)
3758 /// .mode(RoundMode::HalfExpand),
3759 /// )?;
3760 /// assert_eq!(format!("{span:#}"), "349w");
3761 ///
3762 /// # Ok::<(), Box<dyn std::error::Error>>(())
3763 /// ```
3764 #[inline]
3765 pub fn smallest(self, unit: Unit) -> ZonedDifference<'a> {
3766 ZonedDifference { round: self.round.smallest(unit), ..self }
3767 }
3768
3769 /// Set the largest units allowed in the span returned.
3770 ///
3771 /// When a largest unit is not specified and the smallest unit is hours
3772 /// or greater, then the largest unit is automatically set to be equal to
3773 /// the smallest unit. Otherwise, when the largest unit is not specified,
3774 /// it is set to hours.
3775 ///
3776 /// Once a largest unit is set, there is no way to change this rounding
3777 /// configuration back to using the "automatic" default. Instead, callers
3778 /// must create a new configuration.
3779 ///
3780 /// # Errors
3781 ///
3782 /// The largest units, when set, must be at least as big as the smallest
3783 /// units (which defaults to [`Unit::Nanosecond`]). If this is violated,
3784 /// then computing a span with this configuration will result in an error.
3785 ///
3786 /// # Example
3787 ///
3788 /// This shows how to round a span between two zoned datetimes to units no
3789 /// bigger than seconds.
3790 ///
3791 /// ```
3792 /// use jiff::{ToSpan, Unit, Zoned, ZonedDifference};
3793 ///
3794 /// let zdt1 = "2024-03-15 08:14[America/New_York]".parse::<Zoned>()?;
3795 /// let zdt2 = "2030-11-22 08:30[America/New_York]".parse::<Zoned>()?;
3796 /// let span = zdt1.until(
3797 /// ZonedDifference::new(&zdt2).largest(Unit::Second),
3798 /// )?;
3799 /// assert_eq!(span.to_string(), "PT211079760S");
3800 ///
3801 /// # Ok::<(), Box<dyn std::error::Error>>(())
3802 /// ```
3803 #[inline]
3804 pub fn largest(self, unit: Unit) -> ZonedDifference<'a> {
3805 ZonedDifference { round: self.round.largest(unit), ..self }
3806 }
3807
3808 /// Set the rounding mode.
3809 ///
3810 /// This defaults to [`RoundMode::Trunc`] since it's plausible that
3811 /// rounding "up" in the context of computing the span between
3812 /// two zoned datetimes could be surprising in a number of cases. The
3813 /// [`RoundMode::HalfExpand`] mode corresponds to typical rounding you
3814 /// might have learned about in school. But a variety of other rounding
3815 /// modes exist.
3816 ///
3817 /// # Example
3818 ///
3819 /// This shows how to always round "up" towards positive infinity.
3820 ///
3821 /// ```
3822 /// use jiff::{RoundMode, ToSpan, Unit, Zoned, ZonedDifference};
3823 ///
3824 /// let zdt1 = "2024-03-15 08:10[America/New_York]".parse::<Zoned>()?;
3825 /// let zdt2 = "2024-03-15 08:11[America/New_York]".parse::<Zoned>()?;
3826 /// let span = zdt1.until(
3827 /// ZonedDifference::new(&zdt2)
3828 /// .smallest(Unit::Hour)
3829 /// .mode(RoundMode::Ceil),
3830 /// )?;
3831 /// // Only one minute elapsed, but we asked to always round up!
3832 /// assert_eq!(span, 1.hour().fieldwise());
3833 ///
3834 /// // Since `Ceil` always rounds toward positive infinity, the behavior
3835 /// // flips for a negative span.
3836 /// let span = zdt1.since(
3837 /// ZonedDifference::new(&zdt2)
3838 /// .smallest(Unit::Hour)
3839 /// .mode(RoundMode::Ceil),
3840 /// )?;
3841 /// assert_eq!(span, 0.hour().fieldwise());
3842 ///
3843 /// # Ok::<(), Box<dyn std::error::Error>>(())
3844 /// ```
3845 #[inline]
3846 pub fn mode(self, mode: RoundMode) -> ZonedDifference<'a> {
3847 ZonedDifference { round: self.round.mode(mode), ..self }
3848 }
3849
3850 /// Set the rounding increment for the smallest unit.
3851 ///
3852 /// The default value is `1`. Other values permit rounding the smallest
3853 /// unit to the nearest integer increment specified. For example, if the
3854 /// smallest unit is set to [`Unit::Minute`], then a rounding increment of
3855 /// `30` would result in rounding in increments of a half hour. That is,
3856 /// the only minute value that could result would be `0` or `30`.
3857 ///
3858 /// # Errors
3859 ///
3860 /// When the smallest unit is less than days, the rounding increment must
3861 /// divide evenly into the next highest unit after the smallest unit
3862 /// configured (and must not be equivalent to it). For example, if the
3863 /// smallest unit is [`Unit::Nanosecond`], then *some* of the valid values
3864 /// for the rounding increment are `1`, `2`, `4`, `5`, `100` and `500`.
3865 /// Namely, any integer that divides evenly into `1,000` nanoseconds since
3866 /// there are `1,000` nanoseconds in the next highest unit (microseconds).
3867 ///
3868 /// The error will occur when computing the span, and not when setting
3869 /// the increment here.
3870 ///
3871 /// # Example
3872 ///
3873 /// This shows how to round the span between two zoned datetimes to the
3874 /// nearest 5 minute increment.
3875 ///
3876 /// ```
3877 /// use jiff::{RoundMode, ToSpan, Unit, Zoned, ZonedDifference};
3878 ///
3879 /// let zdt1 = "2024-03-15 08:19[America/New_York]".parse::<Zoned>()?;
3880 /// let zdt2 = "2024-03-15 12:52[America/New_York]".parse::<Zoned>()?;
3881 /// let span = zdt1.until(
3882 /// ZonedDifference::new(&zdt2)
3883 /// .smallest(Unit::Minute)
3884 /// .increment(5)
3885 /// .mode(RoundMode::HalfExpand),
3886 /// )?;
3887 /// assert_eq!(format!("{span:#}"), "4h 35m");
3888 ///
3889 /// # Ok::<(), Box<dyn std::error::Error>>(())
3890 /// ```
3891 #[inline]
3892 pub fn increment(self, increment: i64) -> ZonedDifference<'a> {
3893 ZonedDifference { round: self.round.increment(increment), ..self }
3894 }
3895
3896 /// Returns true if and only if this configuration could change the span
3897 /// via rounding.
3898 #[inline]
3899 fn rounding_may_change_span(&self) -> bool {
3900 self.round.rounding_may_change_span_ignore_largest()
3901 }
3902
3903 /// Returns the span of time from `dt1` to the datetime in this
3904 /// configuration. The biggest units allowed are determined by the
3905 /// `smallest` and `largest` settings, but defaults to `Unit::Day`.
3906 #[inline]
3907 fn until_with_largest_unit(&self, zdt1: &Zoned) -> Result<Span, Error> {
3908 let zdt2 = self.zoned;
3909
3910 let sign = t::sign(zdt2, zdt1);
3911 if sign == C(0) {
3912 return Ok(Span::new());
3913 }
3914
3915 let largest = self
3916 .round
3917 .get_largest()
3918 .unwrap_or_else(|| self.round.get_smallest().max(Unit::Hour));
3919 if largest < Unit::Day {
3920 return zdt1.timestamp().until((largest, zdt2.timestamp()));
3921 }
3922 if zdt1.time_zone() != zdt2.time_zone() {
3923 return Err(err!(
3924 "computing the span between zoned datetimes, with \
3925 {largest} units, requires that the time zones are \
3926 equivalent, but {zdt1} and {zdt2} have distinct \
3927 time zones",
3928 largest = largest.singular(),
3929 ));
3930 }
3931 let tz = zdt1.time_zone();
3932
3933 let (dt1, mut dt2) = (zdt1.datetime(), zdt2.datetime());
3934
3935 let mut day_correct: t::SpanDays = C(0).rinto();
3936 if -sign == dt1.time().until_nanoseconds(dt2.time()).signum() {
3937 day_correct += C(1);
3938 }
3939
3940 let mut mid = dt2
3941 .date()
3942 .checked_add(Span::new().days_ranged(day_correct * -sign))
3943 .with_context(|| {
3944 err!(
3945 "failed to add {days} days to date in {dt2}",
3946 days = day_correct * -sign,
3947 )
3948 })?
3949 .to_datetime(dt1.time());
3950 let mut zmid: Zoned = mid.to_zoned(tz.clone()).with_context(|| {
3951 err!(
3952 "failed to convert intermediate datetime {mid} \
3953 to zoned timestamp in time zone {tz}",
3954 tz = tz.diagnostic_name(),
3955 )
3956 })?;
3957 if t::sign(zdt2, &zmid) == -sign {
3958 if sign == C(-1) {
3959 panic!("this should be an error");
3960 }
3961 day_correct += C(1);
3962 mid = dt2
3963 .date()
3964 .checked_add(Span::new().days_ranged(day_correct * -sign))
3965 .with_context(|| {
3966 err!(
3967 "failed to add {days} days to date in {dt2}",
3968 days = day_correct * -sign,
3969 )
3970 })?
3971 .to_datetime(dt1.time());
3972 zmid = mid.to_zoned(tz.clone()).with_context(|| {
3973 err!(
3974 "failed to convert intermediate datetime {mid} \
3975 to zoned timestamp in time zone {tz}",
3976 tz = tz.diagnostic_name(),
3977 )
3978 })?;
3979 if t::sign(zdt2, &zmid) == -sign {
3980 panic!("this should be an error too");
3981 }
3982 }
3983 let remainder_nano = zdt2.timestamp().as_nanosecond_ranged()
3984 - zmid.timestamp().as_nanosecond_ranged();
3985 dt2 = mid;
3986
3987 let date_span = dt1.date().until((largest, dt2.date()))?;
3988 Ok(Span::from_invariant_nanoseconds(Unit::Hour, remainder_nano)
3989 .expect("difference between time always fits in span")
3990 .years_ranged(date_span.get_years_ranged())
3991 .months_ranged(date_span.get_months_ranged())
3992 .weeks_ranged(date_span.get_weeks_ranged())
3993 .days_ranged(date_span.get_days_ranged()))
3994 }
3995}
3996
3997impl<'a> From<&'a Zoned> for ZonedDifference<'a> {
3998 #[inline]
3999 fn from(zdt: &'a Zoned) -> ZonedDifference<'a> {
4000 ZonedDifference::new(zdt)
4001 }
4002}
4003
4004impl<'a> From<(Unit, &'a Zoned)> for ZonedDifference<'a> {
4005 #[inline]
4006 fn from((largest, zdt): (Unit, &'a Zoned)) -> ZonedDifference<'a> {
4007 ZonedDifference::new(zdt).largest(largest)
4008 }
4009}
4010
4011/// Options for [`Zoned::round`].
4012///
4013/// This type provides a way to configure the rounding of a zoned datetime. In
4014/// particular, `Zoned::round` accepts anything that implements the
4015/// `Into<ZonedRound>` trait. There are some trait implementations that
4016/// therefore make calling `Zoned::round` in some common cases more
4017/// ergonomic:
4018///
4019/// * `From<Unit> for ZonedRound` will construct a rounding
4020/// configuration that rounds to the unit given. Specifically,
4021/// `ZonedRound::new().smallest(unit)`.
4022/// * `From<(Unit, i64)> for ZonedRound` is like the one above, but also
4023/// specifies the rounding increment for [`ZonedRound::increment`].
4024///
4025/// Note that in the default configuration, no rounding occurs.
4026///
4027/// # Example
4028///
4029/// This example shows how to round a zoned datetime to the nearest second:
4030///
4031/// ```
4032/// use jiff::{civil::date, Unit, Zoned};
4033///
4034/// let zdt: Zoned = "2024-06-20 16:24:59.5[America/New_York]".parse()?;
4035/// assert_eq!(
4036/// zdt.round(Unit::Second)?,
4037/// // The second rounds up and causes minutes to increase.
4038/// date(2024, 6, 20).at(16, 25, 0, 0).in_tz("America/New_York")?,
4039/// );
4040///
4041/// # Ok::<(), Box<dyn std::error::Error>>(())
4042/// ```
4043///
4044/// The above makes use of the fact that `Unit` implements
4045/// `Into<ZonedRound>`. If you want to change the rounding mode to, say,
4046/// truncation, then you'll need to construct a `ZonedRound` explicitly
4047/// since there are no convenience `Into` trait implementations for
4048/// [`RoundMode`].
4049///
4050/// ```
4051/// use jiff::{civil::date, RoundMode, Unit, Zoned, ZonedRound};
4052///
4053/// let zdt: Zoned = "2024-06-20 16:24:59.5[America/New_York]".parse()?;
4054/// assert_eq!(
4055/// zdt.round(
4056/// ZonedRound::new().smallest(Unit::Second).mode(RoundMode::Trunc),
4057/// )?,
4058/// // The second just gets truncated as if it wasn't there.
4059/// date(2024, 6, 20).at(16, 24, 59, 0).in_tz("America/New_York")?,
4060/// );
4061///
4062/// # Ok::<(), Box<dyn std::error::Error>>(())
4063/// ```
4064#[derive(Clone, Copy, Debug)]
4065pub struct ZonedRound {
4066 round: DateTimeRound,
4067}
4068
4069impl ZonedRound {
4070 /// Create a new default configuration for rounding a [`Zoned`].
4071 #[inline]
4072 pub fn new() -> ZonedRound {
4073 ZonedRound { round: DateTimeRound::new() }
4074 }
4075
4076 /// Set the smallest units allowed in the zoned datetime returned after
4077 /// rounding.
4078 ///
4079 /// Any units below the smallest configured unit will be used, along
4080 /// with the rounding increment and rounding mode, to determine
4081 /// the value of the smallest unit. For example, when rounding
4082 /// `2024-06-20T03:25:30[America/New_York]` to the nearest minute, the `30`
4083 /// second unit will result in rounding the minute unit of `25` up to `26`
4084 /// and zeroing out everything below minutes.
4085 ///
4086 /// This defaults to [`Unit::Nanosecond`].
4087 ///
4088 /// # Errors
4089 ///
4090 /// The smallest units must be no greater than [`Unit::Day`]. And when the
4091 /// smallest unit is `Unit::Day`, the rounding increment must be equal to
4092 /// `1`. Otherwise an error will be returned from [`Zoned::round`].
4093 ///
4094 /// # Example
4095 ///
4096 /// ```
4097 /// use jiff::{civil::date, Unit, ZonedRound};
4098 ///
4099 /// let zdt = date(2024, 6, 20).at(3, 25, 30, 0).in_tz("America/New_York")?;
4100 /// assert_eq!(
4101 /// zdt.round(ZonedRound::new().smallest(Unit::Minute))?,
4102 /// date(2024, 6, 20).at(3, 26, 0, 0).in_tz("America/New_York")?,
4103 /// );
4104 /// // Or, utilize the `From<Unit> for ZonedRound` impl:
4105 /// assert_eq!(
4106 /// zdt.round(Unit::Minute)?,
4107 /// date(2024, 6, 20).at(3, 26, 0, 0).in_tz("America/New_York")?,
4108 /// );
4109 ///
4110 /// # Ok::<(), Box<dyn std::error::Error>>(())
4111 /// ```
4112 #[inline]
4113 pub fn smallest(self, unit: Unit) -> ZonedRound {
4114 ZonedRound { round: self.round.smallest(unit) }
4115 }
4116
4117 /// Set the rounding mode.
4118 ///
4119 /// This defaults to [`RoundMode::HalfExpand`], which rounds away from
4120 /// zero. It matches the kind of rounding you might have been taught in
4121 /// school.
4122 ///
4123 /// # Example
4124 ///
4125 /// This shows how to always round zoned datetimes up towards positive
4126 /// infinity.
4127 ///
4128 /// ```
4129 /// use jiff::{civil::date, RoundMode, Unit, Zoned, ZonedRound};
4130 ///
4131 /// let zdt: Zoned = "2024-06-20 03:25:01[America/New_York]".parse()?;
4132 /// assert_eq!(
4133 /// zdt.round(
4134 /// ZonedRound::new()
4135 /// .smallest(Unit::Minute)
4136 /// .mode(RoundMode::Ceil),
4137 /// )?,
4138 /// date(2024, 6, 20).at(3, 26, 0, 0).in_tz("America/New_York")?,
4139 /// );
4140 ///
4141 /// # Ok::<(), Box<dyn std::error::Error>>(())
4142 /// ```
4143 #[inline]
4144 pub fn mode(self, mode: RoundMode) -> ZonedRound {
4145 ZonedRound { round: self.round.mode(mode) }
4146 }
4147
4148 /// Set the rounding increment for the smallest unit.
4149 ///
4150 /// The default value is `1`. Other values permit rounding the smallest
4151 /// unit to the nearest integer increment specified. For example, if the
4152 /// smallest unit is set to [`Unit::Minute`], then a rounding increment of
4153 /// `30` would result in rounding in increments of a half hour. That is,
4154 /// the only minute value that could result would be `0` or `30`.
4155 ///
4156 /// # Errors
4157 ///
4158 /// When the smallest unit is `Unit::Day`, then the rounding increment must
4159 /// be `1` or else [`Zoned::round`] will return an error.
4160 ///
4161 /// For other units, the rounding increment must divide evenly into the
4162 /// next highest unit above the smallest unit set. The rounding increment
4163 /// must also not be equal to the next highest unit. For example, if the
4164 /// smallest unit is [`Unit::Nanosecond`], then *some* of the valid values
4165 /// for the rounding increment are `1`, `2`, `4`, `5`, `100` and `500`.
4166 /// Namely, any integer that divides evenly into `1,000` nanoseconds since
4167 /// there are `1,000` nanoseconds in the next highest unit (microseconds).
4168 ///
4169 /// # Example
4170 ///
4171 /// This example shows how to round a zoned datetime to the nearest 10
4172 /// minute increment.
4173 ///
4174 /// ```
4175 /// use jiff::{civil::date, RoundMode, Unit, Zoned, ZonedRound};
4176 ///
4177 /// let zdt: Zoned = "2024-06-20 03:24:59[America/New_York]".parse()?;
4178 /// assert_eq!(
4179 /// zdt.round((Unit::Minute, 10))?,
4180 /// date(2024, 6, 20).at(3, 20, 0, 0).in_tz("America/New_York")?,
4181 /// );
4182 ///
4183 /// # Ok::<(), Box<dyn std::error::Error>>(())
4184 /// ```
4185 #[inline]
4186 pub fn increment(self, increment: i64) -> ZonedRound {
4187 ZonedRound { round: self.round.increment(increment) }
4188 }
4189
4190 /// Does the actual rounding.
4191 ///
4192 /// Most of the work is farmed out to civil datetime rounding.
4193 pub(crate) fn round(&self, zdt: &Zoned) -> Result<Zoned, Error> {
4194 let start = zdt.datetime();
4195 let day_length = day_length(start, zdt.time_zone().clone())
4196 .with_context(|| err!("failed to find length of day for {zdt}"))?;
4197 let end = self.round.round(day_length, start)?;
4198 // Like in the ZonedWith API, in order to avoid small changes to clock
4199 // time hitting a 1 hour disambiguation shift, we use offset conflict
4200 // resolution to do our best to "prefer" the offset we already have.
4201 let amb = OffsetConflict::PreferOffset.resolve(
4202 end,
4203 zdt.offset(),
4204 zdt.time_zone().clone(),
4205 )?;
4206 amb.compatible()
4207 }
4208}
4209
4210impl Default for ZonedRound {
4211 #[inline]
4212 fn default() -> ZonedRound {
4213 ZonedRound::new()
4214 }
4215}
4216
4217impl From<Unit> for ZonedRound {
4218 #[inline]
4219 fn from(unit: Unit) -> ZonedRound {
4220 ZonedRound::default().smallest(unit)
4221 }
4222}
4223
4224impl From<(Unit, i64)> for ZonedRound {
4225 #[inline]
4226 fn from((unit, increment): (Unit, i64)) -> ZonedRound {
4227 ZonedRound::from(unit).increment(increment)
4228 }
4229}
4230
4231/// A builder for setting the fields on a [`Zoned`].
4232///
4233/// This builder is constructed via [`Zoned::with`].
4234///
4235/// # Example
4236///
4237/// The builder ensures one can chain together the individual components of a
4238/// zoned datetime without it failing at an intermediate step. For example,
4239/// if you had a date of `2024-10-31T00:00:00[America/New_York]` and wanted
4240/// to change both the day and the month, and each setting was validated
4241/// independent of the other, you would need to be careful to set the day first
4242/// and then the month. In some cases, you would need to set the month first
4243/// and then the day!
4244///
4245/// But with the builder, you can set values in any order:
4246///
4247/// ```
4248/// use jiff::civil::date;
4249///
4250/// let zdt1 = date(2024, 10, 31).at(0, 0, 0, 0).in_tz("America/New_York")?;
4251/// let zdt2 = zdt1.with().month(11).day(30).build()?;
4252/// assert_eq!(
4253/// zdt2,
4254/// date(2024, 11, 30).at(0, 0, 0, 0).in_tz("America/New_York")?,
4255/// );
4256///
4257/// let zdt1 = date(2024, 4, 30).at(0, 0, 0, 0).in_tz("America/New_York")?;
4258/// let zdt2 = zdt1.with().day(31).month(7).build()?;
4259/// assert_eq!(
4260/// zdt2,
4261/// date(2024, 7, 31).at(0, 0, 0, 0).in_tz("America/New_York")?,
4262/// );
4263///
4264/// # Ok::<(), Box<dyn std::error::Error>>(())
4265/// ```
4266#[derive(Clone, Debug)]
4267pub struct ZonedWith {
4268 original: Zoned,
4269 datetime_with: DateTimeWith,
4270 offset: Option<Offset>,
4271 disambiguation: Disambiguation,
4272 offset_conflict: OffsetConflict,
4273}
4274
4275impl ZonedWith {
4276 #[inline]
4277 fn new(original: Zoned) -> ZonedWith {
4278 let datetime_with = original.datetime().with();
4279 ZonedWith {
4280 original,
4281 datetime_with,
4282 offset: None,
4283 disambiguation: Disambiguation::default(),
4284 offset_conflict: OffsetConflict::PreferOffset,
4285 }
4286 }
4287
4288 /// Create a new `Zoned` from the fields set on this configuration.
4289 ///
4290 /// An error occurs when the fields combine to an invalid zoned datetime.
4291 ///
4292 /// For any fields not set on this configuration, the values are taken from
4293 /// the [`Zoned`] that originally created this configuration. When no
4294 /// values are set, this routine is guaranteed to succeed and will always
4295 /// return the original zoned datetime without modification.
4296 ///
4297 /// # Example
4298 ///
4299 /// This creates a zoned datetime corresponding to the last day in the year
4300 /// at noon:
4301 ///
4302 /// ```
4303 /// use jiff::civil::date;
4304 ///
4305 /// let zdt = date(2023, 1, 1).at(12, 0, 0, 0).in_tz("America/New_York")?;
4306 /// assert_eq!(
4307 /// zdt.with().day_of_year_no_leap(365).build()?,
4308 /// date(2023, 12, 31).at(12, 0, 0, 0).in_tz("America/New_York")?,
4309 /// );
4310 ///
4311 /// // It also works with leap years for the same input:
4312 /// let zdt = date(2024, 1, 1).at(12, 0, 0, 0).in_tz("America/New_York")?;
4313 /// assert_eq!(
4314 /// zdt.with().day_of_year_no_leap(365).build()?,
4315 /// date(2024, 12, 31).at(12, 0, 0, 0).in_tz("America/New_York")?,
4316 /// );
4317 ///
4318 /// # Ok::<(), Box<dyn std::error::Error>>(())
4319 /// ```
4320 ///
4321 /// # Example: error for invalid zoned datetime
4322 ///
4323 /// If the fields combine to form an invalid datetime, then an error is
4324 /// returned:
4325 ///
4326 /// ```
4327 /// use jiff::civil::date;
4328 ///
4329 /// let zdt = date(2024, 11, 30).at(15, 30, 0, 0).in_tz("America/New_York")?;
4330 /// assert!(zdt.with().day(31).build().is_err());
4331 ///
4332 /// let zdt = date(2024, 2, 29).at(15, 30, 0, 0).in_tz("America/New_York")?;
4333 /// assert!(zdt.with().year(2023).build().is_err());
4334 ///
4335 /// # Ok::<(), Box<dyn std::error::Error>>(())
4336 /// ```
4337 #[inline]
4338 pub fn build(self) -> Result<Zoned, Error> {
4339 let dt = self.datetime_with.build()?;
4340 let (_, _, offset, time_zone) = self.original.into_parts();
4341 let offset = self.offset.unwrap_or(offset);
4342 let ambiguous = self.offset_conflict.resolve(dt, offset, time_zone)?;
4343 ambiguous.disambiguate(self.disambiguation)
4344 }
4345
4346 /// Set the year, month and day fields via the `Date` given.
4347 ///
4348 /// This overrides any previous year, month or day settings.
4349 ///
4350 /// # Example
4351 ///
4352 /// This shows how to create a new zoned datetime with a different date:
4353 ///
4354 /// ```
4355 /// use jiff::civil::date;
4356 ///
4357 /// let zdt1 = date(2005, 11, 5).at(15, 30, 0, 0).in_tz("America/New_York")?;
4358 /// let zdt2 = zdt1.with().date(date(2017, 10, 31)).build()?;
4359 /// // The date changes but the time remains the same.
4360 /// assert_eq!(
4361 /// zdt2,
4362 /// date(2017, 10, 31).at(15, 30, 0, 0).in_tz("America/New_York")?,
4363 /// );
4364 ///
4365 /// # Ok::<(), Box<dyn std::error::Error>>(())
4366 /// ```
4367 #[inline]
4368 pub fn date(self, date: Date) -> ZonedWith {
4369 ZonedWith { datetime_with: self.datetime_with.date(date), ..self }
4370 }
4371
4372 /// Set the hour, minute, second, millisecond, microsecond and nanosecond
4373 /// fields via the `Time` given.
4374 ///
4375 /// This overrides any previous hour, minute, second, millisecond,
4376 /// microsecond, nanosecond or subsecond nanosecond settings.
4377 ///
4378 /// # Example
4379 ///
4380 /// This shows how to create a new zoned datetime with a different time:
4381 ///
4382 /// ```
4383 /// use jiff::civil::{date, time};
4384 ///
4385 /// let zdt1 = date(2005, 11, 5).at(15, 30, 0, 0).in_tz("America/New_York")?;
4386 /// let zdt2 = zdt1.with().time(time(23, 59, 59, 123_456_789)).build()?;
4387 /// // The time changes but the date remains the same.
4388 /// assert_eq!(
4389 /// zdt2,
4390 /// date(2005, 11, 5)
4391 /// .at(23, 59, 59, 123_456_789)
4392 /// .in_tz("America/New_York")?,
4393 /// );
4394 ///
4395 /// # Ok::<(), Box<dyn std::error::Error>>(())
4396 /// ```
4397 #[inline]
4398 pub fn time(self, time: Time) -> ZonedWith {
4399 ZonedWith { datetime_with: self.datetime_with.time(time), ..self }
4400 }
4401
4402 /// Set the year field on a [`Zoned`].
4403 ///
4404 /// One can access this value via [`Zoned::year`].
4405 ///
4406 /// This overrides any previous year settings.
4407 ///
4408 /// # Errors
4409 ///
4410 /// This returns an error when [`ZonedWith::build`] is called if the
4411 /// given year is outside the range `-9999..=9999`. This can also return an
4412 /// error if the resulting date is otherwise invalid.
4413 ///
4414 /// # Example
4415 ///
4416 /// This shows how to create a new zoned datetime with a different year:
4417 ///
4418 /// ```
4419 /// use jiff::civil::date;
4420 ///
4421 /// let zdt1 = date(2005, 11, 5).at(15, 30, 0, 0).in_tz("America/New_York")?;
4422 /// assert_eq!(zdt1.year(), 2005);
4423 /// let zdt2 = zdt1.with().year(2007).build()?;
4424 /// assert_eq!(zdt2.year(), 2007);
4425 ///
4426 /// # Ok::<(), Box<dyn std::error::Error>>(())
4427 /// ```
4428 ///
4429 /// # Example: only changing the year can fail
4430 ///
4431 /// For example, while `2024-02-29T01:30:00[America/New_York]` is valid,
4432 /// `2023-02-29T01:30:00[America/New_York]` is not:
4433 ///
4434 /// ```
4435 /// use jiff::civil::date;
4436 ///
4437 /// let zdt = date(2024, 2, 29).at(1, 30, 0, 0).in_tz("America/New_York")?;
4438 /// assert!(zdt.with().year(2023).build().is_err());
4439 ///
4440 /// # Ok::<(), Box<dyn std::error::Error>>(())
4441 /// ```
4442 #[inline]
4443 pub fn year(self, year: i16) -> ZonedWith {
4444 ZonedWith { datetime_with: self.datetime_with.year(year), ..self }
4445 }
4446
4447 /// Set the year of a zoned datetime via its era and its non-negative
4448 /// numeric component.
4449 ///
4450 /// One can access this value via [`Zoned::era_year`].
4451 ///
4452 /// # Errors
4453 ///
4454 /// This returns an error when [`ZonedWith::build`] is called if the
4455 /// year is outside the range for the era specified. For [`Era::BCE`], the
4456 /// range is `1..=10000`. For [`Era::CE`], the range is `1..=9999`.
4457 ///
4458 /// # Example
4459 ///
4460 /// This shows that `CE` years are equivalent to the years used by this
4461 /// crate:
4462 ///
4463 /// ```
4464 /// use jiff::civil::{Era, date};
4465 ///
4466 /// let zdt1 = date(2005, 11, 5).at(8, 0, 0, 0).in_tz("America/New_York")?;
4467 /// assert_eq!(zdt1.year(), 2005);
4468 /// let zdt2 = zdt1.with().era_year(2007, Era::CE).build()?;
4469 /// assert_eq!(zdt2.year(), 2007);
4470 ///
4471 /// // CE years are always positive and can be at most 9999:
4472 /// assert!(zdt1.with().era_year(-5, Era::CE).build().is_err());
4473 /// assert!(zdt1.with().era_year(10_000, Era::CE).build().is_err());
4474 ///
4475 /// # Ok::<(), Box<dyn std::error::Error>>(())
4476 /// ```
4477 ///
4478 /// But `BCE` years always correspond to years less than or equal to `0`
4479 /// in this crate:
4480 ///
4481 /// ```
4482 /// use jiff::civil::{Era, date};
4483 ///
4484 /// let zdt1 = date(-27, 7, 1).at(8, 22, 30, 0).in_tz("America/New_York")?;
4485 /// assert_eq!(zdt1.year(), -27);
4486 /// assert_eq!(zdt1.era_year(), (28, Era::BCE));
4487 ///
4488 /// let zdt2 = zdt1.with().era_year(509, Era::BCE).build()?;
4489 /// assert_eq!(zdt2.year(), -508);
4490 /// assert_eq!(zdt2.era_year(), (509, Era::BCE));
4491 ///
4492 /// let zdt2 = zdt1.with().era_year(10_000, Era::BCE).build()?;
4493 /// assert_eq!(zdt2.year(), -9_999);
4494 /// assert_eq!(zdt2.era_year(), (10_000, Era::BCE));
4495 ///
4496 /// // BCE years are always positive and can be at most 10000:
4497 /// assert!(zdt1.with().era_year(-5, Era::BCE).build().is_err());
4498 /// assert!(zdt1.with().era_year(10_001, Era::BCE).build().is_err());
4499 ///
4500 /// # Ok::<(), Box<dyn std::error::Error>>(())
4501 /// ```
4502 ///
4503 /// # Example: overrides `ZonedWith::year`
4504 ///
4505 /// Setting this option will override any previous `ZonedWith::year`
4506 /// option:
4507 ///
4508 /// ```
4509 /// use jiff::civil::{Era, date};
4510 ///
4511 /// let zdt1 = date(2024, 7, 2).at(10, 27, 10, 123).in_tz("America/New_York")?;
4512 /// let zdt2 = zdt1.with().year(2000).era_year(1900, Era::CE).build()?;
4513 /// assert_eq!(
4514 /// zdt2,
4515 /// date(1900, 7, 2).at(10, 27, 10, 123).in_tz("America/New_York")?,
4516 /// );
4517 ///
4518 /// # Ok::<(), Box<dyn std::error::Error>>(())
4519 /// ```
4520 ///
4521 /// Similarly, `ZonedWith::year` will override any previous call to
4522 /// `ZonedWith::era_year`:
4523 ///
4524 /// ```
4525 /// use jiff::civil::{Era, date};
4526 ///
4527 /// let zdt1 = date(2024, 7, 2).at(19, 0, 1, 1).in_tz("America/New_York")?;
4528 /// let zdt2 = zdt1.with().era_year(1900, Era::CE).year(2000).build()?;
4529 /// assert_eq!(
4530 /// zdt2,
4531 /// date(2000, 7, 2).at(19, 0, 1, 1).in_tz("America/New_York")?,
4532 /// );
4533 ///
4534 /// # Ok::<(), Box<dyn std::error::Error>>(())
4535 /// ```
4536 #[inline]
4537 pub fn era_year(self, year: i16, era: Era) -> ZonedWith {
4538 ZonedWith {
4539 datetime_with: self.datetime_with.era_year(year, era),
4540 ..self
4541 }
4542 }
4543
4544 /// Set the month field on a [`Zoned`].
4545 ///
4546 /// One can access this value via [`Zoned::month`].
4547 ///
4548 /// This overrides any previous month settings.
4549 ///
4550 /// # Errors
4551 ///
4552 /// This returns an error when [`ZonedWith::build`] is called if the
4553 /// given month is outside the range `1..=12`. This can also return an
4554 /// error if the resulting date is otherwise invalid.
4555 ///
4556 /// # Example
4557 ///
4558 /// This shows how to create a new zoned datetime with a different month:
4559 ///
4560 /// ```
4561 /// use jiff::civil::date;
4562 ///
4563 /// let zdt1 = date(2005, 11, 5)
4564 /// .at(18, 3, 59, 123_456_789)
4565 /// .in_tz("America/New_York")?;
4566 /// assert_eq!(zdt1.month(), 11);
4567 ///
4568 /// let zdt2 = zdt1.with().month(6).build()?;
4569 /// assert_eq!(zdt2.month(), 6);
4570 ///
4571 /// # Ok::<(), Box<dyn std::error::Error>>(())
4572 /// ```
4573 ///
4574 /// # Example: only changing the month can fail
4575 ///
4576 /// For example, while `2024-10-31T00:00:00[America/New_York]` is valid,
4577 /// `2024-11-31T00:00:00[America/New_York]` is not:
4578 ///
4579 /// ```
4580 /// use jiff::civil::date;
4581 ///
4582 /// let zdt = date(2024, 10, 31).at(0, 0, 0, 0).in_tz("America/New_York")?;
4583 /// assert!(zdt.with().month(11).build().is_err());
4584 ///
4585 /// # Ok::<(), Box<dyn std::error::Error>>(())
4586 /// ```
4587 #[inline]
4588 pub fn month(self, month: i8) -> ZonedWith {
4589 ZonedWith { datetime_with: self.datetime_with.month(month), ..self }
4590 }
4591
4592 /// Set the day field on a [`Zoned`].
4593 ///
4594 /// One can access this value via [`Zoned::day`].
4595 ///
4596 /// This overrides any previous day settings.
4597 ///
4598 /// # Errors
4599 ///
4600 /// This returns an error when [`ZonedWith::build`] is called if the
4601 /// given given day is outside of allowable days for the corresponding year
4602 /// and month fields.
4603 ///
4604 /// # Example
4605 ///
4606 /// This shows some examples of setting the day, including a leap day:
4607 ///
4608 /// ```
4609 /// use jiff::civil::date;
4610 ///
4611 /// let zdt1 = date(2024, 2, 5).at(21, 59, 1, 999).in_tz("America/New_York")?;
4612 /// assert_eq!(zdt1.day(), 5);
4613 /// let zdt2 = zdt1.with().day(10).build()?;
4614 /// assert_eq!(zdt2.day(), 10);
4615 /// let zdt3 = zdt1.with().day(29).build()?;
4616 /// assert_eq!(zdt3.day(), 29);
4617 ///
4618 /// # Ok::<(), Box<dyn std::error::Error>>(())
4619 /// ```
4620 ///
4621 /// # Example: changing only the day can fail
4622 ///
4623 /// This shows some examples that will fail:
4624 ///
4625 /// ```
4626 /// use jiff::civil::date;
4627 ///
4628 /// let zdt1 = date(2023, 2, 5)
4629 /// .at(22, 58, 58, 9_999)
4630 /// .in_tz("America/New_York")?;
4631 /// // 2023 is not a leap year
4632 /// assert!(zdt1.with().day(29).build().is_err());
4633 ///
4634 /// // September has 30 days, not 31.
4635 /// let zdt1 = date(2023, 9, 5).in_tz("America/New_York")?;
4636 /// assert!(zdt1.with().day(31).build().is_err());
4637 ///
4638 /// # Ok::<(), Box<dyn std::error::Error>>(())
4639 /// ```
4640 #[inline]
4641 pub fn day(self, day: i8) -> ZonedWith {
4642 ZonedWith { datetime_with: self.datetime_with.day(day), ..self }
4643 }
4644
4645 /// Set the day field on a [`Zoned`] via the ordinal number of a day
4646 /// within a year.
4647 ///
4648 /// When used, any settings for month are ignored since the month is
4649 /// determined by the day of the year.
4650 ///
4651 /// The valid values for `day` are `1..=366`. Note though that `366` is
4652 /// only valid for leap years.
4653 ///
4654 /// This overrides any previous day settings.
4655 ///
4656 /// # Errors
4657 ///
4658 /// This returns an error when [`ZonedWith::build`] is called if the
4659 /// given day is outside the allowed range of `1..=366`, or when a value of
4660 /// `366` is given for a non-leap year.
4661 ///
4662 /// # Example
4663 ///
4664 /// This demonstrates that if a year is a leap year, then `60` corresponds
4665 /// to February 29:
4666 ///
4667 /// ```
4668 /// use jiff::civil::date;
4669 ///
4670 /// let zdt = date(2024, 1, 1)
4671 /// .at(23, 59, 59, 999_999_999)
4672 /// .in_tz("America/New_York")?;
4673 /// assert_eq!(
4674 /// zdt.with().day_of_year(60).build()?,
4675 /// date(2024, 2, 29)
4676 /// .at(23, 59, 59, 999_999_999)
4677 /// .in_tz("America/New_York")?,
4678 /// );
4679 ///
4680 /// # Ok::<(), Box<dyn std::error::Error>>(())
4681 /// ```
4682 ///
4683 /// But for non-leap years, day 60 is March 1:
4684 ///
4685 /// ```
4686 /// use jiff::civil::date;
4687 ///
4688 /// let zdt = date(2023, 1, 1)
4689 /// .at(23, 59, 59, 999_999_999)
4690 /// .in_tz("America/New_York")?;
4691 /// assert_eq!(
4692 /// zdt.with().day_of_year(60).build()?,
4693 /// date(2023, 3, 1)
4694 /// .at(23, 59, 59, 999_999_999)
4695 /// .in_tz("America/New_York")?,
4696 /// );
4697 ///
4698 /// # Ok::<(), Box<dyn std::error::Error>>(())
4699 /// ```
4700 ///
4701 /// And using `366` for a non-leap year will result in an error, since
4702 /// non-leap years only have 365 days:
4703 ///
4704 /// ```
4705 /// use jiff::civil::date;
4706 ///
4707 /// let zdt = date(2023, 1, 1).at(0, 0, 0, 0).in_tz("America/New_York")?;
4708 /// assert!(zdt.with().day_of_year(366).build().is_err());
4709 /// // The maximal year is not a leap year, so it returns an error too.
4710 /// let zdt = date(9999, 1, 1).at(0, 0, 0, 0).in_tz("America/New_York")?;
4711 /// assert!(zdt.with().day_of_year(366).build().is_err());
4712 ///
4713 /// # Ok::<(), Box<dyn std::error::Error>>(())
4714 /// ```
4715 #[inline]
4716 pub fn day_of_year(self, day: i16) -> ZonedWith {
4717 ZonedWith {
4718 datetime_with: self.datetime_with.day_of_year(day),
4719 ..self
4720 }
4721 }
4722
4723 /// Set the day field on a [`Zoned`] via the ordinal number of a day
4724 /// within a year, but ignoring leap years.
4725 ///
4726 /// When used, any settings for month are ignored since the month is
4727 /// determined by the day of the year.
4728 ///
4729 /// The valid values for `day` are `1..=365`. The value `365` always
4730 /// corresponds to the last day of the year, even for leap years. It is
4731 /// impossible for this routine to return a zoned datetime corresponding to
4732 /// February 29. (Unless there is a relevant time zone transition that
4733 /// provokes disambiguation that shifts the datetime into February 29.)
4734 ///
4735 /// This overrides any previous day settings.
4736 ///
4737 /// # Errors
4738 ///
4739 /// This returns an error when [`ZonedWith::build`] is called if the
4740 /// given day is outside the allowed range of `1..=365`.
4741 ///
4742 /// # Example
4743 ///
4744 /// This demonstrates that `60` corresponds to March 1, regardless of
4745 /// whether the year is a leap year or not:
4746 ///
4747 /// ```
4748 /// use jiff::civil::date;
4749 ///
4750 /// let zdt = date(2023, 1, 1)
4751 /// .at(23, 59, 59, 999_999_999)
4752 /// .in_tz("America/New_York")?;
4753 /// assert_eq!(
4754 /// zdt.with().day_of_year_no_leap(60).build()?,
4755 /// date(2023, 3, 1)
4756 /// .at(23, 59, 59, 999_999_999)
4757 /// .in_tz("America/New_York")?,
4758 /// );
4759 ///
4760 /// let zdt = date(2024, 1, 1)
4761 /// .at(23, 59, 59, 999_999_999)
4762 /// .in_tz("America/New_York")?;
4763 /// assert_eq!(
4764 /// zdt.with().day_of_year_no_leap(60).build()?,
4765 /// date(2024, 3, 1)
4766 /// .at(23, 59, 59, 999_999_999)
4767 /// .in_tz("America/New_York")?,
4768 /// );
4769 ///
4770 /// # Ok::<(), Box<dyn std::error::Error>>(())
4771 /// ```
4772 ///
4773 /// And using `365` for any year will always yield the last day of the
4774 /// year:
4775 ///
4776 /// ```
4777 /// use jiff::civil::date;
4778 ///
4779 /// let zdt = date(2023, 1, 1)
4780 /// .at(23, 59, 59, 999_999_999)
4781 /// .in_tz("America/New_York")?;
4782 /// assert_eq!(
4783 /// zdt.with().day_of_year_no_leap(365).build()?,
4784 /// zdt.last_of_year()?,
4785 /// );
4786 ///
4787 /// let zdt = date(2024, 1, 1)
4788 /// .at(23, 59, 59, 999_999_999)
4789 /// .in_tz("America/New_York")?;
4790 /// assert_eq!(
4791 /// zdt.with().day_of_year_no_leap(365).build()?,
4792 /// zdt.last_of_year()?,
4793 /// );
4794 ///
4795 /// // Careful at the boundaries. The last day of the year isn't
4796 /// // representable with all time zones. For example:
4797 /// let zdt = date(9999, 1, 1)
4798 /// .at(23, 59, 59, 999_999_999)
4799 /// .in_tz("America/New_York")?;
4800 /// assert!(zdt.with().day_of_year_no_leap(365).build().is_err());
4801 /// // But with other time zones, it works okay:
4802 /// let zdt = date(9999, 1, 1)
4803 /// .at(23, 59, 59, 999_999_999)
4804 /// .to_zoned(jiff::tz::TimeZone::fixed(jiff::tz::Offset::MAX))?;
4805 /// assert_eq!(
4806 /// zdt.with().day_of_year_no_leap(365).build()?,
4807 /// zdt.last_of_year()?,
4808 /// );
4809 ///
4810 /// # Ok::<(), Box<dyn std::error::Error>>(())
4811 /// ```
4812 ///
4813 /// A value of `366` is out of bounds, even for leap years:
4814 ///
4815 /// ```
4816 /// use jiff::civil::date;
4817 ///
4818 /// let zdt = date(2024, 1, 1).at(5, 30, 0, 0).in_tz("America/New_York")?;
4819 /// assert!(zdt.with().day_of_year_no_leap(366).build().is_err());
4820 ///
4821 /// # Ok::<(), Box<dyn std::error::Error>>(())
4822 /// ```
4823 #[inline]
4824 pub fn day_of_year_no_leap(self, day: i16) -> ZonedWith {
4825 ZonedWith {
4826 datetime_with: self.datetime_with.day_of_year_no_leap(day),
4827 ..self
4828 }
4829 }
4830
4831 /// Set the hour field on a [`Zoned`].
4832 ///
4833 /// One can access this value via [`Zoned::hour`].
4834 ///
4835 /// This overrides any previous hour settings.
4836 ///
4837 /// # Errors
4838 ///
4839 /// This returns an error when [`ZonedWith::build`] is called if the
4840 /// given hour is outside the range `0..=23`.
4841 ///
4842 /// # Example
4843 ///
4844 /// ```
4845 /// use jiff::civil::time;
4846 ///
4847 /// let zdt1 = time(15, 21, 59, 0).on(2010, 6, 1).in_tz("America/New_York")?;
4848 /// assert_eq!(zdt1.hour(), 15);
4849 /// let zdt2 = zdt1.with().hour(3).build()?;
4850 /// assert_eq!(zdt2.hour(), 3);
4851 ///
4852 /// # Ok::<(), Box<dyn std::error::Error>>(())
4853 /// ```
4854 #[inline]
4855 pub fn hour(self, hour: i8) -> ZonedWith {
4856 ZonedWith { datetime_with: self.datetime_with.hour(hour), ..self }
4857 }
4858
4859 /// Set the minute field on a [`Zoned`].
4860 ///
4861 /// One can access this value via [`Zoned::minute`].
4862 ///
4863 /// This overrides any previous minute settings.
4864 ///
4865 /// # Errors
4866 ///
4867 /// This returns an error when [`ZonedWith::build`] is called if the
4868 /// given minute is outside the range `0..=59`.
4869 ///
4870 /// # Example
4871 ///
4872 /// ```
4873 /// use jiff::civil::time;
4874 ///
4875 /// let zdt1 = time(15, 21, 59, 0).on(2010, 6, 1).in_tz("America/New_York")?;
4876 /// assert_eq!(zdt1.minute(), 21);
4877 /// let zdt2 = zdt1.with().minute(3).build()?;
4878 /// assert_eq!(zdt2.minute(), 3);
4879 ///
4880 /// # Ok::<(), Box<dyn std::error::Error>>(())
4881 /// ```
4882 #[inline]
4883 pub fn minute(self, minute: i8) -> ZonedWith {
4884 ZonedWith { datetime_with: self.datetime_with.minute(minute), ..self }
4885 }
4886
4887 /// Set the second field on a [`Zoned`].
4888 ///
4889 /// One can access this value via [`Zoned::second`].
4890 ///
4891 /// This overrides any previous second settings.
4892 ///
4893 /// # Errors
4894 ///
4895 /// This returns an error when [`ZonedWith::build`] is called if the
4896 /// given second is outside the range `0..=59`.
4897 ///
4898 /// # Example
4899 ///
4900 /// ```
4901 /// use jiff::civil::time;
4902 ///
4903 /// let zdt1 = time(15, 21, 59, 0).on(2010, 6, 1).in_tz("America/New_York")?;
4904 /// assert_eq!(zdt1.second(), 59);
4905 /// let zdt2 = zdt1.with().second(3).build()?;
4906 /// assert_eq!(zdt2.second(), 3);
4907 ///
4908 /// # Ok::<(), Box<dyn std::error::Error>>(())
4909 /// ```
4910 #[inline]
4911 pub fn second(self, second: i8) -> ZonedWith {
4912 ZonedWith { datetime_with: self.datetime_with.second(second), ..self }
4913 }
4914
4915 /// Set the millisecond field on a [`Zoned`].
4916 ///
4917 /// One can access this value via [`Zoned::millisecond`].
4918 ///
4919 /// This overrides any previous millisecond settings.
4920 ///
4921 /// Note that this only sets the millisecond component. It does
4922 /// not change the microsecond or nanosecond components. To set
4923 /// the fractional second component to nanosecond precision, use
4924 /// [`ZonedWith::subsec_nanosecond`].
4925 ///
4926 /// # Errors
4927 ///
4928 /// This returns an error when [`ZonedWith::build`] is called if the
4929 /// given millisecond is outside the range `0..=999`, or if both this and
4930 /// [`ZonedWith::subsec_nanosecond`] are set.
4931 ///
4932 /// # Example
4933 ///
4934 /// This shows the relationship between [`Zoned::millisecond`] and
4935 /// [`Zoned::subsec_nanosecond`]:
4936 ///
4937 /// ```
4938 /// use jiff::civil::time;
4939 ///
4940 /// let zdt1 = time(15, 21, 35, 0).on(2010, 6, 1).in_tz("America/New_York")?;
4941 /// let zdt2 = zdt1.with().millisecond(123).build()?;
4942 /// assert_eq!(zdt2.subsec_nanosecond(), 123_000_000);
4943 ///
4944 /// # Ok::<(), Box<dyn std::error::Error>>(())
4945 /// ```
4946 #[inline]
4947 pub fn millisecond(self, millisecond: i16) -> ZonedWith {
4948 ZonedWith {
4949 datetime_with: self.datetime_with.millisecond(millisecond),
4950 ..self
4951 }
4952 }
4953
4954 /// Set the microsecond field on a [`Zoned`].
4955 ///
4956 /// One can access this value via [`Zoned::microsecond`].
4957 ///
4958 /// This overrides any previous microsecond settings.
4959 ///
4960 /// Note that this only sets the microsecond component. It does
4961 /// not change the millisecond or nanosecond components. To set
4962 /// the fractional second component to nanosecond precision, use
4963 /// [`ZonedWith::subsec_nanosecond`].
4964 ///
4965 /// # Errors
4966 ///
4967 /// This returns an error when [`ZonedWith::build`] is called if the
4968 /// given microsecond is outside the range `0..=999`, or if both this and
4969 /// [`ZonedWith::subsec_nanosecond`] are set.
4970 ///
4971 /// # Example
4972 ///
4973 /// This shows the relationship between [`Zoned::microsecond`] and
4974 /// [`Zoned::subsec_nanosecond`]:
4975 ///
4976 /// ```
4977 /// use jiff::civil::time;
4978 ///
4979 /// let zdt1 = time(15, 21, 35, 0).on(2010, 6, 1).in_tz("America/New_York")?;
4980 /// let zdt2 = zdt1.with().microsecond(123).build()?;
4981 /// assert_eq!(zdt2.subsec_nanosecond(), 123_000);
4982 ///
4983 /// # Ok::<(), Box<dyn std::error::Error>>(())
4984 /// ```
4985 #[inline]
4986 pub fn microsecond(self, microsecond: i16) -> ZonedWith {
4987 ZonedWith {
4988 datetime_with: self.datetime_with.microsecond(microsecond),
4989 ..self
4990 }
4991 }
4992
4993 /// Set the nanosecond field on a [`Zoned`].
4994 ///
4995 /// One can access this value via [`Zoned::nanosecond`].
4996 ///
4997 /// This overrides any previous nanosecond settings.
4998 ///
4999 /// Note that this only sets the nanosecond component. It does
5000 /// not change the millisecond or microsecond components. To set
5001 /// the fractional second component to nanosecond precision, use
5002 /// [`ZonedWith::subsec_nanosecond`].
5003 ///
5004 /// # Errors
5005 ///
5006 /// This returns an error when [`ZonedWith::build`] is called if the
5007 /// given nanosecond is outside the range `0..=999`, or if both this and
5008 /// [`ZonedWith::subsec_nanosecond`] are set.
5009 ///
5010 /// # Example
5011 ///
5012 /// This shows the relationship between [`Zoned::nanosecond`] and
5013 /// [`Zoned::subsec_nanosecond`]:
5014 ///
5015 /// ```
5016 /// use jiff::civil::time;
5017 ///
5018 /// let zdt1 = time(15, 21, 35, 0).on(2010, 6, 1).in_tz("America/New_York")?;
5019 /// let zdt2 = zdt1.with().nanosecond(123).build()?;
5020 /// assert_eq!(zdt2.subsec_nanosecond(), 123);
5021 ///
5022 /// # Ok::<(), Box<dyn std::error::Error>>(())
5023 /// ```
5024 #[inline]
5025 pub fn nanosecond(self, nanosecond: i16) -> ZonedWith {
5026 ZonedWith {
5027 datetime_with: self.datetime_with.nanosecond(nanosecond),
5028 ..self
5029 }
5030 }
5031
5032 /// Set the subsecond nanosecond field on a [`Zoned`].
5033 ///
5034 /// If you want to access this value on `Zoned`, then use
5035 /// [`Zoned::subsec_nanosecond`].
5036 ///
5037 /// This overrides any previous subsecond nanosecond settings.
5038 ///
5039 /// Note that this sets the entire fractional second component to
5040 /// nanosecond precision, and overrides any individual millisecond,
5041 /// microsecond or nanosecond settings. To set individual components,
5042 /// use [`ZonedWith::millisecond`], [`ZonedWith::microsecond`] or
5043 /// [`ZonedWith::nanosecond`].
5044 ///
5045 /// # Errors
5046 ///
5047 /// This returns an error when [`ZonedWith::build`] is called if the
5048 /// given subsecond nanosecond is outside the range `0..=999,999,999`,
5049 /// or if both this and one of [`ZonedWith::millisecond`],
5050 /// [`ZonedWith::microsecond`] or [`ZonedWith::nanosecond`] are set.
5051 ///
5052 /// # Example
5053 ///
5054 /// This shows the relationship between constructing a `Zoned` value
5055 /// with subsecond nanoseconds and its individual subsecond fields:
5056 ///
5057 /// ```
5058 /// use jiff::civil::time;
5059 ///
5060 /// let zdt1 = time(15, 21, 35, 0).on(2010, 6, 1).in_tz("America/New_York")?;
5061 /// let zdt2 = zdt1.with().subsec_nanosecond(123_456_789).build()?;
5062 /// assert_eq!(zdt2.millisecond(), 123);
5063 /// assert_eq!(zdt2.microsecond(), 456);
5064 /// assert_eq!(zdt2.nanosecond(), 789);
5065 ///
5066 /// # Ok::<(), Box<dyn std::error::Error>>(())
5067 /// ```
5068 #[inline]
5069 pub fn subsec_nanosecond(self, subsec_nanosecond: i32) -> ZonedWith {
5070 ZonedWith {
5071 datetime_with: self
5072 .datetime_with
5073 .subsec_nanosecond(subsec_nanosecond),
5074 ..self
5075 }
5076 }
5077
5078 /// Set the offset to use in the new zoned datetime.
5079 ///
5080 /// This can be used in some cases to explicitly disambiguate a datetime
5081 /// that could correspond to multiple instants in time.
5082 ///
5083 /// How the offset is used to construct a new zoned datetime
5084 /// depends on the offset conflict resolution strategy
5085 /// set via [`ZonedWith::offset_conflict`]. The default is
5086 /// [`OffsetConflict::PreferOffset`], which will always try to use the
5087 /// offset to resolve a datetime to an instant, unless the offset is
5088 /// incorrect for this zoned datetime's time zone. In which case, only the
5089 /// time zone is used to select the correct offset (which may involve using
5090 /// the disambiguation strategy set via [`ZonedWith::disambiguation`]).
5091 ///
5092 /// # Example
5093 ///
5094 /// This example shows parsing the first time the 1 o'clock hour appeared
5095 /// on a clock in New York on 2024-11-03, and then changing only the
5096 /// offset to flip it to the second time 1 o'clock appeared on the clock:
5097 ///
5098 /// ```
5099 /// use jiff::{tz, Zoned};
5100 ///
5101 /// let zdt1: Zoned = "2024-11-03 01:30-04[America/New_York]".parse()?;
5102 /// let zdt2 = zdt1.with().offset(tz::offset(-5)).build()?;
5103 /// assert_eq!(
5104 /// zdt2.to_string(),
5105 /// // Everything stays the same, except for the offset.
5106 /// "2024-11-03T01:30:00-05:00[America/New_York]",
5107 /// );
5108 ///
5109 /// // If we use an invalid offset for the America/New_York time zone,
5110 /// // then it will be ignored and the disambiguation strategy set will
5111 /// // be used.
5112 /// let zdt3 = zdt1.with().offset(tz::offset(-12)).build()?;
5113 /// assert_eq!(
5114 /// zdt3.to_string(),
5115 /// // The default disambiguation is Compatible.
5116 /// "2024-11-03T01:30:00-04:00[America/New_York]",
5117 /// );
5118 /// // But we could change the disambiguation strategy to reject such
5119 /// // cases!
5120 /// let result = zdt1
5121 /// .with()
5122 /// .offset(tz::offset(-12))
5123 /// .disambiguation(tz::Disambiguation::Reject)
5124 /// .build();
5125 /// assert!(result.is_err());
5126 ///
5127 /// # Ok::<(), Box<dyn std::error::Error>>(())
5128 /// ```
5129 #[inline]
5130 pub fn offset(self, offset: Offset) -> ZonedWith {
5131 ZonedWith { offset: Some(offset), ..self }
5132 }
5133
5134 /// Set the conflict resolution strategy for when an offset is inconsistent
5135 /// with the time zone.
5136 ///
5137 /// See the documentation on [`OffsetConflict`] for more details about the
5138 /// different strategies one can choose.
5139 ///
5140 /// Unlike parsing (where the default is `OffsetConflict::Reject`), the
5141 /// default for `ZonedWith` is [`OffsetConflict::PreferOffset`], which
5142 /// avoids daylight saving time disambiguation causing unexpected 1-hour
5143 /// shifts after small changes to clock time.
5144 ///
5145 /// # Example
5146 ///
5147 /// ```
5148 /// use jiff::Zoned;
5149 ///
5150 /// // Set to the "second" time 1:30 is on the clocks in New York on
5151 /// // 2024-11-03. The offset in the datetime string makes this
5152 /// // unambiguous.
5153 /// let zdt1 = "2024-11-03T01:30-05[America/New_York]".parse::<Zoned>()?;
5154 /// // Now we change the minute field:
5155 /// let zdt2 = zdt1.with().minute(34).build()?;
5156 /// assert_eq!(
5157 /// zdt2.to_string(),
5158 /// // Without taking the offset of the `Zoned` value into account,
5159 /// // this would have defaulted to using the "compatible"
5160 /// // disambiguation strategy, which would have selected the earlier
5161 /// // offset of -04 instead of sticking with the later offset of -05.
5162 /// "2024-11-03T01:34:00-05:00[America/New_York]",
5163 /// );
5164 ///
5165 /// // But note that if we change the clock time such that the previous
5166 /// // offset is no longer valid (by moving back before DST ended), then
5167 /// // the default strategy will automatically adapt and change the offset.
5168 /// let zdt2 = zdt1.with().hour(0).build()?;
5169 /// assert_eq!(
5170 /// zdt2.to_string(),
5171 /// "2024-11-03T00:30:00-04:00[America/New_York]",
5172 /// );
5173 ///
5174 /// # Ok::<(), Box<dyn std::error::Error>>(())
5175 /// ```
5176 #[inline]
5177 pub fn offset_conflict(self, strategy: OffsetConflict) -> ZonedWith {
5178 ZonedWith { offset_conflict: strategy, ..self }
5179 }
5180
5181 /// Set the disambiguation strategy for when a zoned datetime falls into a
5182 /// time zone transition "fold" or "gap."
5183 ///
5184 /// The most common manifestation of such time zone transitions is daylight
5185 /// saving time. In most cases, the transition into daylight saving time
5186 /// moves the civil time ("the time you see on the clock") ahead one hour.
5187 /// This is called a "gap" because an hour on the clock is skipped. While
5188 /// the transition out of daylight saving time moves the civil time back
5189 /// one hour. This is called a "fold" because an hour on the clock is
5190 /// repeated.
5191 ///
5192 /// In the case of a gap, an ambiguous datetime manifests as a time that
5193 /// never appears on a clock. (For example, `02:30` on `2024-03-10` in New
5194 /// York.) In the case of a fold, an ambiguous datetime manifests as a
5195 /// time that repeats itself. (For example, `01:30` on `2024-11-03` in New
5196 /// York.) So when a fold occurs, you don't know whether it's the "first"
5197 /// occurrence of that time or the "second."
5198 ///
5199 /// Time zone transitions are not just limited to daylight saving time,
5200 /// although those are the most common. In other cases, a transition occurs
5201 /// because of a change in the offset of the time zone itself. (See the
5202 /// examples below.)
5203 ///
5204 /// # Example: time zone offset change
5205 ///
5206 /// In this example, we explore a time zone offset change in Hawaii that
5207 /// occurred on `1947-06-08`. Namely, Hawaii went from a `-10:30` offset
5208 /// to a `-10:00` offset at `02:00`. This results in a 30 minute gap in
5209 /// civil time.
5210 ///
5211 /// ```
5212 /// use jiff::{civil::date, tz, ToSpan, Zoned};
5213 ///
5214 /// // This datetime is unambiguous...
5215 /// let zdt1 = "1943-06-02T02:05[Pacific/Honolulu]".parse::<Zoned>()?;
5216 /// // but... 02:05 didn't exist on clocks on 1947-06-08.
5217 /// let zdt2 = zdt1
5218 /// .with()
5219 /// .disambiguation(tz::Disambiguation::Later)
5220 /// .year(1947)
5221 /// .day(8)
5222 /// .build()?;
5223 /// // Our parser is configured to select the later time, so we jump to
5224 /// // 02:35. But if we used `Disambiguation::Earlier`, then we'd get
5225 /// // 01:35.
5226 /// assert_eq!(zdt2.datetime(), date(1947, 6, 8).at(2, 35, 0, 0));
5227 /// assert_eq!(zdt2.offset(), tz::offset(-10));
5228 ///
5229 /// // If we subtract 10 minutes from 02:35, notice that we (correctly)
5230 /// // jump to 01:55 *and* our offset is corrected to -10:30.
5231 /// let zdt3 = zdt2.checked_sub(10.minutes())?;
5232 /// assert_eq!(zdt3.datetime(), date(1947, 6, 8).at(1, 55, 0, 0));
5233 /// assert_eq!(zdt3.offset(), tz::offset(-10).saturating_sub(30.minutes()));
5234 ///
5235 /// # Ok::<(), Box<dyn std::error::Error>>(())
5236 /// ```
5237 ///
5238 /// # Example: offset conflict resolution and disambiguation
5239 ///
5240 /// This example shows how the disambiguation configuration can
5241 /// interact with the default offset conflict resolution strategy of
5242 /// [`OffsetConflict::PreferOffset`]:
5243 ///
5244 /// ```
5245 /// use jiff::{civil::date, tz, Zoned};
5246 ///
5247 /// // This datetime is unambiguous.
5248 /// let zdt1 = "2024-03-11T02:05[America/New_York]".parse::<Zoned>()?;
5249 /// assert_eq!(zdt1.offset(), tz::offset(-4));
5250 /// // But the same time on March 10 is ambiguous because there is a gap!
5251 /// let zdt2 = zdt1
5252 /// .with()
5253 /// .disambiguation(tz::Disambiguation::Earlier)
5254 /// .day(10)
5255 /// .build()?;
5256 /// assert_eq!(zdt2.datetime(), date(2024, 3, 10).at(1, 5, 0, 0));
5257 /// assert_eq!(zdt2.offset(), tz::offset(-5));
5258 ///
5259 /// # Ok::<(), Box<dyn std::error::Error>>(())
5260 /// ```
5261 ///
5262 /// Namely, while we started with an offset of `-04`, it (along with all
5263 /// other offsets) are considered invalid during civil time gaps due to
5264 /// time zone transitions (such as the beginning of daylight saving time in
5265 /// most locations).
5266 ///
5267 /// The default disambiguation strategy is
5268 /// [`Disambiguation::Compatible`], which in the case of gaps, chooses the
5269 /// time after the gap:
5270 ///
5271 /// ```
5272 /// use jiff::{civil::date, tz, Zoned};
5273 ///
5274 /// // This datetime is unambiguous.
5275 /// let zdt1 = "2024-03-11T02:05[America/New_York]".parse::<Zoned>()?;
5276 /// assert_eq!(zdt1.offset(), tz::offset(-4));
5277 /// // But the same time on March 10 is ambiguous because there is a gap!
5278 /// let zdt2 = zdt1
5279 /// .with()
5280 /// .day(10)
5281 /// .build()?;
5282 /// assert_eq!(zdt2.datetime(), date(2024, 3, 10).at(3, 5, 0, 0));
5283 /// assert_eq!(zdt2.offset(), tz::offset(-4));
5284 ///
5285 /// # Ok::<(), Box<dyn std::error::Error>>(())
5286 /// ```
5287 ///
5288 /// Alternatively, one can choose to always respect the offset, and thus
5289 /// civil time for the provided time zone will be adjusted to match the
5290 /// instant prescribed by the offset. In this case, no disambiguation is
5291 /// performed:
5292 ///
5293 /// ```
5294 /// use jiff::{civil::date, tz, Zoned};
5295 ///
5296 /// // This datetime is unambiguous. But `2024-03-10T02:05` is!
5297 /// let zdt1 = "2024-03-11T02:05[America/New_York]".parse::<Zoned>()?;
5298 /// assert_eq!(zdt1.offset(), tz::offset(-4));
5299 /// // But the same time on March 10 is ambiguous because there is a gap!
5300 /// let zdt2 = zdt1
5301 /// .with()
5302 /// .offset_conflict(tz::OffsetConflict::AlwaysOffset)
5303 /// .day(10)
5304 /// .build()?;
5305 /// // Why do we get this result? Because `2024-03-10T02:05-04` is
5306 /// // `2024-03-10T06:05Z`. And in `America/New_York`, the civil time
5307 /// // for that timestamp is `2024-03-10T01:05-05`.
5308 /// assert_eq!(zdt2.datetime(), date(2024, 3, 10).at(1, 5, 0, 0));
5309 /// assert_eq!(zdt2.offset(), tz::offset(-5));
5310 ///
5311 /// # Ok::<(), Box<dyn std::error::Error>>(())
5312 /// ```
5313 #[inline]
5314 pub fn disambiguation(self, strategy: Disambiguation) -> ZonedWith {
5315 ZonedWith { disambiguation: strategy, ..self }
5316 }
5317}
5318
5319#[inline]
5320fn day_length(
5321 dt: DateTime,
5322 tz: TimeZone,
5323) -> Result<ZonedDayNanoseconds, Error> {
5324 // FIXME: We should be doing this with a &TimeZone, but will need a
5325 // refactor so that we do zone-aware arithmetic using just a Timestamp and
5326 // a &TimeZone.
5327 let tz2 = tz.clone();
5328 let start = dt.start_of_day().to_zoned(tz).with_context(move || {
5329 let tzname = tz2.diagnostic_name();
5330 err!("failed to find start of day for {dt} in time zone {tzname}")
5331 })?;
5332 let end = start.checked_add(Span::new().days_ranged(C(1))).with_context(
5333 || err!("failed to add 1 day to {start} to find length of day"),
5334 )?;
5335 let span = start
5336 .timestamp()
5337 .until((Unit::Nanosecond, end.timestamp()))
5338 .with_context(|| {
5339 err!(
5340 "failed to compute span in nanoseconds \
5341 from {start} until {end}"
5342 )
5343 })?;
5344 let nanos = span.get_nanoseconds_ranged();
5345 ZonedDayNanoseconds::try_rfrom("nanoseconds-per-zoned-day", nanos)
5346 .with_context(|| {
5347 err!(
5348 "failed to convert span between {start} until {end} \
5349 to nanoseconds",
5350 )
5351 })
5352}
5353
5354#[cfg(test)]
5355mod tests {
5356 use std::io::Cursor;
5357
5358 use alloc::string::ToString;
5359
5360 use crate::{
5361 civil::{date, datetime},
5362 span::span_eq,
5363 tz, ToSpan,
5364 };
5365
5366 use super::*;
5367
5368 #[test]
5369 fn until_with_largest_unit() {
5370 if crate::tz::db().is_definitively_empty() {
5371 return;
5372 }
5373
5374 let zdt1: Zoned = date(1995, 12, 7)
5375 .at(3, 24, 30, 3500)
5376 .in_tz("Asia/Kolkata")
5377 .unwrap();
5378 let zdt2: Zoned =
5379 date(2019, 1, 31).at(15, 30, 0, 0).in_tz("Asia/Kolkata").unwrap();
5380 let span = zdt1.until(&zdt2).unwrap();
5381 span_eq!(
5382 span,
5383 202956
5384 .hours()
5385 .minutes(5)
5386 .seconds(29)
5387 .milliseconds(999)
5388 .microseconds(996)
5389 .nanoseconds(500)
5390 );
5391 let span = zdt1.until((Unit::Year, &zdt2)).unwrap();
5392 span_eq!(
5393 span,
5394 23.years()
5395 .months(1)
5396 .days(24)
5397 .hours(12)
5398 .minutes(5)
5399 .seconds(29)
5400 .milliseconds(999)
5401 .microseconds(996)
5402 .nanoseconds(500)
5403 );
5404
5405 let span = zdt2.until((Unit::Year, &zdt1)).unwrap();
5406 span_eq!(
5407 span,
5408 -23.years()
5409 .months(1)
5410 .days(24)
5411 .hours(12)
5412 .minutes(5)
5413 .seconds(29)
5414 .milliseconds(999)
5415 .microseconds(996)
5416 .nanoseconds(500)
5417 );
5418 let span = zdt1.until((Unit::Nanosecond, &zdt2)).unwrap();
5419 span_eq!(span, 730641929999996500i64.nanoseconds());
5420
5421 let zdt1: Zoned =
5422 date(2020, 1, 1).at(0, 0, 0, 0).in_tz("America/New_York").unwrap();
5423 let zdt2: Zoned = date(2020, 4, 24)
5424 .at(21, 0, 0, 0)
5425 .in_tz("America/New_York")
5426 .unwrap();
5427 let span = zdt1.until(&zdt2).unwrap();
5428 span_eq!(span, 2756.hours());
5429 let span = zdt1.until((Unit::Year, &zdt2)).unwrap();
5430 span_eq!(span, 3.months().days(23).hours(21));
5431
5432 let zdt1: Zoned = date(2000, 10, 29)
5433 .at(0, 0, 0, 0)
5434 .in_tz("America/Vancouver")
5435 .unwrap();
5436 let zdt2: Zoned = date(2000, 10, 29)
5437 .at(23, 0, 0, 5)
5438 .in_tz("America/Vancouver")
5439 .unwrap();
5440 let span = zdt1.until((Unit::Day, &zdt2)).unwrap();
5441 span_eq!(span, 24.hours().nanoseconds(5));
5442 }
5443
5444 #[cfg(target_pointer_width = "64")]
5445 #[test]
5446 fn zoned_size() {
5447 #[cfg(debug_assertions)]
5448 {
5449 #[cfg(feature = "alloc")]
5450 {
5451 assert_eq!(96, core::mem::size_of::<Zoned>());
5452 }
5453 #[cfg(all(target_pointer_width = "64", not(feature = "alloc")))]
5454 {
5455 assert_eq!(96, core::mem::size_of::<Zoned>());
5456 }
5457 }
5458 #[cfg(not(debug_assertions))]
5459 {
5460 #[cfg(feature = "alloc")]
5461 {
5462 assert_eq!(40, core::mem::size_of::<Zoned>());
5463 }
5464 #[cfg(all(target_pointer_width = "64", not(feature = "alloc")))]
5465 {
5466 // This asserts the same value as the alloc value above, but
5467 // it wasn't always this way, which is why it's written out
5468 // separately. Moreover, in theory, I'd be open to regressing
5469 // this value if it led to an improvement in alloc-mode. But
5470 // more likely, it would be nice to decrease this size in
5471 // non-alloc modes.
5472 assert_eq!(40, core::mem::size_of::<Zoned>());
5473 }
5474 }
5475 }
5476
5477 /// A `serde` deserializer compatibility test.
5478 ///
5479 /// Serde YAML used to be unable to deserialize `jiff` types,
5480 /// as deserializing from bytes is not supported by the deserializer.
5481 ///
5482 /// - <https://github.com/BurntSushi/jiff/issues/138>
5483 /// - <https://github.com/BurntSushi/jiff/discussions/148>
5484 #[test]
5485 fn zoned_deserialize_yaml() {
5486 if crate::tz::db().is_definitively_empty() {
5487 return;
5488 }
5489
5490 let expected = datetime(2024, 10, 31, 16, 33, 53, 123456789)
5491 .in_tz("UTC")
5492 .unwrap();
5493
5494 let deserialized: Zoned =
5495 serde_yaml::from_str("2024-10-31T16:33:53.123456789+00:00[UTC]")
5496 .unwrap();
5497
5498 assert_eq!(deserialized, expected);
5499
5500 let deserialized: Zoned = serde_yaml::from_slice(
5501 "2024-10-31T16:33:53.123456789+00:00[UTC]".as_bytes(),
5502 )
5503 .unwrap();
5504
5505 assert_eq!(deserialized, expected);
5506
5507 let cursor = Cursor::new(b"2024-10-31T16:33:53.123456789+00:00[UTC]");
5508 let deserialized: Zoned = serde_yaml::from_reader(cursor).unwrap();
5509
5510 assert_eq!(deserialized, expected);
5511 }
5512
5513 /// This is a regression test for a case where changing a zoned datetime
5514 /// to have a time of midnight ends up producing a counter-intuitive
5515 /// result.
5516 ///
5517 /// See: <https://github.com/BurntSushi/jiff/issues/211>
5518 #[test]
5519 fn zoned_with_time_dst_after_gap() {
5520 if crate::tz::db().is_definitively_empty() {
5521 return;
5522 }
5523
5524 let zdt1: Zoned = "2024-03-31T12:00[Atlantic/Azores]".parse().unwrap();
5525 assert_eq!(
5526 zdt1.to_string(),
5527 "2024-03-31T12:00:00+00:00[Atlantic/Azores]"
5528 );
5529
5530 let zdt2 = zdt1.with().time(Time::midnight()).build().unwrap();
5531 assert_eq!(
5532 zdt2.to_string(),
5533 "2024-03-31T01:00:00+00:00[Atlantic/Azores]"
5534 );
5535 }
5536
5537 /// Similar to `zoned_with_time_dst_after_gap`, but tests what happens
5538 /// when moving from/to both sides of the gap.
5539 ///
5540 /// See: <https://github.com/BurntSushi/jiff/issues/211>
5541 #[test]
5542 fn zoned_with_time_dst_us_eastern() {
5543 if crate::tz::db().is_definitively_empty() {
5544 return;
5545 }
5546
5547 let zdt1: Zoned = "2024-03-10T01:30[US/Eastern]".parse().unwrap();
5548 assert_eq!(zdt1.to_string(), "2024-03-10T01:30:00-05:00[US/Eastern]");
5549 let zdt2 = zdt1.with().hour(2).build().unwrap();
5550 assert_eq!(zdt2.to_string(), "2024-03-10T03:30:00-04:00[US/Eastern]");
5551
5552 let zdt1: Zoned = "2024-03-10T03:30[US/Eastern]".parse().unwrap();
5553 assert_eq!(zdt1.to_string(), "2024-03-10T03:30:00-04:00[US/Eastern]");
5554 let zdt2 = zdt1.with().hour(2).build().unwrap();
5555 assert_eq!(zdt2.to_string(), "2024-03-10T03:30:00-04:00[US/Eastern]");
5556
5557 // I originally thought that this was difference from Temporal. Namely,
5558 // I thought that Temporal ignored the disambiguation setting (and the
5559 // bad offset). But it doesn't. I was holding it wrong.
5560 //
5561 // See: https://github.com/tc39/proposal-temporal/issues/3078
5562 let zdt1: Zoned = "2024-03-10T01:30[US/Eastern]".parse().unwrap();
5563 assert_eq!(zdt1.to_string(), "2024-03-10T01:30:00-05:00[US/Eastern]");
5564 let zdt2 = zdt1
5565 .with()
5566 .offset(tz::offset(10))
5567 .hour(2)
5568 .disambiguation(Disambiguation::Earlier)
5569 .build()
5570 .unwrap();
5571 assert_eq!(zdt2.to_string(), "2024-03-10T01:30:00-05:00[US/Eastern]");
5572
5573 // This should also respect the disambiguation setting even without
5574 // explicitly specifying an invalid offset. This is becaue `02:30-05`
5575 // is regarded as invalid since `02:30` isn't a valid civil time on
5576 // this date in this time zone.
5577 let zdt1: Zoned = "2024-03-10T01:30[US/Eastern]".parse().unwrap();
5578 assert_eq!(zdt1.to_string(), "2024-03-10T01:30:00-05:00[US/Eastern]");
5579 let zdt2 = zdt1
5580 .with()
5581 .hour(2)
5582 .disambiguation(Disambiguation::Earlier)
5583 .build()
5584 .unwrap();
5585 assert_eq!(zdt2.to_string(), "2024-03-10T01:30:00-05:00[US/Eastern]");
5586 }
5587
5588 #[test]
5589 fn zoned_precision_loss() {
5590 if crate::tz::db().is_definitively_empty() {
5591 return;
5592 }
5593
5594 let zdt1: Zoned = "2025-01-25T19:32:21.783444592+01:00[Europe/Paris]"
5595 .parse()
5596 .unwrap();
5597 let span = 1.second();
5598 let zdt2 = &zdt1 + span;
5599 assert_eq!(
5600 zdt2.to_string(),
5601 "2025-01-25T19:32:22.783444592+01:00[Europe/Paris]"
5602 );
5603 assert_eq!(zdt1, &zdt2 - span, "should be reversible");
5604 }
5605
5606 // See: https://github.com/BurntSushi/jiff/issues/290
5607 #[test]
5608 fn zoned_roundtrip_regression() {
5609 if crate::tz::db().is_definitively_empty() {
5610 return;
5611 }
5612
5613 let zdt: Zoned =
5614 "2063-03-31T10:00:00+11:00[Australia/Sydney]".parse().unwrap();
5615 assert_eq!(zdt.offset(), super::Offset::constant(11));
5616 let roundtrip = zdt.time_zone().to_zoned(zdt.datetime()).unwrap();
5617 assert_eq!(zdt, roundtrip);
5618 }
5619}