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}