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