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