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