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}