tai_time/
lib.rs

1//! A nanosecond-precision monotonic clock timestamp based on the TAI time
2//! standard.
3//!
4//! # Overview
5//!
6//! While Rust's standard library already provides the [`std::time::Instant`]
7//! monotonic timestamp, its absolute value is opaque. In many scientific and
8//! engineering applications such as simulations, GNSS and synchronized systems,
9//! monotonic timestamps based on absolute time references are required.
10//!
11//! This crate provides a fairly unopinionated timestamp for such applications
12//! with a focus on simplicity, adherence to Rust's `std::time` idioms and
13//! interoperability with the [`std::time::Duration`] type.
14//!
15//! A [`TaiTime`] timestamp specifies a [TAI] point in time. It is represented
16//! as a 64-bit signed number of seconds and a positive number of nanoseconds,
17//! relative to 1970-01-01 00:00:00 TAI or to any arbitrary epoch. This
18//! timestamp format has a number of desirable properties:
19//!
20//! - it is computationally efficient for arithmetic operations involving the
21//!   standard [`Duration`] type, which uses a very similar internal
22//!   representation,
23//! - when a 1970 epoch is chosen (see [`MonotonicTime`]):
24//!   * exact conversion to a Unix timestamp is trivial and only requires
25//!     subtracting from this timestamp the number of leap seconds between TAI
26//!     and UTC time,
27//!   * it constitutes a strict 96-bit superset of 80-bit PTP IEEE-1588
28//!     timestamps, a widely used standard for high-precision time distribution,
29//!   * it is substantially similar (though not strictly identical) to the
30//!     [TAI64N] time format,
31//! - with a custom epoch, other monotonic clocks such as the Global Position
32//!   System clock, the Galileo System Time clock and the BeiDou Time clock can
33//!   be represented (see [`GpsTime`], [`GstTime`], [`BdtTime`], [`Tai1958Time`]
34//!   and [`Tai1972Time`]).
35//!
36//! [`MonotonicTime`], an alias for [`TaiTime`] with an epoch set at 1970-01-01
37//! 00:00:00 TAI, is the recommended timestamp choice when no specific epoch is
38//! mandated.
39//!
40//! On systems where `std` is present, [`TaiClock`] can generate TAI timestamps
41//! based on the monotonic system clock. On platforms that support it
42//! (currently, only Linux), the native TAI system clock time can be retrieved
43//! with [`TaiTime::now`].
44//!
45//! [TAI]: https://en.wikipedia.org/wiki/International_Atomic_Time
46//! [TAI64N]: https://cr.yp.to/libtai/tai64.html
47//!
48//!
49//! # Design choices and limitations
50//!
51//! Leap seconds are never automatically computed during conversion to/from
52//! UTC-based timestamps. This is intentional: since leap seconds cannot be
53//! predicted far in the future, any attempt to "hide" their existence from user
54//! code would lend a false sense of security and, down the line, would make it
55//! more difficult to identify failures subsequent to the introduction of new
56//! leap seconds.
57//!
58//!
59//! # Features flags
60//!
61//! ### Support for `no-std`
62//!
63//! By default, this crate enables the `std` feature to access the operating
64//! system clock and allow conversion to/from `time::SystemTime`. It can be made
65//! `no-std`-compatible by specifying `default-features = false`.
66//!
67//! ### Support for time-related crates
68//!
69//! Conversion methods to and from UTC date-time stamps from the [chrono] crate
70//! are available with the `chrono` feature.
71//!
72//! [chrono]: https://crates.io/crates/chrono
73//!
74//! ### TAI system clock
75//!
76//! On Linux only, it is possible to read TAI time from the system clock by
77//! activating the `tai_clock` feature. Be sure to read about possible caveats
78//! in [`TaiTime::now`].
79//!
80//! ### Serialization
81//!
82//! `TaiTime` and related error types can be (de)serialized with `serde` by
83//! activating the `serde` feature.
84//!
85//! ### `defmt` support
86//!
87//! Activating the `defmt` feature will derive the
88//! [`defmt::Format`](https://defmt.ferrous-systems.com/format) trait on
89//! `TaiTime` and related error types.
90//!
91//! ### JSON Schema
92//!
93//! Activating the `schemars` feature will derive the
94//! [`schemars::JsonSchema`](https://docs.rs/schemars/latest/schemars/) trait
95//! on `TaiTime`.
96//!  
97//! # Examples
98//!
99//! Basic usage:
100//!
101//! ```
102//! use tai_time::{GpsTime, MonotonicClock, MonotonicTime};
103//!
104//! // A timestamp dated 2009-02-13 23:31:30.987654321 TAI.
105//! // (same value as Unix timestamp for 2009-02-13 23:31:30.987654321 UTC).
106//! let t0 = MonotonicTime::new(1_234_567_890, 987_654_321).unwrap();
107//!
108//! // Current TAI time based on the system clock, assuming 37 leap seconds.
109//! let clock = MonotonicClock::init_from_utc(37);
110//! let t1 = clock.now();
111//! println!("Current TAI time: {}", t1);
112//!
113//! // Elapsed time between `t0` and `t1`.
114//! let dt = t1.duration_since(t0);
115//! println!("t1 -t0: {}s, {}ns", dt.as_secs(), dt.subsec_nanos());
116//!
117//! // Elapsed time since `t1`.
118//! let dt = clock.now().duration_since(t1);
119//! println!("Elapsed: {}s, {}ns", dt.as_secs(), dt.subsec_nanos());
120//!
121//! // Print out `t1` as a GPS timestamp.
122//! let gps_t1: GpsTime = t1.to_tai_time().unwrap();
123//! println!("GPS timestamp: {}s, {}ns", gps_t1.as_secs(), gps_t1.subsec_nanos());
124//! ```
125//!
126//! Construction from date-time fields and date-time strings:
127//!
128//! ```
129//! use tai_time::{MonotonicTime, Tai1958Time};
130//!
131//! let t0 = MonotonicTime::try_from_date_time(2222, 11, 11, 12, 34, 56, 789000000).unwrap();
132//!
133//! // The `FromStr` implementation accepts date-time stamps with the format:
134//! // [±][Y]...[Y]YYYY-MM-DD hh:mm:ss[.d[d]...[d]]
135//! // or:
136//! // [±][Y]...[Y]YYYY-MM-DD'T'hh:mm:ss[.d[d]...[d]]
137//! assert_eq!("2222-11-11 12:34:56.789".parse(), Ok(t0));
138//! ```
139//!
140//! Formatted display as date-time:
141//!
142//! ```
143//! use tai_time::MonotonicTime;
144//!
145//! let t0 = MonotonicTime::try_from_date_time(1234, 12, 13, 14, 15, 16, 123456000).unwrap();
146//!
147//! assert_eq!(
148//!     format!("{}", t0),
149//!     "1234-12-13 14:15:16.123456"
150//! );
151//! assert_eq!(
152//!     format!("{:.0}", t0),
153//!     "1234-12-13 14:15:16"
154//! );
155//! assert_eq!(
156//!     format!("{:.3}", t0),
157//!     "1234-12-13 14:15:16.123"
158//! );
159//! assert_eq!(
160//!     format!("{:.9}", t0),
161//!     "1234-12-13 14:15:16.123456000"
162//! );
163//! ```
164//!
165//! Reading TAI time directly from the system clock (Linux-only, requires
166//! feature `tai_clock`):
167//!
168//! ```
169//! use tai_time::MonotonicTime;
170//!
171//! # #[cfg(all(
172//! #     feature = "tai_clock",
173//! #     any(
174//! #         target_os = "android",
175//! #         target_os = "emscripten",
176//! #         target_os = "fuchsia",
177//! #         target_os = "linux"
178//! #     )
179//! # ))]
180//! # {
181//! let now = MonotonicTime::now();
182//!
183//! println!("Current TAI time: {}", now);
184//! # }
185//! ```
186#![warn(missing_docs, missing_debug_implementations, unreachable_pub)]
187#![cfg_attr(not(feature = "std"), no_std)]
188#![cfg_attr(docsrs, feature(doc_cfg))]
189
190mod date_time;
191mod errors;
192#[cfg(feature = "std")]
193mod tai_clock;
194
195use core::fmt;
196use core::ops::{Add, AddAssign, Sub, SubAssign};
197use core::str::FromStr;
198use core::time::Duration;
199
200use date_time::*;
201pub use errors::{DateTimeError, OutOfRangeError, ParseDateTimeError};
202#[cfg(feature = "std")]
203pub use tai_clock::*;
204
205const NANOS_PER_SEC: u32 = 1_000_000_000;
206const UNIX_EPOCH_YEAR: i32 = 1970;
207
208/// Recommended [`TaiTime`] alias for the general case, using an epoch set at
209/// 1970-01-01 00:00:00 TAI.
210///
211/// The epoch of this timestamp coincides with the PTP epoch as defined by the
212/// IEEE 1588-2008 standard, and with the
213/// [`TAI64`](https://cr.yp.to/libtai/tai64.html) epoch. It is, however,
214/// distinct from the Unix epoch, which is set at 1970-01-01 00:00:00 UTC.
215///
216/// When no specific epoch is required, this timestamp should be considered the
217/// most sensible default as it makes it possible to easily convert TAI
218/// timestamps to Unix timestamps by simple subtraction of the TAI - UTC leap
219/// seconds.
220///
221/// # Examples
222///
223/// ```
224/// use tai_time::MonotonicTime;
225///
226/// // Set the timestamp one nanosecond after the 1970 TAI epoch.
227/// let mut timestamp = MonotonicTime::new(0, 1).unwrap();
228///
229/// assert_eq!(timestamp, "1970-01-01 00:00:00.000000001".parse().unwrap());
230/// ```
231pub type MonotonicTime = TaiTime<0>;
232
233/// A [`TaiTime`] alias using the Global Positioning System (GPS) epoch.
234///
235/// This timestamp is relative to 1980-01-06 00:00:00 UTC (1980-01-06 00:00:19
236/// TAI).
237///
238/// # Examples
239///
240/// ```
241/// use tai_time::GpsTime;
242///
243/// // Set the timestamp one nanosecond after the GPS epoch.
244/// let mut timestamp = GpsTime::new(0, 1).unwrap();
245///
246/// assert_eq!(timestamp, "1980-01-06 00:00:19.000000001".parse().unwrap());
247/// ```
248pub type GpsTime = TaiTime<315_964_819>;
249
250/// A [`TaiTime`] alias using the Galileo System Time (GST) epoch.
251///
252/// This timestamp is relative to 1999-08-21 23:59:47 UTC (1999-08-22 00:00:19
253/// TAI).
254///
255/// # Examples
256///
257/// ```
258/// use tai_time::GstTime;
259///
260/// // Set the timestamp one nanosecond after the GST epoch.
261/// let mut timestamp = GstTime::new(0, 1).unwrap();
262///
263/// assert_eq!(timestamp, "1999-08-22 00:00:19.000000001".parse().unwrap());
264/// ```
265pub type GstTime = TaiTime<935_280_019>;
266
267/// A [`TaiTime`] alias using the BeiDou Time (BDT) epoch.
268///
269/// This timestamp is relative to 2006-01-01 00:00:00 UTC (2006-01-01 00:00:33
270/// TAI).
271///
272/// # Examples
273///
274/// ```
275/// use tai_time::BdtTime;
276///
277/// // Set the timestamp one nanosecond after the BDT epoch.
278/// let mut timestamp = BdtTime::new(0, 1).unwrap();
279///
280/// assert_eq!(timestamp, "2006-01-01 00:00:33.000000001".parse().unwrap());
281/// ```
282pub type BdtTime = TaiTime<1_136_073_633>;
283
284/// A [`TaiTime`] alias using an epoch set at 1958-01-01 00:00:00 TAI.
285///
286/// Timestamps with this epoch are in common use in TAI-based clocks. While most
287/// literature sources consider that this epoch corresponds to 1958-01-01
288/// 00:00:00 UTC without any leap seconds, UTC was not formally defined at that
289/// date and there is no unanimous consensus on this point. Notably, the
290/// `chrono::tai_clock` introduced in C++20 considers that this epoch
291/// corresponds to 1957-12-31 23:59:50 UTC.
292///
293/// # Examples
294///
295/// ```
296/// use tai_time::Tai1958Time;
297///
298/// // Set the timestamp one nanosecond after the 1958 TAI epoch.
299/// let mut timestamp = Tai1958Time::new(0, 1).unwrap();
300///
301/// assert_eq!(timestamp, "1958-01-01 00:00:00.000000001".parse().unwrap());
302/// ```
303pub type Tai1958Time = TaiTime<-378_691_200>;
304
305/// A [`TaiTime`] alias using an epoch set at 1972-01-01 00:00:00 TAI.
306///
307/// Timestamps with this epoch are in common use in TAI-based clocks. The epoch
308/// is exactly 10s in the past of 1972-01-01 00:00:00 UTC.
309///
310/// # Examples
311///
312/// ```
313/// use tai_time::Tai1972Time;
314///
315/// // Set the timestamp one nanosecond after the 1972 TAI epoch.
316/// let mut timestamp = Tai1972Time::new(0, 1).unwrap();
317///
318/// assert_eq!(timestamp, "1972-01-01 00:00:00.000000001".parse().unwrap());
319/// ```
320pub type Tai1972Time = TaiTime<63_072_000>;
321
322/// Nanosecond-precision monotonic clock timestamp parametrized by its epoch.
323///
324/// A timestamp specifies a [TAI] point in time. It is represented as a 64-bit
325/// signed number of seconds and a positive number of nanoseconds, counted with
326/// reference to the epoch specified by the generic parameter.
327///
328/// `EPOCH_REF` defines the epoch via its signed distance in seconds from
329/// 1970-01-01 00:00:00 TAI.
330///
331/// See also: [`MonotonicTime`], [`GpsTime`], [`GstTime`], [`BdtTime`],
332/// [`Tai1958Time`] and [`Tai1972Time`].
333///
334/// [TAI]: https://en.wikipedia.org/wiki/International_Atomic_Time
335///
336/// # Examples
337///
338/// ```
339/// use std::time::Duration;
340/// use tai_time::TaiTime;
341///
342/// // A timestamp type with an epoch at 1970:01:01 00:02:03 TAI.
343/// type MyCustomTime = TaiTime<123>;
344///
345/// // A timestamp set to 2009-02-13 23:33:33.333333333 TAI.
346/// let mut timestamp = MyCustomTime::new(1_234_567_890, 333_333_333).unwrap();
347///
348/// // Increment the timestamp by 123.456s.
349/// timestamp += Duration::new(123, 456_000_000);
350///
351/// assert_eq!(timestamp, MyCustomTime::new(1_234_568_013, 789_333_333).unwrap());
352/// assert_eq!(timestamp.as_secs(), 1_234_568_013);
353/// assert_eq!(timestamp.subsec_nanos(), 789_333_333);
354/// ```
355#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
356#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
357#[cfg_attr(feature = "defmt", derive(defmt::Format))]
358#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
359pub struct TaiTime<const EPOCH_REF: i64> {
360    /// The number of whole seconds in the future (if positive) or in the past
361    /// (if negative) of 1970-01-01 00:00:00 TAI.
362    ///
363    /// Note that the automatic derivation of `PartialOrd` relies on
364    /// lexicographical comparison so the `secs` field must appear before
365    /// `nanos` in declaration order to be given higher priority.
366    secs: i64,
367    /// The sub-second number of nanoseconds in the future of the point in time
368    /// defined by `secs`.
369    #[cfg_attr(feature = "serde", serde(deserialize_with = "validate_nanos"))]
370    nanos: u32,
371}
372
373impl<const EPOCH_REF: i64> TaiTime<EPOCH_REF> {
374    /// Associated constant making it possible to retrieve `EPOCH_REF` from a
375    /// `TaiTime` type alias (used by `TaiClock`).
376    #[cfg(feature = "std")]
377    const EPOCH_REF: i64 = EPOCH_REF;
378
379    /// The reference epoch, which by definition is always a null timestamp.
380    pub const EPOCH: Self = Self { secs: 0, nanos: 0 };
381
382    /// The minimum possible `TaiTime` timestamp.
383    pub const MIN: Self = Self {
384        secs: i64::MIN,
385        nanos: 0,
386    };
387
388    /// The maximum possible `TaiTime` timestamp.
389    pub const MAX: Self = Self {
390        secs: i64::MAX,
391        nanos: NANOS_PER_SEC - 1,
392    };
393
394    /// Creates a timestamp from its parts.
395    ///
396    /// The number of seconds is relative to the epoch. The number of
397    /// nanoseconds is always positive and always points towards the future.
398    ///
399    /// Returns `None` if the number of nanoseconds is greater than 999 999 999.
400    ///
401    /// # Example
402    ///
403    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
404    ///
405    /// ```
406    /// use std::time::Duration;
407    /// use tai_time::MonotonicTime;
408    ///
409    /// // A timestamp set to 2009-02-13 23:31:30.987654321 TAI.
410    /// let timestamp = MonotonicTime::new(1_234_567_890, 987_654_321).unwrap();
411    ///
412    /// // A timestamp set 0.5s in the past of the epoch.
413    /// let timestamp = MonotonicTime::new(-1, 500_000_000).unwrap();
414    /// assert_eq!(timestamp, MonotonicTime::EPOCH - Duration::from_millis(500));
415    /// ```
416    pub const fn new(secs: i64, subsec_nanos: u32) -> Option<Self> {
417        if subsec_nanos >= NANOS_PER_SEC {
418            return None;
419        }
420
421        Some(Self {
422            secs,
423            nanos: subsec_nanos,
424        })
425    }
426
427    /// Creates a timestamp from the TAI system clock.
428    ///
429    /// This is currently only supported on Linux and relies on the
430    /// `clock_gettime` system call with a `CLOCK_TAI` clock ID.
431    ///
432    /// The use of the Linux TAI clock is subject to several caveats, most
433    /// importantly:
434    ///
435    /// 1) On many default-configured Linux systems, the offset between TAI and
436    ///    UTC is arbitrarily set to 0 at boot time, in which case the TAI
437    ///    system clock will actually only differ from UTC time by the number of
438    ///    leap seconds introduced *after* the system was booted (most likely,
439    ///    0).
440    /// 2) Some systems are configured to perform *leap second smearing* by
441    ///    altering the rate of the system clock over a 24h period so as to
442    ///    avoid the leap second discontinuity; this entirely defeats the
443    ///    purpose of the TAI clock which becomes effectively synchronized to
444    ///    the (leap-smeared) UTC system clock.
445    ///
446    /// The first issue can be easily remedied, however, by using `chrony` and,
447    /// if necessary, making sure that the `leapsectz` parameter in
448    /// `chrony.conf` is set to `right/UTC`. Alternatively, one can specify the
449    /// `leapfile` path in `ntp.conf` or set the TAI offset directly with a call
450    /// to `adjtimex` or `ntp_adjtime`.
451    ///
452    /// # Panics
453    ///
454    /// Panics if the result is outside the representable range for the
455    /// resulting timestamp. This is a highly unlikely occurrence since the
456    /// range of a `TaiTime` spans more than ±292 billion years from its epoch.
457    ///
458    /// See [`try_now`](Self::try_now) for a non-panicking alternative.
459    ///
460    /// Note that this constructor will never fail with the [`MonotonicTime`]
461    /// alias as it shares the same epoch as the TAI system clock.
462    #[cfg(all(
463        feature = "tai_clock",
464        any(
465            target_os = "android",
466            target_os = "emscripten",
467            target_os = "fuchsia",
468            target_os = "linux"
469        )
470    ))]
471    pub fn now() -> Self {
472        Self::try_now().expect("overflow when converting timestamp")
473    }
474
475    /// Creates a timestamp from the TAI system clock.
476    ///
477    /// This is a non-panicking alternative to [`now`](Self::now).
478    ///
479    /// When using the [`MonotonicTime`] alias, use [`now`](Self::now) since it
480    /// will never fail.
481    ///
482    /// Returns an error if the result is outside the representable range for
483    /// the resulting timestamp. This is a highly unlikely occurrence since the
484    /// range of a `TaiTime` spans more than ±292 billion years from its epoch.
485    #[cfg(all(
486        feature = "tai_clock",
487        any(
488            target_os = "android",
489            target_os = "emscripten",
490            target_os = "fuchsia",
491            target_os = "linux"
492        )
493    ))]
494    pub fn try_now() -> Result<Self, OutOfRangeError> {
495        let time = nix::time::clock_gettime(nix::time::ClockId::CLOCK_TAI)
496            .expect("unexpected error while calling clock_gettime");
497
498        #[allow(clippy::useless_conversion)]
499        let secs: i64 = time.tv_sec().try_into().unwrap();
500        #[allow(clippy::useless_conversion)]
501        let subsec_nanos: u32 = time.tv_nsec().try_into().unwrap();
502
503        // The timestamp _should_ have the same epoch as `MonotonicTime`, i.e.
504        // 1970-01-01 00:00:00 TAI.
505        let t = MonotonicTime::new(secs, subsec_nanos).unwrap();
506
507        t.to_tai_time().ok_or(OutOfRangeError(()))
508    }
509
510    /// Creates a timestamp from the UTC system clock.
511    ///
512    /// This is a shorthand for `from_system_time(&SystemTime::now(),
513    /// leap_secs)`.
514    ///
515    /// The argument is the difference between TAI and UTC time in seconds
516    /// (a.k.a. leap seconds) applicable at the date represented by the
517    /// timestamp. For reference, this offset has been +37s since 2017-01-01, a
518    /// value which is to remain valid until at least 2026-12-28. See the
519    /// [official IERS bulletin
520    /// C](http://hpiers.obspm.fr/iers/bul/bulc/bulletinc.dat) for leap second
521    /// announcements or the [IERS
522    /// table](https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat) for
523    /// current and historical values.
524    ///
525    /// Beware that the behavior of the system clock near a leap second
526    /// shouldn't be relied upon, where *near* might actually stand for the
527    /// whole 24h period preceding a leap second due to the possible use of the
528    /// so-called *leap second smearing* strategy.
529    ///
530    /// See also: [`from_system_time`](Self::from_system_time).
531    ///
532    /// # Panics
533    ///
534    /// Panics if the result is outside the representable range for the
535    /// resulting timestamp. This is a highly unlikely occurrence since the
536    /// range of a `TaiTime` spans more than ±292 billion years from its epoch.
537    ///
538    /// See [`try_now_from_utc`](Self::try_now_from_utc) for a non-panicking
539    /// alternative.
540    ///
541    /// # Examples
542    ///
543    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
544    ///
545    /// ```
546    /// use tai_time::MonotonicTime;
547    ///
548    /// // Compute the current timestamp assuming that the current difference
549    /// // between TAI and UTC time is 37s.
550    /// let timestamp = MonotonicTime::now_from_utc(37);
551    /// ```
552    #[cfg(feature = "std")]
553    pub fn now_from_utc(leap_secs: i64) -> Self {
554        Self::from_system_time(&std::time::SystemTime::now(), leap_secs)
555    }
556
557    /// Creates a timestamp from the UTC system clock.
558    ///
559    /// This is a non-panicking alternative to
560    /// [now_from_utc](Self::now_from_utc).
561    ///
562    /// Returns an error if the result is outside the representable range for
563    /// the resulting timestamp. This is a highly unlikely occurrence since the
564    /// range of a `TaiTime` spans more than ±292 billion years from its epoch.
565    #[cfg(feature = "std")]
566    pub fn try_now_from_utc(leap_secs: i64) -> Result<Self, OutOfRangeError> {
567        Self::try_from_system_time(&std::time::SystemTime::now(), leap_secs)
568    }
569
570    /// Creates a timestamp from a date-time representation.
571    ///
572    /// The first argument is the proleptic Gregorian year. It follows the ISO
573    /// 8601 interpretation of year 0 as year 1 BC.
574    ///
575    /// Other arguments follow the usual calendar convention, with month and day
576    /// numerals starting at 1.
577    ///
578    /// Note that the proleptic Gregorian calendar extrapolates dates before
579    /// 1582 using the conventional leap year rules, and considers year 0 as a
580    /// leap year. Proleptic Gregorian dates may therefore differ from those of
581    /// the Julian calendar.
582    ///
583    /// Returns an error if any of the arguments is invalid, or if the
584    /// calculated timestamp is outside the representable range.
585    ///
586    /// May also return an error if the result is outside the representable
587    /// range for the resulting timestamp. This is a highly unlikely occurrence
588    /// since the range of a `TaiTime` spans more than ±292 billion years from
589    /// its epoch, and is impossible with the provided `TaiTime` aliases
590    /// ([`MonotonicTime`], [`GpsTime`], [`GstTime`], [`BdtTime`],
591    /// [`Tai1958Time`] or [`Tai1972Time`]).
592    ///
593    ///
594    /// # Example
595    ///
596    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
597    ///
598    /// ```
599    /// use std::time::Duration;
600    /// use tai_time::MonotonicTime;
601    ///
602    /// // A timestamp set to 2009-02-13 23:31:30.987654321 TAI.
603    /// let timestamp = MonotonicTime::try_from_date_time(2009, 2, 13, 23, 31, 30, 987_654_321);
604    /// assert_eq!(timestamp, Ok(MonotonicTime::new(1_234_567_890, 987_654_321).unwrap()));
605    /// ```
606    pub const fn try_from_date_time(
607        year: i32,
608        month: u8,
609        day: u8,
610        hour: u8,
611        min: u8,
612        sec: u8,
613        nano: u32,
614    ) -> Result<Self, DateTimeError> {
615        if month < 1 || month > 12 {
616            return Err(DateTimeError::InvalidMonth(month));
617        }
618        if day < 1 || day > days_in_month(year, month) {
619            return Err(DateTimeError::InvalidDayOfMonth(day));
620        }
621        if hour > 23 {
622            return Err(DateTimeError::InvalidHour(hour));
623        }
624        if min > 59 {
625            return Err(DateTimeError::InvalidMinute(min));
626        }
627        if sec > 59 {
628            return Err(DateTimeError::InvalidSecond(sec));
629        }
630        if nano > NANOS_PER_SEC {
631            return Err(DateTimeError::InvalidNanosecond(nano));
632        }
633
634        let days = days_from_year_0(year) - days_from_year_0(UNIX_EPOCH_YEAR)
635            + day_of_year(year, month, day) as i64;
636
637        // Note that the following cannot overflow since `days` cannot be
638        // greater than approx. ±365.25*2^31.
639        let secs = days * 86400 + hour as i64 * 3600 + min as i64 * 60 + sec as i64;
640
641        if let Some(secs) = secs.checked_sub(EPOCH_REF) {
642            Ok(Self { secs, nanos: nano })
643        } else {
644            Err(DateTimeError::OutOfRange)
645        }
646    }
647
648    /// Creates a TAI timestamp from a Unix timestamp.
649    ///
650    /// The last argument is the difference between TAI and UTC time in seconds
651    /// (a.k.a. leap seconds) applicable at the date represented by the
652    /// timestamp. For reference, this offset has been +37s since 2017-01-01, a
653    /// value which is to remain valid until at least 2026-12-28. See the
654    /// [official IERS bulletin
655    /// C](http://hpiers.obspm.fr/iers/bul/bulc/bulletinc.dat) for leap second
656    /// announcements or the [IERS
657    /// table](https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat) for
658    /// current and historical values.
659    ///
660    /// This method tolerates Unix timestamps that account for leap seconds by
661    /// adding them to the nanosecond field: all nanoseconds in excess of
662    /// 999 999 999 will carry over into the seconds.
663    ///
664    /// Note that there is no unanimous consensus regarding the conversion
665    /// between TAI and Unix timestamps prior to 1972.
666    ///
667    /// # Panics
668    ///
669    /// Panics if the result is outside the representable range for the
670    /// resulting timestamp. This is a highly unlikely occurrence since the
671    /// range of a `TaiTime` spans more than ±292 billion years from its epoch.
672    ///
673    /// See [`try_from_unix_timestamp`](Self::try_from_unix_timestamp) for a
674    /// non-panicking alternative.
675    ///
676    /// # Examples
677    ///
678    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
679    ///
680    /// ```
681    /// use tai_time::MonotonicTime;
682    ///
683    /// // Creates a timestamp corresponding to 2001-09-15 05:05:00.005 UTC,
684    /// // accounting for the +32s difference between UAI and UTC on 2001-09-15.
685    /// assert_eq!(
686    ///     MonotonicTime::from_unix_timestamp(1_000_530_300, 5_000_000, 32),
687    ///     MonotonicTime::new(1_000_530_332, 5_000_000).unwrap()
688    /// );
689    /// ```
690    pub const fn from_unix_timestamp(secs: i64, subsec_nanos: u32, leap_secs: i64) -> Self {
691        if let Ok(timestamp) = Self::try_from_unix_timestamp(secs, subsec_nanos, leap_secs) {
692            return timestamp;
693        }
694
695        panic!("overflow when converting timestamp");
696    }
697
698    /// Creates a TAI timestamp from a Unix timestamp.
699    ///
700    /// This is a non-panicking alternative to
701    /// [from_unix_timestamp](Self::from_unix_timestamp).
702    ///
703    /// Returns an error if the result is outside the representable range for
704    /// the resulting timestamp. This is a highly unlikely occurrence since the
705    /// range of a `TaiTime` spans more than ±292 billion years from its epoch.
706    pub const fn try_from_unix_timestamp(
707        secs: i64,
708        subsec_nanos: u32,
709        leap_secs: i64,
710    ) -> Result<Self, OutOfRangeError> {
711        // Carry over into the seconds all seconds in excess of 1s. This is
712        // useful e.g. with `chrono` which adds leap seconds to the nanoseconds.
713        let (secs_carry, subsec_nanos) = if subsec_nanos < NANOS_PER_SEC {
714            (0, subsec_nanos)
715        } else {
716            let secs = subsec_nanos / NANOS_PER_SEC;
717
718            (secs as i64, subsec_nanos - secs * NANOS_PER_SEC)
719        };
720
721        if let Some(secs) = secs.checked_add(secs_carry)
722            && let Some(secs) = secs.checked_add(leap_secs)
723            && let Some(secs) = secs.checked_sub(EPOCH_REF)
724        {
725            return Ok(Self {
726                secs,
727                nanos: subsec_nanos,
728            });
729        }
730
731        Err(OutOfRangeError(()))
732    }
733
734    /// Creates a TAI timestamp from a `SystemTime` timestamp.
735    ///
736    /// The last argument is the difference between TAI and UTC time in seconds
737    /// (a.k.a. leap seconds) applicable at the date represented by the
738    /// timestamp. For reference, this offset has been +37s since 2017-01-01, a
739    /// value which is to remain valid until at least 2026-12-28. See the
740    /// [official IERS bulletin
741    /// C](http://hpiers.obspm.fr/iers/bul/bulc/bulletinc.dat) for leap second
742    /// announcements or the [IERS
743    /// table](https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat) for
744    /// current and historical values.
745    ///
746    /// # Panics
747    ///
748    /// Panics if the result is outside the representable range for the
749    /// resulting timestamp. This is a highly unlikely occurrence since the
750    /// range of a `TaiTime` spans more than ±292 billion years from its epoch.
751    ///
752    /// See [`try_from_system_time`](Self::try_from_system_time) for a
753    /// non-panicking alternative.
754    ///
755    /// # Examples
756    ///
757    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
758    ///
759    /// ```
760    /// use std::time::{Duration, SystemTime};
761    /// use tai_time::MonotonicTime;
762    ///
763    /// // Creates a timestamp corresponding to 2001-09-15 05:05:00.005 UTC,
764    /// // accounting for the +32s difference between UAI and UTC on 2001-09-15.
765    /// let system_time = SystemTime::UNIX_EPOCH + Duration::new(1_000_530_300, 5_000_000);
766    /// assert_eq!(
767    ///     MonotonicTime::from_system_time(&system_time, 32),
768    ///     MonotonicTime::new(1_000_530_332, 5_000_000).unwrap()
769    /// );
770    /// ```
771    #[cfg(feature = "std")]
772    pub fn from_system_time(system_time: &std::time::SystemTime, leap_secs: i64) -> Self {
773        Self::try_from_system_time(system_time, leap_secs)
774            .expect("overflow when converting timestamp")
775    }
776
777    /// Creates a TAI timestamp from a `SystemTime` timestamp.
778    ///
779    /// This is a non-panicking alternative to
780    /// [`from_system_time`](Self::from_system_time).
781    ///
782    /// Returns an error if the result is outside the representable range for
783    /// the resulting timestamp. This is a highly unlikely occurrence since the
784    /// range of a `TaiTime` spans more than ±292 billion years from its epoch.
785    #[cfg(feature = "std")]
786    pub fn try_from_system_time(
787        system_time: &std::time::SystemTime,
788        leap_secs: i64,
789    ) -> Result<Self, OutOfRangeError> {
790        let unix_time = system_time
791            .duration_since(std::time::SystemTime::UNIX_EPOCH)
792            .unwrap();
793
794        leap_secs
795            .checked_sub(EPOCH_REF)
796            .and_then(|secs| Self::new(secs, 0).unwrap().checked_add(unix_time))
797            .ok_or(OutOfRangeError(()))
798    }
799
800    /// Creates a timestamp from a `chrono::DateTime`.
801    ///
802    /// The argument is the difference between TAI and UTC time in seconds
803    /// (a.k.a. leap seconds) applicable at the date represented by the
804    /// timestamp. For reference, this offset has been +37s since 2017-01-01, a
805    /// value which is to remain valid until at least 2026-12-28. See the
806    /// [official IERS bulletin
807    /// C](http://hpiers.obspm.fr/iers/bul/bulc/bulletinc.dat) for leap second
808    /// announcements or the [IERS
809    /// table](https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat) for
810    /// current and historical values.
811    ///
812    /// While no error will be reported, this method should not be considered
813    /// appropriate for timestamps in the past of 1972.
814    ///
815    /// # Panics
816    ///
817    /// Panics if the result is outside the representable range for the
818    /// resulting timestamp. This is a highly unlikely occurrence since the
819    /// range of a `TaiTime` spans more than ±292 billion years from its epoch
820    /// while the range of a `chrono::DateTime` spans from year -262144 to year
821    /// 262143.
822    ///
823    /// See [`try_from_chrono_date_time`](Self::try_from_chrono_date_time) for a
824    /// non-panicking alternative.
825    ///
826    /// Note that the conversion will never fail with the provided `TaiTime`
827    /// aliases ([`MonotonicTime`], [`GpsTime`], [`GstTime`], [`BdtTime`],
828    /// [`Tai1958Time`] or [`Tai1972Time`]).
829    ///
830    ///
831    /// # Examples
832    ///
833    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
834    ///
835    /// ```
836    /// use tai_time::MonotonicTime;
837    /// use chrono::DateTime;
838    ///
839    /// let tai_date_time: MonotonicTime = "2001-09-15 05:05:32.005".parse().unwrap();
840    /// let chrono_date_time = DateTime::parse_from_rfc3339("2001-09-15T05:05:00.005Z").unwrap();
841    ///
842    /// assert_eq!(
843    ///     MonotonicTime::from_chrono_date_time(&chrono_date_time, 32),
844    ///     tai_date_time
845    /// );
846    /// ```
847    #[cfg(feature = "chrono")]
848    pub const fn from_chrono_date_time<Tz: chrono::TimeZone>(
849        date_time: &chrono::DateTime<Tz>,
850        leap_secs: i64,
851    ) -> Self {
852        if let Ok(timestamp) = Self::try_from_chrono_date_time(date_time, leap_secs) {
853            return timestamp;
854        }
855
856        panic!("overflow when converting timestamp");
857    }
858
859    /// Creates a timestamp from a `chrono::DateTime`.
860    ///
861    /// This is a non-panicking alternative to
862    /// [`from_chrono_date_time`](Self::from_chrono_date_time).
863    ///
864    /// When using the provided `TaiTime` aliases ([`MonotonicTime`],
865    /// [`GpsTime`], [`GstTime`], [`BdtTime`], [`Tai1958Time`] or
866    /// [`Tai1972Time`]), use
867    /// [`from_chrono_date_time`](Self::from_chrono_date_time) since the
868    /// conversion will never fail.
869    ///
870    /// Returns an error if the result is outside the representable range for
871    /// the resulting timestamp. This is a highly unlikely occurrence since the
872    /// range of a `TaiTime` spans more than ±292 billion years from its epoch
873    /// while the range of a `chrono::DateTime` spans from year -262144 to year
874    /// 262143.
875    #[cfg(feature = "chrono")]
876    pub const fn try_from_chrono_date_time<Tz: chrono::TimeZone>(
877        date_time: &chrono::DateTime<Tz>,
878        leap_secs: i64,
879    ) -> Result<Self, OutOfRangeError> {
880        // Note: `try_from_unix_timestamp` takes care of carrying over
881        // nanoseconds in excess of 1, which `chrono` generates in case of leap
882        // seconds.
883        Self::try_from_unix_timestamp(
884            date_time.timestamp(),
885            date_time.timestamp_subsec_nanos(),
886            leap_secs,
887        )
888    }
889
890    /// Returns the signed value of the closest second boundary that is equal to
891    /// or lower than the timestamp, relative to the [`EPOCH`](TaiTime::EPOCH).
892    ///
893    /// This value is the same as the one that would be provided to construct
894    /// the timestamp with [`new()`](TaiTime::new).
895    ///
896    /// # Examples
897    ///
898    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
899    ///
900    /// ```
901    /// use std::time::Duration;
902    /// use tai_time::MonotonicTime;
903    ///
904    /// let timestamp = MonotonicTime::new(1_234_567_890, 987_654_321).unwrap();
905    /// assert_eq!(timestamp.as_secs(), 1_234_567_890);
906    ///
907    /// let timestamp = MonotonicTime::EPOCH - Duration::new(3, 500_000_000);
908    /// assert_eq!(timestamp.as_secs(), -4);
909    /// ```
910    pub const fn as_secs(&self) -> i64 {
911        self.secs
912    }
913
914    /// Returns the sub-second fractional part in nanoseconds.
915    ///
916    /// Note that nanoseconds always point towards the future even if the date
917    /// is in the past of the [`EPOCH`](TaiTime::EPOCH).
918    ///
919    /// # Examples
920    ///
921    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
922    ///
923    /// ```
924    /// use tai_time::MonotonicTime;
925    ///
926    /// let timestamp = MonotonicTime::new(1_234_567_890, 987_654_321).unwrap();
927    /// assert_eq!(timestamp.subsec_nanos(), 987_654_321);
928    /// ```
929    pub const fn subsec_nanos(&self) -> u32 {
930        self.nanos
931    }
932
933    /// Returns the number of seconds of the corresponding Unix timestamp.
934    ///
935    /// The argument is the difference between TAI and UTC time in seconds
936    /// (a.k.a. leap seconds) applicable at the date represented by the
937    /// timestamp. For reference, this offset has been +37s since 2017-01-01, a
938    /// value which is to remain valid until at least 2026-12-28. See the
939    /// [official IERS bulletin
940    /// C](http://hpiers.obspm.fr/iers/bul/bulc/bulletinc.dat) for leap second
941    /// announcements or the [IERS
942    /// table](https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat) for
943    /// current and historical values.
944    ///
945    /// This method merely subtracts the offset from the value returned by
946    /// [`as_secs()`](Self::as_secs); its main purpose is to prevent mistakes
947    /// regarding the direction in which the offset should be applied.
948    ///
949    /// The nanosecond part of a Unix timestamp can be simply retrieved with
950    /// [`subsec_nanos()`](Self::subsec_nanos) since UTC and TAI differ by a
951    /// whole number of seconds since 1972.
952    ///
953    /// Note that there is no unanimous consensus regarding the conversion
954    /// between TAI and Unix timestamps prior to 1972.
955    ///
956    /// Returns `None` if the result cannot be represented as a Unix timestamp.
957    /// This is a highly unlikely occurrence since the range of a Unix timestamp
958    /// spans more than ±292 billion years from 1970-01-01 00:00:00 UTC.
959    ///
960    /// # Examples
961    ///
962    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
963    ///
964    /// ```
965    /// use tai_time::MonotonicTime;
966    ///
967    /// // Set the date to 2000-01-01 00:00:00 TAI.
968    /// let timestamp = MonotonicTime::new(946_684_800, 0).unwrap();
969    ///
970    /// // Convert to a Unix timestamp, accounting for the +32s difference between
971    /// // TAI and UTC on 2000-01-01.
972    /// assert_eq!(
973    ///     timestamp.as_unix_secs(32),
974    ///     Some(946_684_768)
975    /// );
976    /// ```
977    pub const fn as_unix_secs(&self, leap_secs: i64) -> Option<i64> {
978        match self.secs.checked_sub(leap_secs) {
979            Some(secs) => secs.checked_add(EPOCH_REF),
980            None => None,
981        }
982    }
983
984    /// Returns a timestamp with a different reference epoch.
985    ///
986    /// Returns `None` if the result is outside the representable range for the
987    /// resulting timestamp. This is a highly unlikely occurrence since the
988    /// range of a `TaiTime` spans more than ±292 billion years from its epoch.
989    ///
990    /// # Examples
991    ///
992    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
993    ///
994    /// ```
995    /// use tai_time::{GpsTime, MonotonicTime};
996    ///
997    /// // Set the date to 2000-01-01 00:00:00 TAI.
998    /// let timestamp = MonotonicTime::new(946_684_800, 0).unwrap();
999    ///
1000    /// // Convert to a GPS timestamp.
1001    /// let gps_timestamp: GpsTime = timestamp.to_tai_time().unwrap();
1002    /// assert_eq!(
1003    ///     gps_timestamp,
1004    ///     GpsTime::new(630_719_981, 0).unwrap()
1005    /// );
1006    /// ```
1007    pub const fn to_tai_time<const OTHER_EPOCH_REF: i64>(
1008        &self,
1009    ) -> Option<TaiTime<OTHER_EPOCH_REF>> {
1010        if let Some(secs) = EPOCH_REF.checked_sub(OTHER_EPOCH_REF)
1011            && let Some(secs) = secs.checked_add(self.secs)
1012        {
1013            return Some(TaiTime {
1014                secs,
1015                nanos: self.nanos,
1016            });
1017        }
1018
1019        None
1020    }
1021
1022    /// Returns a `SystemTime` based on the timestamp.
1023    ///
1024    /// The argument is the difference between TAI and UTC time in seconds
1025    /// (a.k.a. leap seconds) applicable at the date represented by the
1026    /// timestamp. For reference, this offset has been +37s since 2017-01-01, a
1027    /// value which is to remain valid until at least 2026-12-28. See the
1028    /// [official IERS bulletin
1029    /// C](http://hpiers.obspm.fr/iers/bul/bulc/bulletinc.dat) for leap second
1030    /// announcements or the [IERS
1031    /// table](https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat) for
1032    /// current and historical values.
1033    ///
1034    /// While no error will be reported, this method should not be considered
1035    /// appropriate for timestamps in the past of 1972.
1036    ///
1037    /// Returns `None` if the resulting timestamp predates the Unix epoch
1038    /// (1970-01-01 00:00:00 UTC) or cannot be otherwise represented as a
1039    /// `SystemTime`.
1040    ///
1041    /// # Examples
1042    ///
1043    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
1044    ///
1045    /// ```
1046    /// use std::time::{Duration, SystemTime};
1047    /// use tai_time::MonotonicTime;
1048    ///
1049    /// // Set the date to 2000-01-01 00:00:00.123 TAI.
1050    /// let timestamp = MonotonicTime::new(946_684_800, 123_000_000).unwrap();
1051    ///
1052    /// // Obtain a `SystemTime`, accounting for the +32s difference between
1053    /// // TAI and UTC on 2000-01-01.
1054    /// assert_eq!(
1055    ///     timestamp.to_system_time(32),
1056    ///     Some(SystemTime::UNIX_EPOCH + Duration::new(946_684_768, 123_000_000))
1057    /// );
1058    /// ```
1059    #[cfg(feature = "std")]
1060    pub fn to_system_time(&self, leap_secs: i64) -> Option<std::time::SystemTime> {
1061        let secs: u64 = self
1062            .as_unix_secs(leap_secs)
1063            .and_then(|secs| secs.try_into().ok())?;
1064
1065        std::time::SystemTime::UNIX_EPOCH.checked_add(Duration::new(secs, self.subsec_nanos()))
1066    }
1067
1068    /// Returns a `chrono::DateTime` based on the timestamp.
1069    ///
1070    /// The argument is the difference between TAI and UTC time in seconds
1071    /// (a.k.a. leap seconds) applicable at the date represented by the
1072    /// timestamp. For reference, this offset has been +37s since 2017-01-01, a
1073    /// value which is to remain valid until at least 2026-12-28. See the
1074    /// [official IERS bulletin
1075    /// C](http://hpiers.obspm.fr/iers/bul/bulc/bulletinc.dat) for leap second
1076    /// announcements or the [IERS
1077    /// table](https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat) for
1078    /// current and historical values.
1079    ///
1080    /// While no error will be reported, this method should not be considered
1081    /// appropriate for timestamps in the past of 1972.
1082    ///
1083    /// Returns `None` if the resulting timestamp cannot be represented by a
1084    /// `chrono::DateTime`, which may occur if the date predates year -262144 or
1085    /// postdates year 262143.
1086    ///
1087    /// # Examples
1088    ///
1089    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
1090    ///
1091    /// ```
1092    /// use tai_time::MonotonicTime;
1093    ///
1094    /// // Set the date to 2000-01-01 00:00:00.123 TAI (1999-12-31 23:59:28.123 UTC).
1095    /// let timestamp = MonotonicTime::new(946_684_800, 123_000_000).unwrap();
1096    ///
1097    /// // Obtain a `chrono::DateTime`, accounting for the +32s difference between
1098    /// // TAI and UTC on 2000-01-01.
1099    /// let date_time = timestamp.to_chrono_date_time(32).unwrap();
1100    /// assert_eq!(
1101    ///     date_time.to_string(),
1102    ///     "1999-12-31 23:59:28.123 UTC"
1103    /// );
1104    /// ```
1105    #[cfg(feature = "chrono")]
1106    pub fn to_chrono_date_time(&self, leap_secs: i64) -> Option<chrono::DateTime<chrono::Utc>> {
1107        self.as_unix_secs(leap_secs)
1108            .and_then(|secs| chrono::DateTime::from_timestamp(secs, self.nanos))
1109    }
1110
1111    /// Adds a duration to a timestamp, checking for overflow.
1112    ///
1113    /// Returns `None` if overflow occurred.
1114    ///
1115    /// # Examples
1116    ///
1117    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
1118    ///
1119    /// ```
1120    /// use std::time::Duration;
1121    /// use tai_time::MonotonicTime;
1122    ///
1123    /// let timestamp = MonotonicTime::new(1_234_567_890, 987_654_321).unwrap();
1124    /// assert!(timestamp.checked_add(Duration::new(10, 123_456_789)).is_some());
1125    /// assert!(timestamp.checked_add(Duration::MAX).is_none());
1126    /// ```
1127    pub const fn checked_add(self, rhs: Duration) -> Option<Self> {
1128        // A durations in seconds greater than `i64::MAX` is actually fine as
1129        // long as the number of seconds does not effectively overflow which is
1130        // why the below does not use `checked_add`. So technically the below
1131        // addition may wrap around on the negative side due to the
1132        // unsigned-to-signed cast of the duration, but this does not
1133        // necessarily indicate an actual overflow. Actual overflow can be ruled
1134        // out by verifying that the new timestamp is in the future of the old
1135        // timestamp.
1136        let mut secs = self.secs.wrapping_add(rhs.as_secs() as i64);
1137
1138        // Check for overflow.
1139        if secs < self.secs {
1140            return None;
1141        }
1142
1143        let mut nanos = self.nanos + rhs.subsec_nanos();
1144        if nanos >= NANOS_PER_SEC {
1145            secs = if let Some(s) = secs.checked_add(1) {
1146                s
1147            } else {
1148                return None;
1149            };
1150            nanos -= NANOS_PER_SEC;
1151        }
1152
1153        Some(Self { secs, nanos })
1154    }
1155
1156    /// Subtracts a duration from a timestamp, checking for overflow.
1157    ///
1158    /// Returns `None` if overflow occurred.
1159    ///
1160    /// # Examples
1161    ///
1162    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
1163    ///
1164    /// ```
1165    /// use std::time::Duration;
1166    /// use tai_time::MonotonicTime;
1167    ///
1168    /// let timestamp = MonotonicTime::new(1_234_567_890, 987_654_321).unwrap();
1169    /// assert!(timestamp.checked_sub(Duration::new(10, 123_456_789)).is_some());
1170    /// assert!(timestamp.checked_sub(Duration::MAX).is_none());
1171    /// ```
1172    pub const fn checked_sub(self, rhs: Duration) -> Option<Self> {
1173        // A durations in seconds greater than `i64::MAX` is actually fine as
1174        // long as the number of seconds does not effectively overflow, which is
1175        // why the below does not use `checked_sub`. So technically the below
1176        // subtraction may wrap around on the positive side due to the
1177        // unsigned-to-signed cast of the duration, but this does not
1178        // necessarily indicate an actual overflow. Actual overflow can be ruled
1179        // out by verifying that the new timestamp is in the past of the old
1180        // timestamp.
1181        let mut secs = self.secs.wrapping_sub(rhs.as_secs() as i64);
1182
1183        // Check for overflow.
1184        if secs > self.secs {
1185            return None;
1186        }
1187
1188        let nanos = if self.nanos < rhs.subsec_nanos() {
1189            secs = if let Some(s) = secs.checked_sub(1) {
1190                s
1191            } else {
1192                return None;
1193            };
1194
1195            (self.nanos + NANOS_PER_SEC) - rhs.subsec_nanos()
1196        } else {
1197            self.nanos - rhs.subsec_nanos()
1198        };
1199
1200        Some(Self { secs, nanos })
1201    }
1202
1203    /// Subtracts a timestamp from another timestamp.
1204    ///
1205    /// Consider using [`checked_duration_since`](Self::checked_duration_since)
1206    /// if the relative ordering of the timestamps is not known with certainty.
1207    ///
1208    /// # Panics
1209    ///
1210    /// Panics if the argument lies in the future of `self`.
1211    ///
1212    /// # Examples
1213    ///
1214    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
1215    ///
1216    /// ```
1217    /// use std::time::Duration;
1218    /// use tai_time::MonotonicTime;
1219    ///
1220    /// let timestamp_earlier = MonotonicTime::new(1_234_567_879, 987_654_321).unwrap();
1221    /// let timestamp_later = MonotonicTime::new(1_234_567_900, 123_456_789).unwrap();
1222    /// assert_eq!(
1223    ///     timestamp_later.duration_since(timestamp_earlier),
1224    ///     Duration::new(20, 135_802_468)
1225    /// );
1226    /// ```
1227    pub const fn duration_since(self, earlier: Self) -> Duration {
1228        if let Some(duration) = self.checked_duration_since(earlier) {
1229            return duration;
1230        }
1231
1232        panic!("attempt to substract a timestamp from an earlier timestamp");
1233    }
1234
1235    /// Computes the duration elapsed between a timestamp and an earlier
1236    /// timestamp, checking that the timestamps are appropriately ordered.
1237    ///
1238    /// Returns `None` if the argument lies in the future of `self`.
1239    ///
1240    /// # Examples
1241    ///
1242    /// (Shown here for `MonotonicTime`, an alias for `TaiTime<0>`)
1243    ///
1244    /// ```
1245    /// use std::time::Duration;
1246    /// use tai_time::MonotonicTime;
1247    ///
1248    /// let timestamp_earlier = MonotonicTime::new(1_234_567_879, 987_654_321).unwrap();
1249    /// let timestamp_later = MonotonicTime::new(1_234_567_900, 123_456_789).unwrap();
1250    /// assert!(timestamp_later.checked_duration_since(timestamp_earlier).is_some());
1251    /// assert!(timestamp_earlier.checked_duration_since(timestamp_later).is_none());
1252    /// ```
1253    pub const fn checked_duration_since(self, earlier: Self) -> Option<Duration> {
1254        // If the subtraction of the nanosecond fractions would overflow, carry
1255        // over one second to the nanoseconds.
1256        let (secs, nanos) = if earlier.nanos > self.nanos {
1257            if let Some(s) = self.secs.checked_sub(1) {
1258                (s, self.nanos + NANOS_PER_SEC)
1259            } else {
1260                return None;
1261            }
1262        } else {
1263            (self.secs, self.nanos)
1264        };
1265
1266        // Make sure the computation of the duration will not overflow the
1267        // seconds.
1268        if secs < earlier.secs {
1269            return None;
1270        }
1271
1272        // This subtraction may wrap around if the difference between the two
1273        // timestamps is more than `i64::MAX`, but even if it does the result
1274        // will be correct once cast to an unsigned integer.
1275        let delta_secs = secs.wrapping_sub(earlier.secs) as u64;
1276
1277        // The below subtraction is guaranteed to never overflow.
1278        let delta_nanos = nanos - earlier.nanos;
1279
1280        Some(Duration::new(delta_secs, delta_nanos))
1281    }
1282}
1283
1284impl<const EPOCH_REF: i64> Add<Duration> for TaiTime<EPOCH_REF> {
1285    type Output = Self;
1286
1287    /// Adds a duration to a timestamp.
1288    ///
1289    /// # Panics
1290    ///
1291    /// This function panics if the resulting timestamp cannot be represented.
1292    ///
1293    /// See [`TaiTime::checked_add`] for a non-panicking alternative.
1294    fn add(self, other: Duration) -> Self {
1295        self.checked_add(other)
1296            .expect("overflow when adding duration to timestamp")
1297    }
1298}
1299
1300impl<const EPOCH_REF: i64> Sub<Duration> for TaiTime<EPOCH_REF> {
1301    type Output = Self;
1302
1303    /// Subtracts a duration from a timestamp.
1304    ///
1305    /// # Panics
1306    ///
1307    /// This function panics if the resulting timestamp cannot be represented.
1308    ///
1309    /// See [`TaiTime::checked_sub`] for a non-panicking alternative.
1310    fn sub(self, other: Duration) -> Self {
1311        self.checked_sub(other)
1312            .expect("overflow when subtracting duration from timestamp")
1313    }
1314}
1315
1316impl<const EPOCH_REF: i64> AddAssign<Duration> for TaiTime<EPOCH_REF> {
1317    /// Increments the timestamp by a duration.
1318    ///
1319    /// # Panics
1320    ///
1321    /// This function panics if the resulting timestamp cannot be represented.
1322    fn add_assign(&mut self, other: Duration) {
1323        *self = *self + other;
1324    }
1325}
1326
1327impl<const EPOCH_REF: i64> SubAssign<Duration> for TaiTime<EPOCH_REF> {
1328    /// Decrements the timestamp by a duration.
1329    ///
1330    /// # Panics
1331    ///
1332    /// This function panics if the resulting timestamp cannot be represented.
1333    fn sub_assign(&mut self, other: Duration) {
1334        *self = *self - other;
1335    }
1336}
1337
1338impl<const EPOCH_REF: i64> FromStr for TaiTime<EPOCH_REF> {
1339    type Err = ParseDateTimeError;
1340
1341    /// Parses an RFC3339-like TAI date-time with signed years. Since TAI is
1342    /// timezone-independent, time zones and offsets suffixes are invalid.
1343    ///
1344    /// Expected format:
1345    ///
1346    /// `[±][Y]...[Y]YYYY-MM-DD hh:mm:ss[.d[d]...[d]]`
1347    ///
1348    /// or:
1349    ///
1350    /// `[±][Y]...[Y]YYYY-MM-DD'T'hh:mm:ss[.d[d]...[d]]`
1351    ///
1352    /// where delimiter `T` between date and time may also be a lowercase `t`.
1353    ///
1354    /// The year may take any value within `±i32::MAX`.
1355    fn from_str(s: &str) -> Result<Self, Self::Err> {
1356        let (year, month, day, hour, min, sec, nano) = parse_date_time(s)?;
1357
1358        Self::try_from_date_time(year, month, day, hour, min, sec, nano)
1359            .map_err(ParseDateTimeError::RangeError)
1360    }
1361}
1362
1363impl<const EPOCH_REF: i64> fmt::Display for TaiTime<EPOCH_REF> {
1364    /// Displays the TAI timestamp as an RFC3339-like date-time.
1365    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1366        use core::str::from_utf8;
1367
1368        // We need to use an i128 timestamp as it may otherwise overflow when
1369        // translated to year 0.
1370        let secs_from_year_0: i128 =
1371            self.secs as i128 + EPOCH_REF as i128 + days_from_year_0(1970) as i128 * 86400;
1372        let (year, doy, mut sec) = secs_to_date_time(secs_from_year_0);
1373        let (month, day) = month_and_day_of_month(year, doy);
1374        let hour = sec / 3600;
1375        sec -= hour * 3600;
1376        let min = sec / 60;
1377        sec -= min * 60;
1378
1379        write!(
1380            f,
1381            "{}{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
1382            if year < 0 { "-" } else { "" },
1383            year.abs(),
1384            month,
1385            day,
1386            hour,
1387            min,
1388            sec
1389        )?;
1390
1391        fn split_last_digit(n: u32) -> (u32, u32) {
1392            // A fast implementation of n/10.
1393            let left = (((n as u64) * 429496730u64) >> 32) as u32;
1394            // A fast implementation of n%10.
1395            let right = n - left * 10;
1396
1397            (left, right)
1398        }
1399
1400        match f.precision() {
1401            Some(precision) if precision != 0 => {
1402                let mut n = self.nanos;
1403                let mut buffer = [0u8; 9];
1404
1405                for pos in (0..9).rev() {
1406                    let (new_n, digit) = split_last_digit(n);
1407                    n = new_n;
1408                    buffer[pos] = digit as u8 + 48; // ASCII/UTF8 codepoint for
1409                    // numerals
1410                }
1411
1412                write!(f, ".{}", from_utf8(&buffer[0..precision.min(9)]).unwrap())?;
1413            }
1414            None => {
1415                let mut n = self.nanos;
1416                let mut buffer = [0u8; 9];
1417                let mut precision = None;
1418                for pos in (0..9).rev() {
1419                    let (new_n, digit) = split_last_digit(n);
1420                    if digit != 0 && precision.is_none() {
1421                        precision = Some(pos);
1422                    }
1423                    n = new_n;
1424                    buffer[pos] = digit as u8 + 48;
1425                }
1426
1427                if let Some(precision) = precision {
1428                    write!(f, ".{}", from_utf8(&buffer[0..=precision]).unwrap())?;
1429                }
1430            }
1431            _ => {} // precision == Some(0)
1432        }
1433
1434        Ok(())
1435    }
1436}
1437
1438/// Validator for nanoseconds deserializer.
1439#[cfg(feature = "serde")]
1440fn validate_nanos<'de, D>(deserializer: D) -> Result<u32, D::Error>
1441where
1442    D: serde::de::Deserializer<'de>,
1443{
1444    let v: u32 = serde::de::Deserialize::deserialize(deserializer)?;
1445    if v < NANOS_PER_SEC {
1446        Ok(v)
1447    } else {
1448        Err(serde::de::Error::invalid_value(
1449            serde::de::Unexpected::Unsigned(v as u64),
1450            &"a number of nanoseconds less than 1000000000",
1451        ))
1452    }
1453}
1454
1455#[cfg(test)]
1456mod tests {
1457    use super::*;
1458
1459    #[test]
1460    fn equality() {
1461        let t0 = Tai1972Time::new(123, 123_456_789).unwrap();
1462        let t1 = Tai1972Time::new(123, 123_456_789).unwrap();
1463        let t2 = Tai1972Time::new(123, 123_456_790).unwrap();
1464        let t3 = Tai1972Time::new(124, 123_456_789).unwrap();
1465
1466        assert_eq!(t0, t1);
1467        assert_ne!(t0, t2);
1468        assert_ne!(t0, t3);
1469    }
1470
1471    #[test]
1472    fn ordering() {
1473        let t0 = Tai1972Time::new(0, 1).unwrap();
1474        let t1 = Tai1972Time::new(1, 0).unwrap();
1475
1476        assert!(t1 > t0);
1477    }
1478
1479    #[test]
1480    fn epoch_smoke() {
1481        // Set all timestamps to 2009-02-13 23:31:30.123456789 UTC.
1482        const T_UNIX_SECS: i64 = 1_234_567_890;
1483
1484        let t_tai_1970 = MonotonicTime::new(1_234_567_924, 123_456_789).unwrap();
1485        let t_tai_1958 = Tai1958Time::new(1_613_259_124, 123_456_789).unwrap();
1486        let t_tai_1972 = Tai1972Time::new(1_171_495_924, 123_456_789).unwrap();
1487        let t_gps = GpsTime::new(918_603_105, 123_456_789).unwrap();
1488        let t_gst = GstTime::new(299_287_905, 123_456_789).unwrap();
1489        let t_bdt = BdtTime::new(98_494_291, 123_456_789).unwrap();
1490
1491        // Leap seconds can be neglected for this test.
1492        assert_eq!(t_tai_1970.as_unix_secs(34).unwrap(), T_UNIX_SECS);
1493        assert_eq!(t_tai_1958.as_unix_secs(34).unwrap(), T_UNIX_SECS);
1494        assert_eq!(t_tai_1972.as_unix_secs(34).unwrap(), T_UNIX_SECS);
1495        assert_eq!(t_gps.as_unix_secs(34).unwrap(), T_UNIX_SECS);
1496        assert_eq!(t_gst.as_unix_secs(34).unwrap(), T_UNIX_SECS);
1497        assert_eq!(t_bdt.as_unix_secs(34).unwrap(), T_UNIX_SECS);
1498
1499        assert_eq!(t_tai_1970.to_tai_time().unwrap(), t_tai_1958);
1500        assert_eq!(t_tai_1970.to_tai_time().unwrap(), t_tai_1970);
1501        assert_eq!(t_tai_1970.to_tai_time().unwrap(), t_tai_1972);
1502        assert_eq!(t_tai_1970.to_tai_time().unwrap(), t_gps);
1503        assert_eq!(t_tai_1970.to_tai_time().unwrap(), t_gst);
1504        assert_eq!(t_tai_1970.to_tai_time().unwrap(), t_bdt);
1505    }
1506
1507    #[cfg(all(
1508        feature = "std",
1509        feature = "tai_clock",
1510        any(
1511            target_os = "android",
1512            target_os = "emscripten",
1513            target_os = "fuchsia",
1514            target_os = "linux"
1515        )
1516    ))]
1517    #[test]
1518    fn now_smoke() {
1519        let tolerance = Duration::from_secs(100);
1520
1521        // Leap seconds can be neglected for this test.
1522        let now_utc_no_leap = GpsTime::now_from_utc(0);
1523        let now_tai = GpsTime::now();
1524
1525        if now_utc_no_leap > now_tai {
1526            assert!(now_utc_no_leap.duration_since(now_tai) < tolerance);
1527        } else {
1528            assert!(now_tai.duration_since(now_utc_no_leap) < tolerance);
1529        }
1530    }
1531
1532    #[cfg(feature = "std")]
1533    #[test]
1534    fn now_from_utc_smoke() {
1535        const TAI_1972_START_OF_2022: i64 = 1_577_923_200;
1536        const TAI_1972_START_OF_2050: i64 = 2_461_536_000;
1537
1538        // Leap seconds can be neglected for this test.
1539        let now_secs = Tai1972Time::now_from_utc(0).as_secs();
1540
1541        assert!(now_secs > TAI_1972_START_OF_2022);
1542        assert!(now_secs < TAI_1972_START_OF_2050);
1543    }
1544
1545    #[cfg(feature = "std")]
1546    #[test]
1547    fn from_system_time() {
1548        // Unix and TAI 1972 time stamps for 2001:01:01 12:34:56.789 UTC.
1549        let t_unix = Duration::new(978_352_496, 789_000_000);
1550        let t_tai_1972 = Tai1972Time::new(915_280_528, 789_000_000).unwrap();
1551
1552        let system_time = std::time::SystemTime::UNIX_EPOCH + t_unix;
1553        // Account for the +32 leap seconds on that date.
1554        let t = Tai1972Time::from_system_time(&system_time, 32);
1555
1556        assert_eq!(t, t_tai_1972);
1557    }
1558
1559    #[test]
1560    fn from_unix_timestamp() {
1561        // Unix and TAI 1972 time stamps for 2001:01:01 12:34:56.789 UTC.
1562        const T_UNIX_SECS: i64 = 978_352_496;
1563        const T_UNIX_NANOS: u32 = 789_000_000;
1564
1565        let t_tai_1972 = Tai1972Time::new(915_280_528, 789_000_000).unwrap();
1566
1567        // Account for the +32 leap seconds on that date.
1568        let t = Tai1972Time::from_unix_timestamp(T_UNIX_SECS, T_UNIX_NANOS, 32);
1569
1570        assert_eq!(t, t_tai_1972);
1571    }
1572
1573    #[test]
1574    fn from_unix_timestamp_with_carry() {
1575        // Unix and TAI 1972 time stamps for 2001:01:01 12:34:56.789 UTC with 2
1576        // seconds accounted for in the nanoseconds field.
1577        const T_UNIX_SECS: i64 = 978_352_494;
1578        const T_UNIX_NANOS: u32 = 2_789_000_000;
1579
1580        let t_tai_1972 = Tai1972Time::new(915_280_528, 789_000_000).unwrap();
1581
1582        // Account for the +32 leap seconds on that date.
1583        let t = Tai1972Time::from_unix_timestamp(T_UNIX_SECS, T_UNIX_NANOS, 32);
1584
1585        assert_eq!(t, t_tai_1972);
1586    }
1587
1588    #[cfg(feature = "chrono")]
1589    #[test]
1590    fn from_chrono_date_time() {
1591        // TAI 1972 time stamp for 2001:01:01 12:34:56.789 UTC.
1592        let t_tai_1972 = Tai1972Time::new(915_280_528, 789_000_000).unwrap();
1593
1594        let chrono_date_time =
1595            chrono::DateTime::parse_from_rfc3339("2001-01-01T12:34:56.789Z").unwrap();
1596        // Account for the +32 leap seconds on that date.
1597        let t = Tai1972Time::from_chrono_date_time(&chrono_date_time, 32);
1598
1599        assert_eq!(t, t_tai_1972);
1600    }
1601
1602    #[test]
1603    fn as_secs_and_nanos() {
1604        // TAI 1972 time stamp for 1999:01:01 01:23:45.678 UTC.
1605        const T_TAI_1972_SECS: i64 = 852_081_857;
1606        const T_TAI_1972_NANOS: u32 = 678_000_000;
1607
1608        let t = Tai1972Time::new(T_TAI_1972_SECS, T_TAI_1972_NANOS).unwrap();
1609
1610        assert_eq!(t.as_secs(), T_TAI_1972_SECS);
1611        assert_eq!(t.subsec_nanos(), T_TAI_1972_NANOS);
1612    }
1613
1614    #[test]
1615    fn as_unix_secs() {
1616        // Unix and TAI 1972 time stamp for 1999:01:01 01:23:45.678 UTC.
1617        const T_UNIX_SECS: i64 = 915_153_825;
1618        const T_TAI_1972_SECS: i64 = 852_081_857;
1619        const T_TAI_1972_NANOS: u32 = 678_000_000;
1620
1621        let t = Tai1972Time::new(T_TAI_1972_SECS, T_TAI_1972_NANOS).unwrap();
1622
1623        assert_eq!(t.as_unix_secs(32).unwrap(), T_UNIX_SECS);
1624    }
1625
1626    #[test]
1627    fn to_tai_time() {
1628        // GPS and TAI 1972 time stamps for 1999:01:01 01:23:45.678 UTC.
1629        let t_gps = GpsTime::new(599_189_038, 678_000_000).unwrap();
1630        let t_tai_1972 = Tai1972Time::new(852_081_857, 678_000_000).unwrap();
1631
1632        let t: Tai1972Time = t_gps.to_tai_time().unwrap();
1633
1634        assert_eq!(t, t_tai_1972);
1635    }
1636
1637    #[cfg(feature = "std")]
1638    #[test]
1639    fn to_system_time() {
1640        // Unix and TAI 1972 time stamp for 1999:01:01 01:23:45.678 UTC.
1641        const T_UNIX: Duration = Duration::new(915_153_825, 678_000_000);
1642        let t_tai_1972 = Tai1972Time::new(852_081_857, 678_000_000).unwrap();
1643
1644        assert_eq!(
1645            t_tai_1972.to_system_time(32).unwrap(),
1646            std::time::SystemTime::UNIX_EPOCH + T_UNIX
1647        );
1648    }
1649
1650    #[cfg(feature = "chrono")]
1651    #[test]
1652    fn to_chrono_date_time() {
1653        // TAI 1972 time stamp for 1999:01:01 01:23:45.678 UTC.
1654        let t_tai_1972 = Tai1972Time::new(852_081_857, 678_000_000).unwrap();
1655
1656        assert_eq!(
1657            t_tai_1972.to_chrono_date_time(32).unwrap(),
1658            chrono::DateTime::parse_from_rfc3339("1999-01-01T01:23:45.678Z").unwrap()
1659        );
1660    }
1661
1662    #[test]
1663    fn invalid_nanoseconds() {
1664        assert_eq!(Tai1958Time::new(123, 1_000_000_000), None);
1665    }
1666
1667    #[test]
1668    fn duration_since_smoke() {
1669        let t0 = Tai1972Time::new(100, 100_000_000).unwrap();
1670        let t1 = Tai1972Time::new(123, 223_456_789).unwrap();
1671
1672        assert_eq!(
1673            t1.checked_duration_since(t0),
1674            Some(Duration::new(23, 123_456_789))
1675        );
1676    }
1677
1678    #[test]
1679    fn duration_with_carry() {
1680        let t0 = Tai1972Time::new(100, 200_000_000).unwrap();
1681        let t1 = Tai1972Time::new(101, 100_000_000).unwrap();
1682
1683        assert_eq!(
1684            t1.checked_duration_since(t0),
1685            Some(Duration::new(0, 900_000_000))
1686        );
1687    }
1688
1689    #[test]
1690    fn duration_since_extreme() {
1691        const MIN_TIME: Tai1972Time = TaiTime::MIN;
1692        const MAX_TIME: Tai1972Time = TaiTime::MAX;
1693
1694        assert_eq!(
1695            MAX_TIME.checked_duration_since(MIN_TIME),
1696            Some(Duration::new(u64::MAX, NANOS_PER_SEC - 1))
1697        );
1698    }
1699
1700    #[test]
1701    fn duration_since_invalid() {
1702        let t0 = Tai1972Time::new(100, 0).unwrap();
1703        let t1 = Tai1972Time::new(99, 0).unwrap();
1704
1705        assert_eq!(t1.checked_duration_since(t0), None);
1706    }
1707
1708    #[test]
1709    fn add_duration_smoke() {
1710        let t = Tai1972Time::new(-100, 100_000_000).unwrap();
1711        let dt = Duration::new(400, 300_000_000);
1712
1713        assert_eq!(t + dt, Tai1972Time::new(300, 400_000_000).unwrap());
1714    }
1715
1716    #[test]
1717    fn add_duration_with_carry() {
1718        let t = Tai1972Time::new(-100, 900_000_000).unwrap();
1719        let dt1 = Duration::new(400, 100_000_000);
1720        let dt2 = Duration::new(400, 300_000_000);
1721
1722        assert_eq!(t + dt1, Tai1972Time::new(301, 0).unwrap());
1723        assert_eq!(t + dt2, Tai1972Time::new(301, 200_000_000).unwrap());
1724    }
1725
1726    #[test]
1727    fn add_duration_extreme() {
1728        let t = Tai1972Time::new(i64::MIN, 0).unwrap();
1729        let dt = Duration::new(u64::MAX, NANOS_PER_SEC - 1);
1730
1731        assert_eq!(
1732            t + dt,
1733            Tai1972Time::new(i64::MAX, NANOS_PER_SEC - 1).unwrap()
1734        );
1735    }
1736
1737    #[test]
1738    #[should_panic]
1739    fn add_duration_overflow() {
1740        let t = Tai1972Time::new(i64::MIN, 1).unwrap();
1741        let dt = Duration::new(u64::MAX, NANOS_PER_SEC - 1);
1742
1743        let _ = t + dt;
1744    }
1745
1746    #[test]
1747    fn sub_duration_smoke() {
1748        let t = Tai1972Time::new(100, 500_000_000).unwrap();
1749        let dt = Duration::new(400, 300_000_000);
1750
1751        assert_eq!(t - dt, Tai1972Time::new(-300, 200_000_000).unwrap());
1752    }
1753
1754    #[test]
1755    fn sub_duration_with_carry() {
1756        let t = Tai1972Time::new(100, 100_000_000).unwrap();
1757        let dt1 = Duration::new(400, 100_000_000);
1758        let dt2 = Duration::new(400, 300_000_000);
1759
1760        assert_eq!(t - dt1, Tai1972Time::new(-300, 0).unwrap());
1761        assert_eq!(t - dt2, Tai1972Time::new(-301, 800_000_000).unwrap());
1762    }
1763
1764    #[test]
1765    fn sub_duration_extreme() {
1766        let t = Tai1972Time::new(i64::MAX, NANOS_PER_SEC - 1).unwrap();
1767        let dt = Duration::new(u64::MAX, NANOS_PER_SEC - 1);
1768
1769        assert_eq!(t - dt, Tai1972Time::new(i64::MIN, 0).unwrap());
1770    }
1771
1772    #[test]
1773    #[should_panic]
1774    fn sub_duration_overflow() {
1775        let t = Tai1972Time::new(i64::MAX, NANOS_PER_SEC - 2).unwrap();
1776        let dt = Duration::new(u64::MAX, NANOS_PER_SEC - 1);
1777
1778        let _ = t - dt;
1779    }
1780
1781    #[cfg(feature = "chrono")]
1782    #[test]
1783    fn date_time_year_count() {
1784        // This test relies on `chrono` as the source of truth.
1785        use chrono::NaiveDate;
1786
1787        // Check enough years to cover several 400-year, 100-year, 4-year and
1788        // 1-year boundaries, with both negative and positive dates. Check as
1789        // well the most extreme dates supported by `chrono`.
1790        const TEST_MIN_YEAR: i32 = -801;
1791        const TEST_MAX_YEAR: i32 = 801;
1792        const CHRONO_MIN_YEAR: i32 = -0x3ffff;
1793        const CHRONO_MAX_YEAR: i32 = 0x3fffe;
1794
1795        // The test abuses `chrono` by using TAI date-time stamps, pretending
1796        // they are UTC. This works because `chrono` ignores leap seconds in
1797        // arithmetic operations.
1798        let gps_chrono_epoch = NaiveDate::from_ymd_opt(1980, 1, 6)
1799            .unwrap()
1800            .and_hms_opt(0, 0, 19)
1801            .unwrap();
1802
1803        for year in (-TEST_MIN_YEAR..=TEST_MAX_YEAR).chain([CHRONO_MIN_YEAR, CHRONO_MAX_YEAR]) {
1804            // Test the beginning of the year.
1805            let chrono_date_time = NaiveDate::from_ymd_opt(year, 1, 1)
1806                .unwrap()
1807                .and_hms_opt(0, 0, 0)
1808                .unwrap();
1809            let chrono_gps_timestamp = (chrono_date_time - gps_chrono_epoch).num_seconds();
1810            let tai_gps_timestamp = GpsTime::try_from_date_time(year, 1, 1, 0, 0, 0, 0)
1811                .unwrap()
1812                .as_secs();
1813            assert_eq!(tai_gps_timestamp, chrono_gps_timestamp);
1814
1815            // Test the last second of the year.
1816            let chrono_date_time = NaiveDate::from_ymd_opt(year, 12, 31)
1817                .unwrap()
1818                .and_hms_opt(23, 59, 59)
1819                .unwrap();
1820            let chrono_gps_timestamp = (chrono_date_time - gps_chrono_epoch).num_seconds();
1821            let tai_gps_timestamp = GpsTime::try_from_date_time(year, 12, 31, 23, 59, 59, 0)
1822                .unwrap()
1823                .as_secs();
1824            assert_eq!(tai_gps_timestamp, chrono_gps_timestamp);
1825        }
1826    }
1827
1828    #[cfg(feature = "chrono")]
1829    #[test]
1830    fn date_time_day_count() {
1831        // This test relies on `chrono` as the source of truth.
1832        use chrono::{Datelike, NaiveDate};
1833
1834        // Test arbitrary leap and non-leap years, negative and positive.
1835        const TEST_YEARS: [i32; 6] = [-3000, -500, -1, 600, 723, 2400];
1836
1837        // The test abuses `chrono` by using TAI date-time stamps, pretending
1838        // they are UTC. This works because `chrono` ignores leap seconds in
1839        // arithmetic operations.
1840        let bdt_chrono_epoch = NaiveDate::from_ymd_opt(2006, 1, 1)
1841            .unwrap()
1842            .and_hms_opt(0, 0, 33)
1843            .unwrap();
1844
1845        for year in TEST_YEARS {
1846            let mut chrono_date_time = NaiveDate::from_ymd_opt(year, 1, 1)
1847                .unwrap()
1848                .and_hms_opt(0, 0, 0)
1849                .unwrap();
1850
1851            while chrono_date_time.year() == year {
1852                // Test the beginning of the day.
1853                let chrono_bdt_timestamp = (chrono_date_time - bdt_chrono_epoch).num_seconds();
1854                let tai_bdt_timestamp = BdtTime::try_from_date_time(
1855                    year,
1856                    chrono_date_time.month() as u8,
1857                    chrono_date_time.day() as u8,
1858                    0,
1859                    0,
1860                    0,
1861                    0,
1862                )
1863                .unwrap()
1864                .as_secs();
1865                assert_eq!(tai_bdt_timestamp, chrono_bdt_timestamp);
1866
1867                // Test the last second of the day.
1868                chrono_date_time += Duration::from_secs(86399);
1869                let chrono_bdt_timestamp = (chrono_date_time - bdt_chrono_epoch).num_seconds();
1870                let tai_bdt_timestamp = BdtTime::try_from_date_time(
1871                    year,
1872                    chrono_date_time.month() as u8,
1873                    chrono_date_time.day() as u8,
1874                    23,
1875                    59,
1876                    59,
1877                    0,
1878                )
1879                .unwrap()
1880                .as_secs();
1881                assert_eq!(tai_bdt_timestamp, chrono_bdt_timestamp);
1882
1883                chrono_date_time += Duration::from_secs(1);
1884            }
1885        }
1886    }
1887
1888    #[test]
1889    fn date_time_second_count() {
1890        // Pick an arbitrary day.
1891        const TEST_DAY: u8 = 12;
1892        const TEST_MONTH: u8 = 3;
1893        const TEST_YEAR: i32 = -4567;
1894
1895        let mut timestamp =
1896            Tai1958Time::try_from_date_time(TEST_YEAR, TEST_MONTH, TEST_DAY, 0, 0, 0, 0)
1897                .unwrap()
1898                .as_secs();
1899
1900        for hour in 0..=23 {
1901            for min in 0..=59 {
1902                for sec in 0..=59 {
1903                    let t = Tai1958Time::try_from_date_time(
1904                        TEST_YEAR, TEST_MONTH, TEST_DAY, hour, min, sec, 0,
1905                    )
1906                    .unwrap();
1907                    assert_eq!(t.as_secs(), timestamp);
1908                    timestamp += 1;
1909                }
1910            }
1911        }
1912    }
1913
1914    #[test]
1915    fn date_time_string_roundtrip() {
1916        const TEST_DATES: &[(&str, (i32, u8, u8, u8, u8, u8, u32))] = &[
1917            (
1918                "-2147483647-01-01 00:00:00",
1919                (-2147483647, 1, 1, 0, 0, 0, 0),
1920            ),
1921            ("-0000-01-01T00:00:00", (0, 1, 1, 0, 0, 0, 0)),
1922            (
1923                "2000-02-29T12:23:45.000000001",
1924                (2000, 2, 29, 12, 23, 45, 1),
1925            ),
1926            (
1927                "+2345-10-11 12:13:14.123",
1928                (2345, 10, 11, 12, 13, 14, 123_000_000),
1929            ),
1930            (
1931                "2147483647-12-31 23:59:59.999999999",
1932                (2147483647, 12, 31, 23, 59, 59, 999_999_999),
1933            ),
1934        ];
1935
1936        for (date_time_str, date_time) in TEST_DATES {
1937            let (year, month, day, hour, min, sec, nano) = *date_time;
1938
1939            let t0: GstTime = date_time_str.parse().unwrap();
1940            let t1: GpsTime = t0.to_string().parse().unwrap();
1941            assert_eq!(
1942                t1,
1943                GpsTime::try_from_date_time(year, month, day, hour, min, sec, nano).unwrap()
1944            );
1945        }
1946    }
1947
1948    #[test]
1949    fn date_time_invalid() {
1950        const TEST_DATES: &[&str] = &[
1951            "123-01-01 00:00:00",
1952            "-1500-02-29 00:00:00",
1953            "2001-06-31 00:00:00",
1954            "1234-01-00 00:00:00",
1955            "1234-00-01 00:00:00",
1956            "1234-13-01 00:00:00",
1957            "5678-09-10 24:00:00",
1958            "5678-09-10 00:60:00",
1959            "5678-09-10 00:00:60",
1960        ];
1961
1962        for date_time_str in TEST_DATES {
1963            assert!(date_time_str.parse::<MonotonicTime>().is_err());
1964        }
1965    }
1966
1967    #[cfg(feature = "serde")]
1968    #[test]
1969    fn deserialize_from_seq() {
1970        use serde_json;
1971
1972        let data = r#"[987654321, 123456789]"#;
1973
1974        let t: GpsTime = serde_json::from_str(data).unwrap();
1975        assert_eq!(t, GpsTime::new(987654321, 123456789).unwrap());
1976    }
1977
1978    #[cfg(feature = "serde")]
1979    #[test]
1980    fn deserialize_from_map() {
1981        use serde_json;
1982
1983        let data = r#"{"secs": 987654321, "nanos": 123456789}"#;
1984
1985        let t: GpsTime = serde_json::from_str(data).unwrap();
1986        assert_eq!(t, GpsTime::new(987654321, 123456789).unwrap());
1987    }
1988
1989    #[cfg(feature = "serde")]
1990    #[test]
1991    fn deserialize_invalid_nanos() {
1992        use serde_json;
1993
1994        let data = r#"{"secs": 987654321, "nanos": 1000000000}"#;
1995
1996        let t: Result<GpsTime, serde_json::Error> = serde_json::from_str(data);
1997
1998        assert!(t.is_err())
1999    }
2000
2001    #[cfg(feature = "serde")]
2002    #[test]
2003    fn serialize_roundtrip() {
2004        use serde_json;
2005
2006        let t0 = GpsTime::new(987654321, 123456789).unwrap();
2007
2008        let data = serde_json::to_string(&t0).unwrap();
2009
2010        let t1: GpsTime = serde_json::from_str(&data).unwrap();
2011
2012        assert_eq!(t0, t1);
2013    }
2014
2015    #[cfg(feature = "schemars")]
2016    #[test]
2017    fn generate_json_schema() {
2018        let schema = schemars::schema_for!(MonotonicTime);
2019        let schema = schema.as_object().unwrap();
2020        for field in &["secs", "nanos"] {
2021            assert!(schema.get("properties").unwrap().get(field).is_some());
2022            assert!(
2023                schema
2024                    .get("required")
2025                    .unwrap()
2026                    .as_array()
2027                    .unwrap()
2028                    .iter()
2029                    .any(|a| a.as_str() == Some(field))
2030            );
2031        }
2032    }
2033}