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