use core::time::Duration;
#[cfg(feature = "std")]
use std::time::SystemTime;
use anyhow::{anyhow, Result};
use derive_more::{Add, Div, From, Into, Mul, Sub};
use crate::constants::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, From, Into)]
pub struct UTCTimestamp(Duration);
impl UTCTimestamp {
fn from_day_and_nanos(day: UTCDay, time_of_day_ns: u64) -> Self {
let secs = (day.0 as u64 * SECONDS_PER_DAY) + (time_of_day_ns / NANOS_PER_SECOND);
let nanos = (time_of_day_ns % NANOS_PER_SECOND) as u32;
Duration::new(secs, nanos).into()
}
pub fn from_day(day: UTCDay) -> Self {
let secs = day.0 as u64 * SECONDS_PER_DAY;
Duration::from_secs(secs).into()
}
pub fn try_from_day_and_nanos(day: UTCDay, time_of_day_ns: u64) -> Result<Self> {
if time_of_day_ns >= NANOS_PER_DAY {
return Err(anyhow!(
"Nanoseconds not within a day! (time_of_day_ns: {})",
time_of_day_ns
));
}
Ok(Self::from_day_and_nanos(day, time_of_day_ns))
}
#[cfg(feature = "std")]
pub fn try_from_system_time() -> Result<Self> {
let duration = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
Ok(duration.into())
}
pub fn as_utc_duration(&self) -> Duration {
self.0
}
pub fn to_utc_duration(self) -> Duration {
self.0
}
pub fn as_time_of_day_ns(&self) -> u64 {
((self.0.as_secs() % SECONDS_PER_DAY) * NANOS_PER_SECOND) + (self.0.subsec_nanos() as u64)
}
pub fn as_utc_day(&self) -> UTCDay {
((self.0.as_secs() / SECONDS_PER_DAY) as u32).into()
}
}
impl From<UTCDay> for UTCTimestamp {
fn from(day: UTCDay) -> Self {
UTCTimestamp::from_day(day)
}
}
pub trait UTCTransformations
where
Self: Sized,
{
fn from_utc_duration(duration: Duration) -> Self {
let timestamp = duration.into();
Self::from_utc_timestamp(timestamp)
}
fn as_utc_duration(&self) -> Duration {
self.as_utc_timestamp().into()
}
fn from_utc_secs(s: u64) -> Self {
let timestamp = Duration::from_secs(s).into();
Self::from_utc_timestamp(timestamp)
}
fn as_utc_secs(&self) -> u64 {
self.as_utc_duration().as_secs()
}
fn from_utc_millis(ms: u64) -> Self {
let timestamp = Duration::from_millis(ms).into();
Self::from_utc_timestamp(timestamp)
}
fn as_utc_millis(&self) -> u64 {
self.as_utc_duration().as_millis() as u64
}
fn from_utc_micros(us: u64) -> Self {
let timestamp = Duration::from_micros(us).into();
Self::from_utc_timestamp(timestamp)
}
fn as_utc_micros(&self) -> u64 {
self.as_utc_duration().as_micros() as u64
}
fn from_utc_nanos(ns: u64) -> Self {
let timestamp = Duration::from_nanos(ns).into();
Self::from_utc_timestamp(timestamp)
}
fn as_utc_nanos(&self) -> u64 {
self.as_utc_duration().as_nanos() as u64
}
#[cfg(feature = "std")]
fn try_from_system_time() -> Result<Self> {
let timestamp = UTCTimestamp::try_from_system_time()?;
Ok(Self::from_utc_timestamp(timestamp))
}
fn from_utc_timestamp(timestamp: UTCTimestamp) -> Self;
fn as_utc_timestamp(&self) -> UTCTimestamp;
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Default,
Add,
Sub,
Mul,
Div,
From,
Into,
)]
pub struct UTCDay(u32);
impl UTCDay {
pub fn as_utc_weekday(&self) -> u8 {
((self.0 as u64 + 4) % 7) as u8
}
}
impl UTCTransformations for UTCDay {
fn from_utc_secs(s: u64) -> Self {
Self((s / SECONDS_PER_DAY) as u32)
}
fn as_utc_secs(&self) -> u64 {
(self.0 as u64) * SECONDS_PER_DAY
}
fn from_utc_millis(ms: u64) -> Self {
Self((ms / MILLIS_PER_DAY) as u32)
}
fn as_utc_millis(&self) -> u64 {
(self.0 as u64) * MILLIS_PER_DAY
}
fn from_utc_micros(us: u64) -> Self {
Self((us / MICROS_PER_DAY) as u32)
}
fn as_utc_micros(&self) -> u64 {
(self.0 as u64) * MICROS_PER_DAY
}
fn from_utc_nanos(ns: u64) -> Self {
Self((ns / NANOS_PER_DAY) as u32)
}
fn as_utc_nanos(&self) -> u64 {
(self.0 as u64) * NANOS_PER_DAY
}
fn from_utc_timestamp(timestamp: UTCTimestamp) -> Self {
timestamp.as_utc_day()
}
fn as_utc_timestamp(&self) -> UTCTimestamp {
UTCTimestamp::from_day(*self)
}
}
impl From<Duration> for UTCDay {
fn from(duration: Duration) -> Self {
Self::from_utc_duration(duration)
}
}
impl From<UTCTimestamp> for UTCDay {
fn from(timestamp: UTCTimestamp) -> Self {
Self::from_utc_timestamp(timestamp)
}
}
#[cfg(test)]
mod test {
use anyhow::Result;
use core::time::Duration;
use crate::time::{UTCDay, UTCTimestamp, UTCTransformations, SECONDS_PER_DAY};
#[test]
fn test_from_days_and_nanos() -> Result<()> {
let test_cases = [
(Duration::from_nanos(0), UTCDay(0), 0, 4),
(Duration::from_nanos(123456789), UTCDay(0), 123456789, 4),
(
Duration::from_millis(1686756677000),
UTCDay(19522),
55_877_000_000_000,
3,
),
(
Duration::from_millis(1709220677000),
UTCDay(19782),
55_877_000_000_000,
4,
),
(
Duration::from_millis(1677684677000),
UTCDay(19417),
55_877_000_000_000,
3,
),
(
Duration::new(u32::MAX as u64 * SECONDS_PER_DAY, 0),
UTCDay(u32::MAX),
0,
0,
),
];
for (expected_timestamp, utc_days, time_of_day_ns, weekday) in test_cases {
let timestamp = UTCTimestamp::try_from_day_and_nanos(utc_days, time_of_day_ns)?;
assert_eq!(timestamp, expected_timestamp.into());
assert_eq!(UTCDay::from_utc_timestamp(timestamp), utc_days);
assert_eq!(timestamp.as_time_of_day_ns(), time_of_day_ns);
assert_eq!(utc_days.as_utc_weekday(), weekday);
}
Ok(())
}
}