use super::civil::{days_to_ymd, ymd_to_days};
use crate::time_units::TimeUnit;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DateTimeComponents {
pub year: i32,
pub month: u32, pub day: u32, pub hour: u32, pub minute: u32, pub second: u32, pub nanosecond: u32, }
#[inline]
pub fn extract_components(timestamp: i64, time_unit: TimeUnit) -> DateTimeComponents {
let (total_seconds, nanos) = match time_unit {
TimeUnit::Seconds => (timestamp, 0),
TimeUnit::Milliseconds => {
let secs = timestamp / 1_000;
let millis = (timestamp % 1_000) as i32;
let nanos = if millis < 0 {
((millis + 1_000) * 1_000_000) as u32
} else {
(millis * 1_000_000) as u32
};
(secs, nanos)
}
TimeUnit::Microseconds => {
let secs = timestamp / 1_000_000;
let micros = (timestamp % 1_000_000) as i32;
let nanos = if micros < 0 {
((micros + 1_000_000) * 1_000) as u32
} else {
(micros * 1_000) as u32
};
(secs, nanos)
}
TimeUnit::Nanoseconds => {
let secs = timestamp / 1_000_000_000;
let ns = (timestamp % 1_000_000_000) as i32;
let nanos = if ns < 0 {
(ns + 1_000_000_000) as u32
} else {
ns as u32
};
(secs, nanos)
}
TimeUnit::Days => {
return DateTimeComponents {
year: 0,
month: 0,
day: 0,
hour: 0,
minute: 0,
second: 0,
nanosecond: 0,
};
}
};
let days = total_seconds.div_euclid(86400);
let (year, month, day) = days_to_ymd(days);
let seconds_in_day = total_seconds.rem_euclid(86400) as u32;
let hour = seconds_in_day / 3600;
let minute = (seconds_in_day % 3600) / 60;
let second = seconds_in_day % 60;
DateTimeComponents {
year,
month,
day,
hour,
minute,
second,
nanosecond: nanos,
}
}
#[inline]
pub fn components_to_timestamp(comp: &DateTimeComponents, time_unit: TimeUnit) -> i64 {
let days = ymd_to_days(comp.year, comp.month, comp.day);
let seconds_in_day = comp.hour * 3600 + comp.minute * 60 + comp.second;
let total_seconds = days * 86400 + seconds_in_day as i64;
match time_unit {
TimeUnit::Seconds => total_seconds,
TimeUnit::Milliseconds => {
total_seconds * 1_000 + (comp.nanosecond / 1_000_000) as i64
}
TimeUnit::Microseconds => {
total_seconds * 1_000_000 + (comp.nanosecond / 1_000) as i64
}
TimeUnit::Nanoseconds => total_seconds * 1_000_000_000 + comp.nanosecond as i64,
TimeUnit::Days => days,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_components_seconds() {
let comp = extract_components(0, TimeUnit::Seconds);
assert_eq!(comp.year, 1970);
assert_eq!(comp.month, 1);
assert_eq!(comp.day, 1);
assert_eq!(comp.hour, 0);
assert_eq!(comp.minute, 0);
assert_eq!(comp.second, 0);
let ts = ymd_to_days(2000, 1, 1) * 86400 + 12 * 3600 + 34 * 60 + 56;
let comp = extract_components(ts, TimeUnit::Seconds);
assert_eq!(comp.year, 2000);
assert_eq!(comp.month, 1);
assert_eq!(comp.day, 1);
assert_eq!(comp.hour, 12);
assert_eq!(comp.minute, 34);
assert_eq!(comp.second, 56);
}
#[test]
fn test_extract_components_milliseconds() {
let comp = extract_components(123, TimeUnit::Milliseconds);
assert_eq!(comp.year, 1970);
assert_eq!(comp.month, 1);
assert_eq!(comp.day, 1);
assert_eq!(comp.hour, 0);
assert_eq!(comp.minute, 0);
assert_eq!(comp.second, 0);
assert_eq!(comp.nanosecond, 123_000_000);
}
#[test]
fn test_extract_components_microseconds() {
let comp = extract_components(123, TimeUnit::Microseconds);
assert_eq!(comp.year, 1970);
assert_eq!(comp.month, 1);
assert_eq!(comp.day, 1);
assert_eq!(comp.hour, 0);
assert_eq!(comp.minute, 0);
assert_eq!(comp.second, 0);
assert_eq!(comp.nanosecond, 123_000);
}
#[test]
fn test_extract_components_nanoseconds() {
let comp = extract_components(123, TimeUnit::Nanoseconds);
assert_eq!(comp.year, 1970);
assert_eq!(comp.month, 1);
assert_eq!(comp.day, 1);
assert_eq!(comp.hour, 0);
assert_eq!(comp.minute, 0);
assert_eq!(comp.second, 0);
assert_eq!(comp.nanosecond, 123);
}
#[test]
fn test_components_to_timestamp_roundtrip() {
let test_cases = vec![
(0i64, TimeUnit::Seconds),
(1_700_000_000, TimeUnit::Seconds),
(1_700_000_000_000, TimeUnit::Milliseconds),
(1_700_000_000_000_000, TimeUnit::Microseconds),
(1_700_000_000_000_000_000, TimeUnit::Nanoseconds),
];
for (ts, unit) in test_cases {
let comp = extract_components(ts, unit);
let ts2 = components_to_timestamp(&comp, unit);
assert_eq!(ts, ts2, "Round-trip failed for timestamp {} {:?}", ts, unit);
}
}
#[test]
fn test_components_to_timestamp() {
let comp = DateTimeComponents {
year: 2000,
month: 1,
day: 1,
hour: 12,
minute: 34,
second: 56,
nanosecond: 123_456_789,
};
let ts_sec = components_to_timestamp(&comp, TimeUnit::Seconds);
let expected_sec = ymd_to_days(2000, 1, 1) * 86400 + 12 * 3600 + 34 * 60 + 56;
assert_eq!(ts_sec, expected_sec);
let ts_ms = components_to_timestamp(&comp, TimeUnit::Milliseconds);
let expected_ms = expected_sec * 1_000 + 123;
assert_eq!(ts_ms, expected_ms);
let ts_us = components_to_timestamp(&comp, TimeUnit::Microseconds);
let expected_us = expected_sec * 1_000_000 + 123_456;
assert_eq!(ts_us, expected_us);
let ts_ns = components_to_timestamp(&comp, TimeUnit::Nanoseconds);
let expected_ns = expected_sec * 1_000_000_000 + 123_456_789;
assert_eq!(ts_ns, expected_ns);
}
#[test]
fn test_negative_timestamps() {
let comp = extract_components(-1, TimeUnit::Seconds);
assert_eq!(comp.year, 1969);
assert_eq!(comp.month, 12);
assert_eq!(comp.day, 31);
assert_eq!(comp.hour, 23);
assert_eq!(comp.minute, 59);
assert_eq!(comp.second, 59);
}
}