Skip to main content

time/
datetime.rs

1//! The [`DateTime`] type.
2
3use core::{
4    cmp::Ordering,
5    fmt,
6    hash::{Hash, Hasher},
7};
8
9use crate::{
10    civil,
11    error::{self, Error},
12    format::{self, Format, Formatted},
13    parse,
14    timezone::{Disambiguation, TimeZone},
15};
16
17/// The earliest supported calendar year.
18pub(crate) const MIN_YEAR: i32 = -9999;
19/// The latest supported calendar year.
20pub(crate) const MAX_YEAR: i32 = 9999;
21
22pub(crate) const MIN_SECS: i64 = civil::days_from_civil(MIN_YEAR, 1, 1) * civil::SECS_PER_DAY;
23pub(crate) const MAX_SECS: i64 =
24    civil::days_from_civil(MAX_YEAR, 12, 31) * civil::SECS_PER_DAY + (civil::SECS_PER_DAY - 1);
25
26/// Validates civil fields and returns the local wall-clock time as seconds
27/// since the Unix epoch.
28///
29/// A leap second (`second == 60`) is accepted and represented as `:59` of the
30/// same minute; the library never produces `:60` on output.
31pub(crate) fn local_seconds_checked(
32    year: i32,
33    month: u8,
34    day: u8,
35    hour: u8,
36    minute: u8,
37    second: u8,
38) -> Result<i64, Error> {
39    if !(MIN_YEAR..=MAX_YEAR).contains(&year) {
40        return Err(error::out_of_range("year is outside the supported range"));
41    }
42    if !(1..=12).contains(&month) {
43        return Err(error::invalid("month is not in 1..=12"));
44    }
45    if day < 1 || day > civil::days_in_month(year, month) {
46        return Err(error::invalid("day is not valid for the given month"));
47    }
48    if hour > 23 {
49        return Err(error::invalid("hour is not in 0..=23"));
50    }
51    if minute > 59 {
52        return Err(error::invalid("minute is not in 0..=59"));
53    }
54    if second > 60 {
55        return Err(error::invalid("second is not in 0..=60"));
56    }
57    // A leap second is accepted but mapped onto `:59`.
58    let second = if second == 60 { 59 } else { second };
59    Ok(civil::days_from_civil(year, month, day) * civil::SECS_PER_DAY
60        + i64::from(hour) * 3600
61        + i64::from(minute) * 60
62        + i64::from(second))
63}
64
65/// A day of the week.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
67pub enum Weekday {
68    /// Monday.
69    Monday,
70    /// Tuesday.
71    Tuesday,
72    /// Wednesday.
73    Wednesday,
74    /// Thursday.
75    Thursday,
76    /// Friday.
77    Friday,
78    /// Saturday.
79    Saturday,
80    /// Sunday.
81    Sunday,
82}
83
84impl Weekday {
85    pub(crate) const fn from_index(index: u8) -> Weekday {
86        match index {
87            0 => Weekday::Monday,
88            1 => Weekday::Tuesday,
89            2 => Weekday::Wednesday,
90            3 => Weekday::Thursday,
91            4 => Weekday::Friday,
92            5 => Weekday::Saturday,
93            _ => Weekday::Sunday,
94        }
95    }
96
97    /// Returns the English name of the weekday.
98    #[must_use]
99    #[inline]
100    pub const fn name(self) -> &'static str {
101        match self {
102            Weekday::Monday => "Monday",
103            Weekday::Tuesday => "Tuesday",
104            Weekday::Wednesday => "Wednesday",
105            Weekday::Thursday => "Thursday",
106            Weekday::Friday => "Friday",
107            Weekday::Saturday => "Saturday",
108            Weekday::Sunday => "Sunday",
109        }
110    }
111
112    /// Returns the three-letter English abbreviation of the weekday.
113    #[must_use]
114    #[inline]
115    pub const fn short_name(self) -> &'static str {
116        match self {
117            Weekday::Monday => "Mon",
118            Weekday::Tuesday => "Tue",
119            Weekday::Wednesday => "Wed",
120            Weekday::Thursday => "Thu",
121            Weekday::Friday => "Fri",
122            Weekday::Saturday => "Sat",
123            Weekday::Sunday => "Sun",
124        }
125    }
126
127    /// Returns the ISO day number, `Monday = 1 ..= Sunday = 7`.
128    #[must_use]
129    #[inline]
130    pub const fn number_from_monday(self) -> u8 {
131        match self {
132            Weekday::Monday => 1,
133            Weekday::Tuesday => 2,
134            Weekday::Wednesday => 3,
135            Weekday::Thursday => 4,
136            Weekday::Friday => 5,
137            Weekday::Saturday => 6,
138            Weekday::Sunday => 7,
139        }
140    }
141
142    /// Returns the day number with `Sunday = 0 ..= Saturday = 6`.
143    #[must_use]
144    #[inline]
145    pub const fn number_from_sunday(self) -> u8 {
146        match self {
147            Weekday::Sunday => 0,
148            Weekday::Monday => 1,
149            Weekday::Tuesday => 2,
150            Weekday::Wednesday => 3,
151            Weekday::Thursday => 4,
152            Weekday::Friday => 5,
153            Weekday::Saturday => 6,
154        }
155    }
156}
157
158/// A precise instant in time together with a [`TimeZone`].
159///
160/// A `DateTime` is always absolute: it is represented as nanoseconds since the
161/// Unix epoch (with nanosecond precision) plus the time zone used to display
162/// it. Comparisons and subtraction operate on the instant only.
163///
164/// The supported UTC calendar range is `-9999-01-01` through `9999-12-31`.
165/// (A value near the boundary can display a year one outside this range when a
166/// large UTC offset is applied.)
167///
168/// # Examples
169///
170/// ```
171/// use time::DateTime;
172///
173/// let dt: DateTime = "2024-02-29T12:00:00Z".parse().unwrap();
174/// assert_eq!(dt.year(), 2024);
175/// assert_eq!(dt.month(), 2);
176/// assert_eq!(dt.day(), 29);
177/// ```
178#[derive(Clone, Copy)]
179pub struct DateTime {
180    pub(crate) secs: i64,
181    pub(crate) nanos: u32,
182    pub(crate) offset: i32,
183    pub(crate) zone: TimeZone,
184    /// When both values being compared were obtained from `now`, differences
185    /// are measured with the monotonic clock.
186    #[cfg(feature = "std")]
187    pub(crate) monotonic: Option<std::time::Instant>,
188}
189
190impl DateTime {
191    /// The Unix epoch, `1970-01-01T00:00:00Z`.
192    pub const UNIX_EPOCH: DateTime = DateTime {
193        secs: 0,
194        nanos: 0,
195        offset: 0,
196        zone: TimeZone::UTC,
197        #[cfg(feature = "std")]
198        monotonic: None,
199    };
200
201    pub(crate) const fn from_raw(secs: i64, nanos: u32, offset: i32, zone: TimeZone) -> DateTime {
202        DateTime {
203            secs,
204            nanos,
205            offset,
206            zone,
207            #[cfg(feature = "std")]
208            monotonic: None,
209        }
210    }
211
212    /// Creates a `DateTime` from whole seconds since the Unix epoch, in UTC.
213    ///
214    /// # Errors
215    ///
216    /// Returns an error if the instant is outside the supported range.
217    #[inline]
218    pub fn from_unix(seconds: i64) -> Result<DateTime, Error> {
219        DateTime::from_unix_nanos(i128::from(seconds) * 1_000_000_000)
220    }
221
222    /// Creates a `DateTime` from nanoseconds since the Unix epoch, in UTC.
223    ///
224    /// # Errors
225    ///
226    /// Returns an error if the instant is outside the supported range.
227    #[inline]
228    pub fn from_unix_nanos(nanoseconds: i128) -> Result<DateTime, Error> {
229        let seconds = i64::try_from(nanoseconds.div_euclid(1_000_000_000))
230            .map_err(|_| error::out_of_range("instant is outside the supported range"))?;
231        let nanos = nanoseconds.rem_euclid(1_000_000_000) as u32;
232        DateTime::from_raw_checked(seconds, nanos, TimeZone::UTC)
233    }
234
235    /// Creates a `DateTime` from civil fields in the given time zone.
236    ///
237    /// A civil time that is skipped by a DST transition is shifted forward; a
238    /// civil time that occurs twice selects the earlier instant. Use
239    /// [`DateTime::from_parts_with`] to choose another strategy, or
240    /// [`TimeZone::ambiguity`] to inspect the mapping. A leap second
241    /// (`second == 60`) is accepted and represented as `:59` of the same
242    /// minute; the library never produces `:60` on output.
243    ///
244    /// # Errors
245    ///
246    /// Returns an error if any field is out of range, or if the resulting
247    /// instant is outside the supported range.
248    #[allow(clippy::too_many_arguments)]
249    pub fn from_parts(
250        year: i32,
251        month: u8,
252        day: u8,
253        hour: u8,
254        minute: u8,
255        second: u8,
256        nanosecond: u32,
257        zone: TimeZone,
258    ) -> Result<DateTime, Error> {
259        DateTime::from_parts_with(
260            year,
261            month,
262            day,
263            hour,
264            minute,
265            second,
266            nanosecond,
267            zone,
268            Disambiguation::Compatible,
269        )
270    }
271
272    /// Creates a `DateTime` from civil fields, resolving any ambiguity or gap
273    /// with the given [`Disambiguation`] strategy.
274    ///
275    /// # Errors
276    ///
277    /// Returns an error if any field is out of range, if the resulting instant
278    /// is outside the supported range, or if `disambiguation` is
279    /// [`Disambiguation::Reject`] and the civil time is ambiguous or does not
280    /// exist (see [`ErrorKind::Ambiguous`](crate::ErrorKind::Ambiguous)).
281    #[allow(clippy::too_many_arguments)]
282    pub fn from_parts_with(
283        year: i32,
284        month: u8,
285        day: u8,
286        hour: u8,
287        minute: u8,
288        second: u8,
289        nanosecond: u32,
290        zone: TimeZone,
291        disambiguation: Disambiguation,
292    ) -> Result<DateTime, Error> {
293        if nanosecond >= civil::NANOS_PER_SEC {
294            return Err(error::invalid("nanosecond is not in 0..1_000_000_000"));
295        }
296        let local = local_seconds_checked(year, month, day, hour, minute, second)?;
297        let instant = zone.resolve_with(local, disambiguation)?;
298        DateTime::from_raw_checked(instant, nanosecond, zone)
299    }
300
301    #[inline]
302    pub(crate) fn from_raw_checked(secs: i64, nanos: u32, zone: TimeZone) -> Result<DateTime, Error> {
303        if !(MIN_SECS..=MAX_SECS).contains(&secs) {
304            return Err(error::out_of_range("instant is outside the supported range"));
305        }
306        let offset = zone.offset_at(secs);
307        Ok(DateTime {
308            secs,
309            nanos,
310            offset,
311            zone,
312            #[cfg(feature = "std")]
313            monotonic: None,
314        })
315    }
316
317    /// Returns the current time in the given time zone.
318    #[cfg(feature = "std")]
319    #[must_use]
320    pub fn now_in(zone: TimeZone) -> DateTime {
321        let now = std::time::SystemTime::now();
322        let (seconds, nanos) = match now.duration_since(std::time::UNIX_EPOCH) {
323            Ok(duration) => (duration.as_secs() as i64, duration.subsec_nanos()),
324            Err(err) => {
325                let duration = err.duration();
326                let seconds = duration.as_secs() as i64;
327                if duration.subsec_nanos() == 0 {
328                    (-seconds, 0)
329                } else {
330                    (-seconds - 1, 1_000_000_000 - duration.subsec_nanos())
331                }
332            }
333        };
334        let utc = DateTime::from_unix_nanos(i128::from(seconds) * 1_000_000_000 + i128::from(nanos))
335            .unwrap_or(DateTime::UNIX_EPOCH);
336        let mut result = utc.in_timezone(zone);
337        result.monotonic = Some(std::time::Instant::now());
338        result
339    }
340
341    /// Returns the current time in the system's local time zone.
342    ///
343    /// With the `timezone-system` feature the system zone is used, falling back
344    /// to UTC if it cannot be determined. Without that feature this is UTC.
345    #[cfg(feature = "std")]
346    #[must_use]
347    pub fn now() -> DateTime {
348        #[cfg(feature = "timezone-system")]
349        {
350            match TimeZone::system() {
351                Ok(zone) => DateTime::now_in(zone),
352                Err(_) => DateTime::now_in(TimeZone::UTC),
353            }
354        }
355        #[cfg(not(feature = "timezone-system"))]
356        {
357            DateTime::now_in(TimeZone::UTC)
358        }
359    }
360
361    /// Parses a string, interpreting zone-less input in `zone`.
362    ///
363    /// This is [`str::parse`] with an explicit default zone. An offset or zone
364    /// annotation in the input always takes precedence.
365    ///
366    /// # Errors
367    ///
368    /// Returns an error if the input cannot be parsed or is out of range.
369    pub fn parse_with_timezone(input: &str, zone: TimeZone) -> Result<DateTime, Error> {
370        parse::parse(input, Some(zone))
371    }
372
373    /// Parses a byte slice, interpreting zone-less input as UTC.
374    ///
375    /// This accepts the same grammar as [`str::parse`] and never allocates;
376    /// non-UTF-8 input is rejected.
377    ///
378    /// # Errors
379    ///
380    /// Returns an error if the input cannot be parsed or is out of range.
381    pub fn parse_bytes(input: &[u8]) -> Result<DateTime, Error> {
382        parse::parse_bytes(input, None)
383    }
384
385    /// Parses a byte slice, interpreting zone-less input in `zone`.
386    ///
387    /// # Errors
388    ///
389    /// Returns an error if the input cannot be parsed or is out of range.
390    pub fn parse_bytes_with_timezone(input: &[u8], zone: TimeZone) -> Result<DateTime, Error> {
391        parse::parse_bytes(input, Some(zone))
392    }
393
394    /// Parses a byte slice using a specific [`Format`].
395    ///
396    /// This accepts the same grammar as [`DateTime::parse_with_format`] and
397    /// never allocates; non-UTF-8 input is rejected.
398    ///
399    /// # Errors
400    ///
401    /// Returns an error if the input cannot be parsed or is out of range.
402    pub fn parse_bytes_with_format(input: &[u8], format: Format) -> Result<DateTime, Error> {
403        parse::parse_bytes_with_format(input, format)
404    }
405
406    /// Parses a string using a specific [`Format`].
407    ///
408    /// `TimeOnly` input is interpreted on the Unix epoch date (`1970-01-01`).
409    ///
410    /// # Errors
411    ///
412    /// Returns an error if the input cannot be parsed or is out of range.
413    pub fn parse_with_format(input: &str, format: Format) -> Result<DateTime, Error> {
414        parse::parse_with_format(input, format)
415    }
416
417    /// Returns a best-effort [`Display`](core::fmt::Display) adapter for
418    /// `format`.
419    ///
420    /// This never fails. Formats with a restricted domain can emit a
421    /// non-conforming string for out-of-domain values; see [`Format`] and use
422    /// [`DateTime::try_format`] for strict output.
423    #[must_use]
424    pub fn format(&self, format: Format) -> Formatted<'_> {
425        Formatted::new(self, format)
426    }
427
428    /// Returns a [`Display`](core::fmt::Display) adapter, checking up front
429    /// that the value is representable in `format`.
430    ///
431    /// Unlike [`DateTime::format`], this rejects values that the format cannot
432    /// express conformantly: a year outside `0000..=9999` for every format
433    /// except [`Format::TimeOnly`], and a UTC offset with a seconds component
434    /// for the RFC 3339, RFC 9557, ISO 8601 and RFC 2822 formats.
435    ///
436    /// # Errors
437    ///
438    /// Returns [`ErrorKind::OutOfRange`](crate::ErrorKind::OutOfRange) if the
439    /// value cannot be represented in the requested format.
440    pub fn try_format(&self, format: Format) -> Result<Formatted<'_>, Error> {
441        if !matches!(format, Format::TimeOnly) && !(0..=9999).contains(&self.year()) {
442            return Err(error::out_of_range("value cannot be represented in the requested format"));
443        }
444        if matches!(format, Format::Rfc3339 | Format::Rfc9557 | Format::Iso8601 | Format::Rfc2822)
445            && self.offset() % 60 != 0
446        {
447            return Err(error::out_of_range("offset cannot be represented in the requested format"));
448        }
449        Ok(Formatted::new(self, format))
450    }
451
452    /// Writes this value in `format` into `buffer` without allocating, and
453    /// returns the number of bytes written.
454    ///
455    /// This is the allocation-free companion of [`DateTime::format`]: it
456    /// applies the same strict representability checks as
457    /// [`DateTime::try_format`], so it works in `no_std` environments without
458    /// `alloc`.
459    ///
460    /// # Errors
461    ///
462    /// Returns [`ErrorKind::OutOfRange`](crate::ErrorKind::OutOfRange) if the
463    /// value cannot be represented in `format`, or
464    /// [`ErrorKind::BufferTooSmall`](crate::ErrorKind::BufferTooSmall) if
465    /// `buffer` is too small.
466    pub fn format_to(&self, buffer: &mut [u8], format: Format) -> Result<usize, Error> {
467        self.try_format(format)?.write_to(buffer)
468    }
469
470    /// Returns the number of whole seconds since the Unix epoch.
471    #[must_use]
472    #[inline]
473    pub const fn unix(&self) -> i64 {
474        self.secs
475    }
476
477    /// Returns the number of nanoseconds since the Unix epoch.
478    #[must_use]
479    #[inline]
480    pub const fn unix_nanos(&self) -> i128 {
481        self.secs as i128 * 1_000_000_000 + self.nanos as i128
482    }
483
484    /// Returns the UTC offset in seconds east of Greenwich.
485    #[must_use]
486    #[inline]
487    pub const fn offset(&self) -> i32 {
488        self.offset
489    }
490
491    /// Returns the time zone associated with this value.
492    #[must_use]
493    #[inline]
494    pub const fn timezone(&self) -> TimeZone {
495        self.zone
496    }
497
498    /// Returns the canonical name of the time zone, if it has one.
499    #[must_use]
500    #[inline]
501    pub const fn timezone_name(&self) -> Option<&'static str> {
502        self.zone.annotation_name()
503    }
504
505    /// Returns `true` if daylight saving time is in effect.
506    #[must_use]
507    pub fn is_dst(&self) -> bool {
508        self.zone.is_dst_at(self.secs)
509    }
510
511    /// Returns the local time at this instant in `zone`.
512    #[must_use]
513    #[inline]
514    pub fn in_timezone(&self, zone: TimeZone) -> DateTime {
515        let offset = zone.offset_at(self.secs);
516        DateTime {
517            secs: self.secs,
518            nanos: self.nanos,
519            offset,
520            zone,
521            #[cfg(feature = "std")]
522            monotonic: self.monotonic,
523        }
524    }
525
526    /// Returns the local civil fields: `(year, month, day, hour, minute,
527    /// second, nanosecond)`.
528    #[inline]
529    pub(crate) fn parts(&self) -> (i32, u8, u8, u8, u8, u8, u32) {
530        let local = self.secs + i64::from(self.offset);
531        let days = local.div_euclid(civil::SECS_PER_DAY);
532        let seconds = local.rem_euclid(civil::SECS_PER_DAY);
533        let (year, month, day) = civil::civil_from_days(days);
534        let hour = (seconds / 3600) as u8;
535        let minute = ((seconds % 3600) / 60) as u8;
536        let second = (seconds % 60) as u8;
537        (year, month, day, hour, minute, second, self.nanos)
538    }
539
540    /// Returns the calendar year.
541    #[must_use]
542    #[inline]
543    pub fn year(&self) -> i32 {
544        self.parts().0
545    }
546
547    /// Returns the month (`1..=12`).
548    #[must_use]
549    #[inline]
550    pub fn month(&self) -> u8 {
551        self.parts().1
552    }
553
554    /// Returns the day of the month (`1..=31`).
555    #[must_use]
556    #[inline]
557    pub fn day(&self) -> u8 {
558        self.parts().2
559    }
560
561    /// Returns the hour (`0..=23`).
562    #[must_use]
563    #[inline]
564    pub fn hour(&self) -> u8 {
565        self.parts().3
566    }
567
568    /// Returns the minute (`0..=59`).
569    #[must_use]
570    #[inline]
571    pub fn minute(&self) -> u8 {
572        self.parts().4
573    }
574
575    /// Returns the second (`0..=59`).
576    #[must_use]
577    #[inline]
578    pub fn second(&self) -> u8 {
579        self.parts().5
580    }
581
582    /// Returns the nanosecond (`0..1_000_000_000`).
583    #[must_use]
584    #[inline]
585    pub fn nanosecond(&self) -> u32 {
586        self.nanos
587    }
588
589    /// Returns the millisecond (`0..1000`).
590    #[must_use]
591    #[inline]
592    pub fn millisecond(&self) -> u32 {
593        self.nanos / 1_000_000
594    }
595
596    /// Returns the microsecond (`0..1_000_000`).
597    #[must_use]
598    #[inline]
599    pub fn microsecond(&self) -> u32 {
600        self.nanos / 1_000
601    }
602
603    /// Returns the day of the week.
604    #[must_use]
605    #[inline]
606    pub fn weekday(&self) -> Weekday {
607        let days = (self.secs + i64::from(self.offset)).div_euclid(civil::SECS_PER_DAY);
608        Weekday::from_index(civil::weekday_from_days(days))
609    }
610
611    /// Returns the day of the year (`1..=366`).
612    #[must_use]
613    #[inline]
614    pub fn ordinal(&self) -> u16 {
615        let (year, month, day, ..) = self.parts();
616        civil::ordinal(year, month, day)
617    }
618
619    /// Returns the ISO 8601 week-based year and week number.
620    #[must_use]
621    #[inline]
622    pub fn iso_week(&self) -> (i32, u8) {
623        let (year, month, day, ..) = self.parts();
624        civil::iso_week(year, month, day)
625    }
626
627    /// Adds a [`Duration`](core::time::Duration) to this instant.
628    ///
629    /// # Errors
630    ///
631    /// Returns an error if the result is out of range.
632    pub fn checked_add(&self, duration: core::time::Duration) -> Result<DateTime, Error> {
633        let total = self
634            .unix_nanos()
635            .checked_add(i128::from(duration.as_secs()) * 1_000_000_000)
636            .and_then(|v| v.checked_add(i128::from(duration.subsec_nanos())))
637            .ok_or_else(|| error::out_of_range("result is outside the supported range"))?;
638        #[allow(unused_mut)]
639        let mut result = DateTime::from_unix_nanos(total)?.in_timezone_checked(self.zone)?;
640        #[cfg(feature = "std")]
641        {
642            result.monotonic = self.monotonic.and_then(|reading| reading.checked_add(duration));
643        }
644        Ok(result)
645    }
646
647    /// Subtracts a [`Duration`](core::time::Duration) from this instant.
648    ///
649    /// # Errors
650    ///
651    /// Returns an error if the result is out of range.
652    pub fn checked_sub(&self, duration: core::time::Duration) -> Result<DateTime, Error> {
653        let total = self
654            .unix_nanos()
655            .checked_sub(i128::from(duration.as_secs()) * 1_000_000_000)
656            .and_then(|v| v.checked_sub(i128::from(duration.subsec_nanos())))
657            .ok_or_else(|| error::out_of_range("result is outside the supported range"))?;
658        #[allow(unused_mut)]
659        let mut result = DateTime::from_unix_nanos(total)?.in_timezone_checked(self.zone)?;
660        #[cfg(feature = "std")]
661        {
662            result.monotonic = self.monotonic.and_then(|reading| reading.checked_sub(duration));
663        }
664        Ok(result)
665    }
666
667    /// Adds a duration, saturating at the supported range boundaries.
668    #[must_use]
669    pub fn saturating_add(&self, duration: core::time::Duration) -> DateTime {
670        match self.checked_add(duration) {
671            Ok(dt) => dt,
672            Err(_) => DateTime::from_raw_checked(MAX_SECS, 999_999_999, self.zone)
673                .unwrap_or_else(|_| DateTime::from_raw(MAX_SECS, 999_999_999, self.offset, self.zone)),
674        }
675    }
676
677    /// Subtracts a duration, saturating at the supported range boundaries.
678    #[must_use]
679    pub fn saturating_sub(&self, duration: core::time::Duration) -> DateTime {
680        match self.checked_sub(duration) {
681            Ok(dt) => dt,
682            Err(_) => DateTime::from_raw_checked(MIN_SECS, 0, self.zone)
683                .unwrap_or_else(|_| DateTime::from_raw(MIN_SECS, 0, self.offset, self.zone)),
684        }
685    }
686
687    /// Returns the duration from `earlier` to `self`.
688    ///
689    /// When both values were produced by [`DateTime::now`] or
690    /// [`DateTime::now_in`], the measurement uses the monotonic clock and is
691    /// therefore robust against wall-clock adjustments (for example NTP
692    /// steps). Otherwise the wall clock is used.
693    ///
694    /// # Errors
695    ///
696    /// Returns an error if `earlier` is later than `self`.
697    pub fn duration_since(&self, earlier: DateTime) -> Result<core::time::Duration, Error> {
698        let (seconds, nanos) = self.signed_duration_since(earlier);
699        if seconds < 0 {
700            return Err(error::out_of_range("earlier is later than self"));
701        }
702        Ok(core::time::Duration::new(seconds as u64, nanos))
703    }
704
705    /// Returns the duration from `self` to `later`.
706    ///
707    /// This is the inverse of [`DateTime::duration_since`] and follows the
708    /// same monotonic-clock rules.
709    ///
710    /// # Errors
711    ///
712    /// Returns an error if `later` is earlier than `self`.
713    pub fn duration_until(&self, later: DateTime) -> Result<core::time::Duration, Error> {
714        later.duration_since(*self)
715    }
716
717    /// Returns the signed difference `self - other` as
718    /// `(seconds, subsecond_nanoseconds)`, where the sign is carried by
719    /// `seconds` and the nanosecond component is always in
720    /// `0..=999_999_999`.
721    ///
722    /// The monotonic clock is used when both values carry a reading (see
723    /// [`DateTime::duration_since`]).
724    #[must_use]
725    pub fn signed_duration_since(&self, other: DateTime) -> (i64, u32) {
726        #[cfg(feature = "std")]
727        if let (Some(left), Some(right)) = (self.monotonic, other.monotonic) {
728            return difference_between(left, right);
729        }
730        let mut seconds = self.secs - other.secs;
731        let mut nanos = i64::from(self.nanos) - i64::from(other.nanos);
732        if nanos < 0 {
733            seconds -= 1;
734            nanos += 1_000_000_000;
735        }
736        (seconds, nanos as u32)
737    }
738
739    /// Returns the time elapsed since this value was created.
740    ///
741    /// # Errors
742    ///
743    /// Returns an error if the system clock reports a value before this one
744    /// (only possible without a monotonic reading).
745    #[cfg(feature = "std")]
746    pub fn elapsed(&self) -> Result<core::time::Duration, Error> {
747        DateTime::now_in(self.zone).duration_since(*self)
748    }
749
750    /// Returns `true` if this value carries a monotonic clock reading, which
751    /// [`DateTime::now`] and [`DateTime::now_in`] add so that elapsed-time
752    /// measurement is robust against wall-clock adjustments.
753    #[cfg(feature = "std")]
754    #[must_use]
755    pub const fn has_monotonic(&self) -> bool {
756        self.monotonic.is_some()
757    }
758
759    /// Returns the time zone abbreviation in effect, such as `EST`, when the
760    /// zone has one (UTC reports `UTC`; fixed offsets have none).
761    #[must_use]
762    pub fn abbreviation(&self) -> Option<&'static str> {
763        self.zone
764            .info_at(self.secs)
765            .map(|info| info.abbrev)
766            .filter(|abbrev| !abbrev.is_empty())
767    }
768
769    /// Returns the bounds of the constant-offset period that contains this
770    /// instant, each as an optional [`DateTime`] where `None` means unbounded
771    /// in that direction.
772    ///
773    /// Bounds are reported for embedded-database zones and only span explicit
774    /// transitions. The database materializes future daylight-saving
775    /// transitions up to a rolling horizon (the IANA release year plus 50), so
776    /// dates beyond that horizon have an unbounded end even while a DST rule
777    /// applies; fixed offsets and UTC are always unbounded.
778    #[must_use]
779    pub fn zone_bounds(&self) -> (Option<DateTime>, Option<DateTime>) {
780        let (start, end) = self.zone.bounds_at(self.secs);
781        (
782            start.and_then(|secs| DateTime::from_raw_checked(secs, 0, self.zone).ok()),
783            end.and_then(|secs| DateTime::from_raw_checked(secs, 0, self.zone).ok()),
784        )
785    }
786
787    /// Adds whole calendar days, preserving the local time of day across DST
788    /// transitions.
789    ///
790    /// # Errors
791    ///
792    /// Returns an error if the result is out of range.
793    pub fn add_days(&self, days: i64) -> Result<DateTime, Error> {
794        let (year, month, day, hour, minute, second, nanos) = self.parts();
795        let target = civil::days_from_civil(year, month, day)
796            .checked_add(days)
797            .ok_or_else(|| error::out_of_range("result is outside the supported range"))?;
798        let (year, month, day) = civil::civil_from_days(target);
799        DateTime::from_parts(year, month, day, hour, minute, second, nanos, self.zone)
800    }
801
802    /// Adds whole calendar months, clamping the day to the end of the target
803    /// month.
804    ///
805    /// # Errors
806    ///
807    /// Returns an error if the result is out of range.
808    pub fn add_months(&self, months: i32) -> Result<DateTime, Error> {
809        let (year, month, day, hour, minute, second, nanos) = self.parts();
810        let total = i64::from(year) * 12 + i64::from(month) - 1 + i64::from(months);
811        let new_year = i32::try_from(total.div_euclid(12))
812            .map_err(|_| error::out_of_range("result is outside the supported range"))?;
813        let new_month = (total.rem_euclid(12) + 1) as u8;
814        let new_day = day.min(civil::days_in_month(new_year, new_month));
815        DateTime::from_parts(new_year, new_month, new_day, hour, minute, second, nanos, self.zone)
816    }
817
818    /// Adds whole calendar years, clamping February 29 to February 28 in
819    /// non-leap years.
820    ///
821    /// # Errors
822    ///
823    /// Returns an error if the result is out of range.
824    pub fn add_years(&self, years: i32) -> Result<DateTime, Error> {
825        let (year, month, day, hour, minute, second, nanos) = self.parts();
826        let new_year = year
827            .checked_add(years)
828            .ok_or_else(|| error::out_of_range("result is outside the supported range"))?;
829        let new_day = day.min(civil::days_in_month(new_year, month));
830        DateTime::from_parts(new_year, month, new_day, hour, minute, second, nanos, self.zone)
831    }
832
833    fn in_timezone_checked(&self, zone: TimeZone) -> Result<DateTime, Error> {
834        if self.secs < MIN_SECS || self.secs > MAX_SECS {
835            return Err(error::out_of_range("instant is outside the supported range"));
836        }
837        Ok(self.in_timezone(zone))
838    }
839}
840
841/// The signed difference between two monotonic readings, normalized so the
842/// nanosecond component is non-negative.
843#[cfg(feature = "std")]
844fn difference_between(left: std::time::Instant, right: std::time::Instant) -> (i64, u32) {
845    if let Some(duration) = left.checked_duration_since(right) {
846        (duration.as_secs() as i64, duration.subsec_nanos())
847    } else if let Some(duration) = right.checked_duration_since(left) {
848        let seconds = duration.as_secs() as i64;
849        if duration.subsec_nanos() == 0 {
850            (-seconds, 0)
851        } else {
852            (-seconds - 1, 1_000_000_000 - duration.subsec_nanos())
853        }
854    } else {
855        (0, 0)
856    }
857}
858
859impl Default for DateTime {
860    #[inline]
861    fn default() -> DateTime {
862        DateTime::UNIX_EPOCH
863    }
864}
865
866impl PartialEq for DateTime {
867    #[inline]
868    fn eq(&self, other: &DateTime) -> bool {
869        self.secs == other.secs && self.nanos == other.nanos
870    }
871}
872
873impl Eq for DateTime {}
874
875impl PartialOrd for DateTime {
876    #[inline]
877    fn partial_cmp(&self, other: &DateTime) -> Option<Ordering> {
878        Some(self.cmp(other))
879    }
880}
881
882impl Ord for DateTime {
883    #[inline]
884    fn cmp(&self, other: &DateTime) -> Ordering {
885        (self.secs, self.nanos).cmp(&(other.secs, other.nanos))
886    }
887}
888
889impl Hash for DateTime {
890    #[inline]
891    fn hash<H: Hasher>(&self, state: &mut H) {
892        self.secs.hash(state);
893        self.nanos.hash(state);
894    }
895}
896
897impl fmt::Display for DateTime {
898    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
899        format::write_datetime(f, self)
900    }
901}
902
903impl fmt::Debug for DateTime {
904    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
905        fmt::Display::fmt(self, f)
906    }
907}
908
909impl core::str::FromStr for DateTime {
910    type Err = Error;
911
912    fn from_str(s: &str) -> Result<DateTime, Error> {
913        parse::parse(s, None)
914    }
915}
916
917impl core::ops::Add<core::time::Duration> for DateTime {
918    type Output = DateTime;
919
920    /// Adds a duration, panicking if the result is out of range.
921    fn add(self, rhs: core::time::Duration) -> DateTime {
922        self.checked_add(rhs).expect("DateTime + Duration overflowed")
923    }
924}
925
926impl core::ops::Sub<core::time::Duration> for DateTime {
927    type Output = DateTime;
928
929    /// Subtracts a duration, panicking if the result is out of range.
930    fn sub(self, rhs: core::time::Duration) -> DateTime {
931        self.checked_sub(rhs).expect("DateTime - Duration overflowed")
932    }
933}
934
935impl core::ops::AddAssign<core::time::Duration> for DateTime {
936    fn add_assign(&mut self, rhs: core::time::Duration) {
937        *self = *self + rhs;
938    }
939}
940
941impl core::ops::SubAssign<core::time::Duration> for DateTime {
942    fn sub_assign(&mut self, rhs: core::time::Duration) {
943        *self = *self - rhs;
944    }
945}
946
947#[cfg(feature = "std")]
948impl TryFrom<std::time::SystemTime> for DateTime {
949    type Error = Error;
950
951    /// Converts a [`SystemTime`](std::time::SystemTime) into a `DateTime`.
952    ///
953    /// # Errors
954    ///
955    /// Returns an error if the instant is outside the supported range.
956    fn try_from(value: std::time::SystemTime) -> Result<DateTime, Error> {
957        let (seconds, nanos) = match value.duration_since(std::time::UNIX_EPOCH) {
958            Ok(duration) => {
959                let seconds = i64::try_from(duration.as_secs())
960                    .map_err(|_| error::out_of_range("instant is outside the supported range"))?;
961                (seconds, duration.subsec_nanos())
962            }
963            Err(err) => {
964                let duration = err.duration();
965                let seconds = i64::try_from(duration.as_secs())
966                    .map_err(|_| error::out_of_range("instant is outside the supported range"))?;
967                if duration.subsec_nanos() == 0 {
968                    (-seconds, 0)
969                } else {
970                    (-seconds - 1, 1_000_000_000 - duration.subsec_nanos())
971                }
972            }
973        };
974        DateTime::from_unix_nanos(i128::from(seconds) * 1_000_000_000 + i128::from(nanos))
975    }
976}
977
978#[cfg(feature = "std")]
979impl From<DateTime> for std::time::SystemTime {
980    /// Converts this value into a [`SystemTime`](std::time::SystemTime).
981    ///
982    /// The supported calendar range fits within `SystemTime` on common
983    /// platforms; on platforms with a narrower `SystemTime` this saturates to
984    /// the Unix epoch rather than panicking.
985    fn from(dt: DateTime) -> std::time::SystemTime {
986        let nanos = dt.unix_nanos();
987        if nanos >= 0 {
988            let seconds = (nanos / 1_000_000_000) as u64;
989            let subsec = (nanos % 1_000_000_000) as u32;
990            std::time::UNIX_EPOCH
991                .checked_add(core::time::Duration::new(seconds, subsec))
992                .unwrap_or(std::time::UNIX_EPOCH)
993        } else {
994            let magnitude = -nanos;
995            let seconds = (magnitude / 1_000_000_000) as u64;
996            let subsec = (magnitude % 1_000_000_000) as u32;
997            std::time::UNIX_EPOCH
998                .checked_sub(core::time::Duration::new(seconds, subsec))
999                .unwrap_or(std::time::UNIX_EPOCH)
1000        }
1001    }
1002}