hifitime 0.1.3

Precise date and time handling in Rust built on top of std::time::Duration with leap second support
Documentation
// Disclaimer: this is heavily inspired by std::time::Duration, but it supports longer
// time spans and leap seconds. Moreover, an Instant is defined with respect to
// 01 Jan 1900, as per NTP and TAI specifications.

use std::cmp::PartialEq;
use std::fmt;
use std::ops::{Add, Sub};
pub use std::time::Duration;

/// An `Era` represents whether the associated `Instant` is before the TAI Epoch
/// (01 Jan 1900, midnight) or afterwards. If it is before, than it's refered to as "Past",
/// otherwise is in the "Present" era.
///
/// ```
/// use hifitime::instant::Era;
/// assert!(Era::Past < Era::Present);
/// ```
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub enum Era {
    Past,
    Present,
}

impl fmt::Display for Era {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Era::Present => write!(f, "Present"),
            Era::Past => write!(f, "Past"),
        }
    }
}

/// An `Instant` type represents an instant with respect to 01 Jan 1900 at midnight, as per
/// the International Atomic Time (TAI) system.
#[derive(Clone, Copy, Debug, PartialOrd)]
pub struct Instant {
    era: Era,
    duration: Duration,
}

impl Instant {
    /// Creates a new `Instant` with respect to TAI Epoch: 01 January 1900, 00:00:00.0.
    /// All time systems are represented with respect to this epoch.
    /// Note: this constructor relies on the constructor for std::time::Duration; as such,
    /// refer to [`std::time::Duration::new`](https://doc.rust-lang.org/std/time/struct.Duration.html#method.new)
    /// for pertinent warnings and limitations.
    ///
    /// # Examples
    /// ```
    /// use hifitime::instant::{Era, Instant};
    ///
    /// let epoch = Instant::new(0, 0, Era::Present);
    /// assert_eq!(epoch.secs(), 0);
    /// assert_eq!(epoch.nanos(), 0);
    ///
    /// let one_second_before_1900 = Instant::new(1, 0, Era::Past);
    /// assert_eq!(one_second_before_1900.secs(), 1);
    /// assert_eq!(one_second_before_1900.era(), Era::Past);
    ///
    /// let one_second_after_1900 = Instant::new(1, 0, Era::Present);
    /// assert_eq!(one_second_after_1900.secs(), 1);
    /// assert_eq!(one_second_after_1900.era(), Era::Present);
    ///
    /// assert!(one_second_after_1900 > epoch);
    /// assert!(one_second_after_1900 >= epoch);
    /// assert!(one_second_before_1900 < epoch);
    /// assert!(one_second_before_1900 <= epoch);
    /// assert!(Instant::new(1, 0, Era::Past) < Instant::new(0, 0, Era::Present));
    /// assert!(Instant::new(1, 0, Era::Past) < Instant::new(1, 0, Era::Present));
    /// // NOTE: Equality exists at epoch (or zero offset)
    /// assert_eq!(Instant::new(0, 0, Era::Past), Instant::new(0, 0, Era::Present));
    /// assert_ne!(Instant::new(0, 1, Era::Past), Instant::new(0, 1, Era::Present));
    /// assert_ne!(Instant::new(1, 1, Era::Past), Instant::new(1, 1, Era::Present));
    /// assert_ne!(Instant::new(1, 0, Era::Past), Instant::new(1, 0, Era::Present));
    /// // Sub in the Present era.
    /// let unix = Instant::new(2_208_988_800, 0, Era::Present);
    /// let unix_p1h = Instant::new(2_208_988_800 + 3_600, 0, Era::Present);
    /// assert_eq!(unix_p1h - unix, 3600.0);
    /// assert_eq!(unix - unix_p1h, -3600.0);
    /// ```
    pub fn new(seconds: u64, nanos: u32, era: Era) -> Instant {
        Instant {
            duration: Duration::new(seconds, nanos),
            era: era,
        }
    }

    /// Creates a new `Instant` from the number of seconds compared to `Era`, provided as a floating point value.
    ///
    /// # Example
    /// ```
    /// use hifitime::instant::{Era, Instant};
    ///
    /// let example = Instant::new(159, 159, Era::Present);
    /// let in_secs = example.secs() as f64 + (example.nanos() as f64) * 1e-9;
    /// let precise = Instant::from_precise_seconds(in_secs, Era::Present);
    /// assert_eq!(precise, example);
    ///
    /// let example = Instant::new(159, 159, Era::Past);
    /// let in_secs = example.secs() as f64 + (example.nanos() as f64) * 1e-9;
    /// let precise = Instant::from_precise_seconds(in_secs, Era::Past);
    /// assert_eq!(precise, example);
    /// ```
    pub fn from_precise_seconds(seconds: f64, era: Era) -> Instant {
        let secs_u = seconds.round();
        let nanos = (seconds - secs_u) * 1e9;
        Instant {
            duration: Duration::new(seconds as u64, nanos.round() as u32),
            era: era,
        }
    }

    /// Returns the Duration with respect to Epoch (past OR present), check the `era()`
    pub fn duration(self) -> Duration {
        self.duration
    }

    /// Returns the number of seconds with respect to the epoch.
    pub fn secs(self) -> u64 {
        self.duration.as_secs()
    }

    /// Returns the number of nanoseconds of the given instant.
    pub fn nanos(self) -> u32 {
        self.duration.subsec_nanos()
    }

    /// Returns the Era associated with this instant, i.e. whether it's before or after
    /// the TAI Epoch.
    pub fn era(self) -> Era {
        self.era
    }
}

impl PartialEq for Instant {
    fn eq(&self, other: &Instant) -> bool {
        let spans_eq = self.secs() == other.secs() && self.nanos() == other.nanos();
        if spans_eq && self.secs() == 0 && self.nanos() == 0 {
            // Do not check the era if both Instants are strictly zero seconds before or after epoch
            true
        } else {
            spans_eq && self.era() == other.era()
        }
    }
}

impl Add<Duration> for Instant {
    type Output = Instant;

    /// Adds a given `std::time::Duration` to an `Instant`.
    ///
    /// # Examples
    /// ```
    /// use hifitime::instant::{Era, Instant, Duration};
    /// // Add in the Present era.
    /// let tick = Instant::new(159, 10, Era::Present) + Duration::new(5, 2);
    /// assert_eq!(tick.secs(), 164);
    /// assert_eq!(tick.nanos(), 12);
    /// assert_eq!(tick.era(), Era::Present);

    /// // Add in the Past era.
    /// let tick = Instant::new(159, 10, Era::Past) + Duration::new(5, 2);
    /// assert_eq!(tick.secs(), 154);
    /// assert_eq!(tick.nanos(), 8);
    /// assert_eq!(tick.era(), Era::Past);

    /// // Add from the Past to overflow into the Present
    /// let tick = Instant::new(159, 0, Era::Past) + Duration::new(160, 0);
    /// assert_eq!(tick.secs(), 1);
    /// assert_eq!(tick.nanos(), 0);
    /// assert_eq!(tick.era(), Era::Present);

    /// let tick = Instant::new(0, 5, Era::Past) + Duration::new(0, 6);
    /// assert_eq!(tick.secs(), 0);
    /// assert_eq!(tick.nanos(), 1);
    /// assert_eq!(tick.era(), Era::Present);
    /// ```
    fn add(self, delta: Duration) -> Instant {
        if delta.as_secs() == 0 && delta.subsec_nanos() == 0 {
            self
        } else {
            // Switch the era, an exact time of zero is in the Present era
            match self.era {
                Era::Past => {
                    if (delta.as_secs() >= self.duration.as_secs())
                        || (delta.as_secs() >= self.duration.as_secs()
                            && delta.as_secs() == 0
                            && delta.subsec_nanos() >= self.duration.subsec_nanos())
                    {
                        Instant::new(
                            delta.as_secs() - self.duration.as_secs(),
                            delta.subsec_nanos() - self.duration.subsec_nanos(),
                            Era::Present,
                        )
                    } else {
                        let mut cln = self;
                        cln.duration -= delta;
                        cln
                    }
                }
                Era::Present => {
                    // Adding a duration in the present is trivial
                    let mut cln = self;
                    cln.duration += delta;
                    cln
                }
            }
        }
    }
}

impl Sub<Duration> for Instant {
    type Output = Instant;

    /// Subtracts a given `std::time::Duration` from an `Instant`.
    /// # Examples
    ///
    /// ```
    /// use hifitime::instant::{Era, Instant, Duration};
    /// // Sub in the Present era.
    /// let tick = Instant::new(159, 10, Era::Present) - Duration::new(5, 2);
    /// assert_eq!(tick.secs(), 154);
    /// assert_eq!(tick.nanos(), 8);
    /// assert_eq!(tick.era(), Era::Present);

    /// // Sub in the Past era.
    /// let tick = Instant::new(159, 10, Era::Past) - Duration::new(5, 2);
    /// assert_eq!(tick.secs(), 164);
    /// assert_eq!(tick.nanos(), 12);
    /// assert_eq!(tick.era(), Era::Past);

    /// // Sub from the Present to overflow into the Past
    /// let tick = Instant::new(159, 0, Era::Present) - Duration::new(160, 0);
    /// assert_eq!(tick.secs(), 1);
    /// assert_eq!(tick.nanos(), 0);
    /// assert_eq!(tick.era(), Era::Past);

    /// let tick = Instant::new(0, 5, Era::Present) - Duration::new(0, 6);
    /// assert_eq!(tick.secs(), 0);
    /// assert_eq!(tick.nanos(), 1);
    /// assert_eq!(tick.era(), Era::Past);
    /// ```
    fn sub(self, delta: Duration) -> Instant {
        if delta.as_secs() == 0 && delta.subsec_nanos() == 0 {
            self
        } else {
            // Switch the era, an exact time of zero is in the Present era
            match self.era {
                Era::Past => {
                    // Subtracting a duration in the past is trivial
                    let mut cln = self;
                    cln.duration += delta;
                    cln
                }
                Era::Present => {
                    if (delta.as_secs() >= self.duration.as_secs())
                        || (delta.as_secs() >= self.duration.as_secs()
                            && delta.as_secs() == 0
                            && delta.subsec_nanos() >= self.duration.subsec_nanos())
                    {
                        Instant::new(
                            delta.as_secs() - self.duration.as_secs(),
                            delta.subsec_nanos() - self.duration.subsec_nanos(),
                            Era::Past,
                        )
                    } else {
                        let mut cln = self;
                        cln.duration -= delta;
                        cln
                    }
                }
            }
        }
    }
}

impl Sub<Instant> for Instant {
    type Output = f64;

    /// Subtracts a given `Instant` from another `Instant`. Returns the number of seconds as a positive or negative number.
    /// # Examples
    ///
    /// ```
    /// use hifitime::instant::{Era, Instant};
    /// // Sub in the Present era.
    /// let unix = Instant::new(2_208_988_800, 0, Era::Present);
    /// let unix_p1h = Instant::new(2_208_988_800 + 3_600, 0, Era::Present);
    /// assert_eq!(unix_p1h - unix, 3600.0);
    /// assert_eq!(unix - unix_p1h, -3600.0);

    /// // Sub in the Past era.
    /// let tick = Instant::new(159, 10, Era::Past);
    /// let tock = Instant::new(150, 15, Era::Past);
    /// assert_eq!(tick - tock, -8.999999995);
    /// assert_eq!(tock - tick, 8.999999995);

    /// // Sub across Epoch
    /// let tick = Instant::new(159, 10, Era::Past);
    /// let tock = Instant::new(159, 10, Era::Present);
    /// assert_eq!(tock - tick, 318.00000002);
    /// assert_eq!(tick - tock, -318.00000002);
    /// ```
    fn sub(self, other: Instant) -> f64 {
        if self == other {
            0.0
        } else {
            if self.era == other.era {
                let delta_secs = if self > other {
                    let delta = self.duration - other.duration;
                    delta.as_secs() as f64 + (delta.subsec_nanos() as f64) * 1e-9
                } else {
                    // Sub on Duration fails if duration will be less than zero.
                    let delta = other.duration - self.duration;
                    -1.0 * (delta.as_secs() as f64 + (delta.subsec_nanos() as f64) * 1e-9)
                };
                if self.era == Era::Past {
                    -1.0 * delta_secs
                } else {
                    delta_secs
                }
            } else {
                let delta = self.duration + other.duration;
                let delta_secs = delta.as_secs() as f64 + (delta.subsec_nanos() as f64) * 1e-9;
                if other.era == Era::Present {
                    // This means we are in the past, and past minus present is a negative number.
                    -1.0 * delta_secs
                } else {
                    delta_secs
                }
            }
        }
    }
}

#[test]
fn era_unittest() {
    assert_eq!(format!("{}", Era::Past), "Past");
    assert_eq!(format!("{}", Era::Present), "Present");
    assert!(Era::Past < Era::Present);
}

#[test]
fn instant_unittest() {
    // NOTE: These tests are copy-pasted into the documentation.
    // Add in the Present era.
    let tick = Instant::new(159, 10, Era::Present) + Duration::new(5, 2);
    assert_eq!(tick.secs(), 164);
    assert_eq!(tick.nanos(), 12);
    assert_eq!(tick.era(), Era::Present);

    // Add in the Past era.
    let tick = Instant::new(159, 10, Era::Past) + Duration::new(5, 2);
    assert_eq!(tick.secs(), 154);
    assert_eq!(tick.nanos(), 8);
    assert_eq!(tick.era(), Era::Past);

    // Add from the Past to overflow into the Present
    let tick = Instant::new(159, 0, Era::Past) + Duration::new(160, 0);
    assert_eq!(tick.secs(), 1);
    assert_eq!(tick.nanos(), 0);
    assert_eq!(tick.era(), Era::Present);

    let tick = Instant::new(0, 5, Era::Past) + Duration::new(0, 6);
    assert_eq!(tick.secs(), 0);
    assert_eq!(tick.nanos(), 1);
    assert_eq!(tick.era(), Era::Present);

    // Sub in the Present era.
    let tick = Instant::new(159, 10, Era::Present) - Duration::new(5, 2);
    assert_eq!(tick.secs(), 154);
    assert_eq!(tick.nanos(), 8);
    assert_eq!(tick.era(), Era::Present);

    // Sub in the Past era.
    let tick = Instant::new(159, 10, Era::Past) - Duration::new(5, 2);
    assert_eq!(tick.secs(), 164);
    assert_eq!(tick.nanos(), 12);
    assert_eq!(tick.era(), Era::Past);

    // Sub from the Present to overflow into the Past
    let tick = Instant::new(159, 0, Era::Present) - Duration::new(160, 0);
    assert_eq!(tick.secs(), 1);
    assert_eq!(tick.nanos(), 0);
    assert_eq!(tick.era(), Era::Past);

    let tick = Instant::new(0, 5, Era::Present) - Duration::new(0, 6);
    assert_eq!(tick.secs(), 0);
    assert_eq!(tick.nanos(), 1);
    assert_eq!(tick.era(), Era::Past);
}