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