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