#![crate_name = "tai64"]
#![crate_type = "rlib"]
#![allow(unknown_lints, suspicious_arithmetic_impl)]
#![deny(
warnings,
missing_docs,
unsafe_code,
unused_import_braces,
unused_qualifications
)]
#![doc(html_root_url = "https://docs.rs/tai64/1.0.0")]
extern crate byteorder;
#[cfg(feature = "chrono")]
extern crate chrono;
use byteorder::{BigEndian, ByteOrder};
#[cfg(feature = "chrono")]
use chrono::{DateTime, NaiveDateTime, Utc};
use std::ops::{Add, Sub};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)]
pub struct TAI64(pub u64);
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)]
pub struct TAI64N(pub TAI64, pub u32);
impl TAI64 {
pub fn to_external(self) -> [u8; 8] {
let mut result = [0u8; 8];
BigEndian::write_u64(&mut result, self.0);
result
}
pub fn from_external(ext: &[u8]) -> Option<Self> {
if ext.len() != 8 {
None
} else {
Some(TAI64(BigEndian::read_u64(ext)))
}
}
}
impl TAI64N {
pub fn to_external(&self) -> [u8; 12] {
let mut result = [0u8; 12];
result[..8].copy_from_slice(&self.0.to_external());
BigEndian::write_u32(&mut result[8..], self.1);
result
}
pub fn from_external(ext: &[u8]) -> Option<Self> {
if ext.len() != 12 {
return None;
}
let s = TAI64::from_external(&ext[..8]).unwrap();
let n = BigEndian::read_u32(&ext[8..]);
if n <= 999_999_999 {
Some(TAI64N(s, n))
} else {
None
}
}
}
impl TAI64N {
pub fn now() -> TAI64N {
TAI64N::from_system_time(&SystemTime::now())
}
}
const NANOSECONDS_PER_SECOND: u32 = 1_000_000_000;
impl Add<u64> for TAI64 {
type Output = TAI64;
fn add(self, x: u64) -> TAI64 {
TAI64(self.0 + x)
}
}
impl Sub<u64> for TAI64 {
type Output = TAI64;
fn sub(self, x: u64) -> TAI64 {
TAI64(self.0 - x)
}
}
impl Add<Duration> for TAI64N {
type Output = TAI64N;
fn add(self, d: Duration) -> TAI64N {
let n = self.1 + d.subsec_nanos();
let (carry, n) = if n >= NANOSECONDS_PER_SECOND {
(1, n - NANOSECONDS_PER_SECOND)
} else {
(0, n)
};
TAI64N(self.0 + d.as_secs() + carry, n)
}
}
impl Sub<Duration> for TAI64N {
type Output = TAI64N;
fn sub(self, d: Duration) -> TAI64N {
let (carry, n) = if self.1 >= d.subsec_nanos() {
(0, self.1 - d.subsec_nanos())
} else {
(1, NANOSECONDS_PER_SECOND + self.1 - d.subsec_nanos())
};
TAI64N(self.0 - carry - d.as_secs(), n)
}
}
impl TAI64N {
pub fn duration_since(&self, other: &TAI64N) -> Result<Duration, Duration> {
if self >= other {
let (carry, n) = if self.1 >= other.1 {
(0, self.1 - other.1)
} else {
(1, NANOSECONDS_PER_SECOND + self.1 - other.1)
};
let s = (self.0).0 - carry - (other.0).0;
Ok(Duration::new(s, n))
} else {
Err(other.duration_since(self).unwrap())
}
}
}
impl TAI64 {
pub fn from_unix(secs: i64) -> Self {
TAI64(secs.checked_add(10 + (1 << 62)).unwrap() as u64)
}
pub fn to_unix(self) -> i64 {
(self.0 as i64).checked_sub(10 + (1 << 62)).unwrap()
}
}
pub const UNIX_EPOCH_TAI64N: TAI64N = TAI64N(TAI64(10 + (1 << 62)), 0);
impl TAI64N {
pub fn from_system_time(t: &SystemTime) -> Self {
match t.duration_since(UNIX_EPOCH) {
Ok(d) => UNIX_EPOCH_TAI64N + d,
Err(e) => UNIX_EPOCH_TAI64N - e.duration(),
}
}
pub fn to_system_time(&self) -> SystemTime {
match self.duration_since(&UNIX_EPOCH_TAI64N) {
Ok(d) => UNIX_EPOCH + d,
Err(d) => UNIX_EPOCH - d,
}
}
}
impl From<SystemTime> for TAI64N {
fn from(t: SystemTime) -> TAI64N {
TAI64N::from_system_time(&t)
}
}
#[cfg(feature = "chrono")]
impl TAI64N {
pub fn from_datetime_utc(t: &DateTime<Utc>) -> Self {
let unix_epoch: DateTime<Utc> =
DateTime::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc);
let duration = t.signed_duration_since(unix_epoch);
if duration.num_seconds() > 0 {
UNIX_EPOCH_TAI64N + duration.to_std().unwrap()
} else {
UNIX_EPOCH_TAI64N - unix_epoch.signed_duration_since(*t).to_std().unwrap()
}
}
pub fn to_datetime_utc(&self) -> DateTime<Utc> {
let (secs, nanos) = match self.to_system_time().duration_since(UNIX_EPOCH) {
Ok(duration) => (duration.as_secs() as i64, duration.subsec_nanos()),
Err(e) => (
-(e.duration().as_secs() as i64),
e.duration().subsec_nanos(),
),
};
DateTime::from_utc(NaiveDateTime::from_timestamp(secs, nanos), Utc)
}
}
#[cfg(feature = "chrono")]
impl From<DateTime<Utc>> for TAI64N {
fn from(t: DateTime<Utc>) -> TAI64N {
TAI64N::from_datetime_utc(&t)
}
}
#[cfg(test)]
#[macro_use]
extern crate quickcheck;
#[cfg(test)]
mod tests {
#[cfg(feature = "chrono")]
extern crate chrono;
use super::*;
use std::time::{Duration, UNIX_EPOCH};
#[cfg(feature = "chrono")]
use self::chrono::prelude::*;
use quickcheck::{Arbitrary, Gen};
#[cfg(feature = "chrono")]
#[test]
fn known_answer() {
let t = NaiveDate::from_ymd(1992, 6, 2).and_hms(8, 6, 59);
let unix_secs = t.timestamp();
let tai64 = TAI64::from_unix(unix_secs);
assert_eq!(tai64.0, 0x400000002a2b2c2d);
assert_eq!(
&tai64.to_external(),
&[0x40, 0, 0, 0, 0x2a, 0x2b, 0x2c, 0x2d]
);
}
#[test]
fn before_epoch() {
let t = UNIX_EPOCH - Duration::new(0, 1);
let tai64n = TAI64N::from_system_time(&t);
let t1 = tai64n.to_system_time();
assert_eq!(t, t1);
let t = UNIX_EPOCH - Duration::new(488294802189, 999999999);
let tai64n = TAI64N::from_system_time(&t);
let t1 = tai64n.to_system_time();
assert_eq!(t, t1);
let t = UNIX_EPOCH - Duration::new(73234, 68416841);
let tai64n = TAI64N::from_system_time(&t);
let t1 = tai64n.to_system_time();
assert_eq!(t, t1);
}
impl Arbitrary for TAI64N {
fn arbitrary<G: Gen>(g: &mut G) -> Self {
let s = u64::arbitrary(g);
let n = u32::arbitrary(g) % NANOSECONDS_PER_SECOND;
TAI64N(TAI64(s), n)
}
}
quickcheck!{
fn tai64n_add_sub(x: TAI64N, y: Duration) -> bool {
x + y - y == x
}
fn duration_add_sub(x: TAI64N, y: TAI64N) -> bool {
match x.duration_since(&y) {
Ok(d) => {
assert_eq!(x, y + d);
assert_eq!(y, x - d);
}
Err(d) => {
assert_eq!(y, x + d);
assert_eq!(x, y - d);
}
}
true
}
fn to_from_system_time(before_epoch: bool, d: Duration) -> bool {
let st = if before_epoch {
UNIX_EPOCH + d
} else {
UNIX_EPOCH - d
};
let st1 = TAI64N::from_system_time(&st).to_system_time();
st == st1
}
}
}