pub mod ntp;
pub mod system;
pub mod timezones;
pub mod epoch {
pub const UNIX: &str = "1970-01-01 00:00:00";
pub const WINDOWS_NT: &str = "1601-01-01 00:00:00";
pub const WEBKIT: &str = "1601-01-01 00:00:00";
pub const MAC_OS: &str = "1904-01-01 00:00:00";
pub const MAC_OS_CFA: &str = "2001-01-01 00:00:00";
pub const SAS_4GL: &str = "1960-01-01 00:00:00";
}
use chrono::Local;
pub use ntp::*;
pub use system::*;
pub use timezones::*;
pub const REF_TIME_1970: u64 = 2208988800;
pub const OFFSET_1601: u64 = 11644473600;
pub const MAGIC_SAS_4GL: i64 = 315619200;
pub const MAGIC_MAC_OS: i64 = 2082844800;
pub const MAGIC_MAC_OS_CFA: i64 = 978307200;
pub fn now() -> i64 {
System::now().unix()
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum RelativeTime {
Past,
Present,
Future
}
impl core::fmt::Display for RelativeTime {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
RelativeTime::Past => write!(f, "past"),
RelativeTime::Present => write!(f, "present"),
RelativeTime::Future => write!(f, "future"),
}
}
}
pub trait Time {
fn now() -> Self;
fn strptime<T: ToString, G: ToString>(s: T, format: G) -> Self;
fn unix(&self) -> i64;
fn unix_ms(&self) -> i64;
fn windows_ns(&self) -> i64 {
((self.epoch() as f64) * 1e4) as i64
}
fn webkit(&self) -> i64 {
((self.epoch() as f64) * 1e3) as i64
}
fn mac_os(&self) -> i64 {
self.unix() + MAGIC_MAC_OS
}
fn mac_os_cfa(&self) -> i64 {
self.unix() - MAGIC_MAC_OS_CFA
}
fn sas_4gl(&self) -> i64 {
self.unix() + MAGIC_SAS_4GL
}
fn strftime(&self, format: &str) -> String;
fn epoch(&self) -> i64 {
self.unix_ms() + (OFFSET_1601 as i64 * 1000i64)
}
fn pretty(&self) -> String {
self.strftime("%Y-%m-%d %H:%M:%S")
}
#[doc(hidden)]
fn from_epoch(timestamp: u64) -> Self;
#[doc(hidden)]
fn raw(&self) -> u64;
fn iso8601(&self) -> String {
self.strftime("%Y-%m-%d %H:%M:%S.") + &(self.raw() % 1000).to_string()
}
fn rfc3339(&self) -> String {
self.strftime("%Y-%m-%dT%H:%M:%S.") + &(self.raw() % 1000).to_string() + "Z"
}
#[doc(hidden)]
fn utc_offset(&self) -> i32;
fn tz_offset(&self) -> String {
let offset = self.utc_offset();
let sign = if offset < 0 { "-" } else { "+" };
let offset = offset.abs();
let hours = offset / 3600;
let minutes = (offset % 3600) / 60;
format!("{}{:02}:{:02}", sign, hours, minutes)
}
fn tz_enum(&self) -> Option<Tz> {
Tz::from_offset(-self.utc_offset())
}
fn change_tz<T: ToString>(&self, offset: T) -> Self
where Self: Sized {
let offset = offset.to_string();
let offset_seconds = offset[1..3].parse::<i32>().unwrap() * 3600
+ offset[4..6].parse::<i32>().unwrap() * 60;
let offset_seconds = if offset.starts_with('+') {
offset_seconds
} else {
-offset_seconds
};
let duped_secs = offset_seconds;
let utc_self = Self::from_epoch_offset((self.raw() as i64 + (self.utc_offset() as i64 * 1000i64)) as u64, 0);
Self::from_epoch_offset((utc_self.raw() as i64 + (offset_seconds as i64 * 1000i64)) as u64, -duped_secs)
}
fn local(&self) -> Self
where Self: Sized {
self.change_tz(Local::now().format("%:z").to_string())
}
fn add_seconds(&self, duration: i64) -> Self
where Self: Sized {
Self::from_epoch((self.raw() as i64 + (duration * 1000)) as u64)
}
fn add_minutes(&self, minutes: i64) -> Self
where Self: Sized {
self.add_seconds(minutes * 60)
}
fn add_hours(&self, hours: i64) -> Self
where Self: Sized {
self.add_seconds(hours * 3600)
}
fn add_days(&self, days: i64) -> Self
where Self: Sized {
self.add_seconds(days * 86400)
}
fn add_weeks(&self, weeks: i64) -> Self
where Self: Sized {
self.add_seconds(weeks * 604800)
}
fn past_future<T: Time>(&self, other: &T) -> RelativeTime {
#[allow(clippy::comparison_chain)] if self.raw() < other.raw() {
RelativeTime::Past
} else if self.raw() > other.raw() {
RelativeTime::Future
} else {
RelativeTime::Present
}
}
fn add_duration<T: ImplsDuration>(&self, duration: T) -> Self
where Self: Sized {
self.add_seconds(duration.num_seconds())
}
fn cast<T: Time>(&self) -> T
where Self: Sized {
T::from_epoch(self.raw())
}
#[doc(hidden)]
fn from_epoch_offset(timestamp: u64, offset: i32) -> Self;
}
pub trait ImplsDuration {
fn num_seconds(&self) -> i64;
}
impl ImplsDuration for chrono::Duration {
fn num_seconds(&self) -> i64 {
self.num_seconds()
}
}
impl ImplsDuration for core::time::Duration {
fn num_seconds(&self) -> i64 {
self.as_secs() as i64
}
}
pub trait TimeDiff {
fn diff<T: Time>(&self, other: &T) -> u64
where
Self: Time,
{
self.raw().abs_diff(other.raw()) / 1000
}
fn diff_ms<T: Time>(&self, other: &T) -> u64
where
Self: Time,
{
self.raw().abs_diff(other.raw())
}
}
pub trait StrTime {
fn parse_time<T: Time>(&self, format: &str) -> T
where
Self: core::fmt::Display,
{
T::strptime(self, format)
}
fn strp_iso8601<T: Time>(&self) -> T
where
Self: core::fmt::Display,
{
T::strptime(self, "%Y-%m-%dT%H:%M:%S.%f")
}
fn strp_rf3339<T: Time>(&self) -> T
where
Self: core::fmt::Display,
{
T::strptime(self, "%Y-%m-%dT%H:%M:%S.%fZ")
}
}
pub trait IntTime: core::fmt::Display + Into<u64> {
fn unix<T: Time>(self) -> T {
let unix: u64 = self.into();
T::from_epoch((unix + OFFSET_1601) * 1000)
}
fn windows_ns<T: Time>(self) -> T {
T::from_epoch(self.into() / (1e4 as u64))
}
fn webkit<T: Time>(self) -> T {
T::from_epoch(self.into() / (1e3 as u64))
}
fn mac_os<T: Time>(self) -> T {
let selfu64: u64 = self.into();
let selfi64: i64 = selfu64 as i64;
let unix: i64 = selfi64 - MAGIC_MAC_OS;
T::from_epoch((unix + (OFFSET_1601 as i64)) as u64 * 1000)
}
fn mac_os_cfa<T: Time>(self) -> T {
let selfu64: u64 = self.into();
let unix: u64 = selfu64 + MAGIC_MAC_OS_CFA as u64;
T::from_epoch((unix + OFFSET_1601) * 1000)
}
fn sas_4gl<T: Time>(self) -> T {
let selfu64: u64 = self.into();
let selfi64: i64 = selfu64 as i64;
let unix: i64 = selfi64 - MAGIC_SAS_4GL;
T::from_epoch((unix + (OFFSET_1601 as i64)) as u64 * 1000)
}
fn ts_print(self) -> String {
let duration = chrono::Duration::seconds(self.into() as i64);
format!(
"{}w {}d {}h {}m {}s",
duration.num_weeks(),
duration.num_days() % 7,
duration.num_hours() % 24,
duration.num_minutes() % 60,
duration.num_seconds() % 60
)
}
}
impl StrTime for str {}
impl StrTime for String {}
impl<T: core::fmt::Display + Into<u64>> IntTime for T {}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_system() {
let x = System::now();
println!("{}", x);
println!("{}", x.unix_ms());
println!("{}", x.strftime("%Y-%m-%d %H:%M:%S"));
}
#[test]
fn test_ntp() {
let x = Ntp::now();
println!("{:#?}", x);
println!("{}", x.unix_ms());
println!("{}", x);
}
#[test]
fn strptime() {
let x = System::strptime("2015-02-18 23:16:09.234", "%Y-%m-%d %H:%M:%S%.3f");
println!("2015 - {}", x);
let x = Ntp::strptime("2021-01-01 00:00:00 +0000", "%Y-%m-%d %H:%M:%S %z");
println!("2021 - {}", x);
assert_eq!(x.unix(), 1609459200);
println!("1950 - {}", Ntp::strptime("1950-01-01 00:00:00", "%Y-%m-%d %H:%M:%S"))
}
#[test]
fn str_time() {
let date2017 = "2017-01-01 00:00:00".parse_time::<System>("%Y-%m-%d %H:%M:%S");
println!("2017 - {}", date2017);
assert_eq!(date2017.unix(), 1483228800);
}
#[test]
fn time_diff() {
let x = "2015-01-01 00:00:00".parse_time::<System>("%Y-%m-%d %H:%M:%S");
let y = "2017-01-01 00:00:00".parse_time::<System>("%Y-%m-%d %H:%M:%S");
println!("{} difference", y.diff(&x).ts_print());
println!("{} milliseconds difference", x.diff_ms(&y));
assert_eq!(x.diff(&y), 63158400u64);
}
#[test]
fn int_ntp_time() {
assert_eq!(1483228800u32.unix::<Ntp>().pretty(), "2017-01-01 00:00:00");
assert_eq!(
131277024000000000u64.windows_ns::<Ntp>().pretty(),
"2017-01-01 00:00:00"
);
assert_eq!(
3787310789u64.mac_os::<Ntp>().pretty(),
"2024-01-05 14:46:29"
);
assert_eq!(
726158877u32.mac_os_cfa::<Ntp>().pretty(),
"2024-01-05 14:47:57"
);
assert_eq!(0u32.sas_4gl::<Ntp>().pretty(), "1960-01-01 00:00:00");
}
#[test]
fn int_system_time() {
assert_eq!(1483228800u32.unix::<System>().pretty(), "2017-01-01 00:00:00");
assert_eq!(
131277024000000000u64.windows_ns::<System>().pretty(),
"2017-01-01 00:00:00"
);
assert_eq!(
3787310789u64.mac_os::<System>().pretty(),
"2024-01-05 14:46:29"
);
assert_eq!(
726158877u32.mac_os_cfa::<System>().pretty(),
"2024-01-05 14:47:57"
);
assert_eq!(0u32.sas_4gl::<System>().pretty(), "1960-01-01 00:00:00");
}
#[test]
fn windows_tests() {
let x = System::now();
println!("{} lots of 100ns since Windows epoch", x.windows_ns());
}
#[test]
fn mac_os() {
let x = System::now();
println!("{} seconds since Mac OS epoch", x.mac_os());
println!("{} seconds since Mac OS Absolute epoch", x.mac_os_cfa());
}
#[test]
fn sas_4gl() {
let x = System::now();
println!("{} seconds since SAS 4GL epoch", x.sas_4gl());
}
#[test]
fn rfc3339_iso8601() {
let x = System::now();
println!("{}", x.iso8601());
println!("{}", x.rfc3339());
}
#[test]
fn strptime_rfc_and_iso() {
let x = "2017-01-01T00:00:00.000".strp_iso8601::<System>();
let y = "2017-01-01T00:00:00.000Z".strp_rf3339::<System>();
assert_eq!(x.unix(), 1483228800);
assert_eq!(y.unix(), 1483228800);
}
#[test]
fn tz_tests() {
let x = Ntp::now();
println!("{:#?}", x);
println!("{}", x.change_tz("+01:00"));
println!("{}", x.change_tz("-01:00"));
}
#[test]
fn test_add_seconds() {
let x = System::now();
println!("{}", x.add_seconds(3600));
}
#[test]
fn test_add_minshoursdaysweeks() {
let x = System::now();
println!("{}", x.add_minutes(60));
println!("{}", x.add_hours(24));
println!("{}", x.add_days(7));
println!("{}", x.add_weeks(52));
}
#[test]
fn long_ago_dates() {
let x = System::from_epoch(0);
println!("{}", x);
println!("{}", x.unix());
println!("{}", x.strftime("%Y-%m-%d %H:%M:%S"));
}
#[test]
fn test_past_future() {
let x = "2029-01-01T00:00:00.000".strp_iso8601::<System>();
let y = "2017-01-01T00:00:00.000Z".strp_rf3339::<System>();
println!("{} is in the {}", x, x.past_future(&y));
println!("{} is in the {}", y, y.past_future(&x));
}
#[test]
fn test_add_duration() {
let x = System::now();
println!("{}", x.add_duration(std::time::Duration::from_secs(3600)));
println!("{}", x.add_duration(chrono::Duration::seconds(3600)));
}
#[test]
fn test_local() {
let x = System::now();
println!("{}", x.local());
}
#[test]
fn test_tz_enum() {
let x = System::now().change_tz("+08:00");
println!("{}", x.tz_enum().unwrap_or_default());
println!("{}", Tz::from_offset(3600).unwrap_or_default());
println!("{}", Tz::from_offset(0).unwrap_or_default()); println!("{}", Tz::from_offset(3600).unwrap_or_default()); println!("{}", Tz::from_offset(7200).unwrap_or_default()); println!("{}", Tz::from_offset(123456).unwrap_or_default()); println!("{}", Tz::Acst.offset_struct(System::now()));
}
#[test]
fn huge_number() {
let x = System::strptime("+262143-01-01 00:00:00", "%Y-%m-%d %H:%M:%S");
println!("{}", x);
}
#[test]
fn test_cast() {
let x = System::now();
println!("{:#?}", x.cast::<Ntp>());
}
}