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