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