use super::civil::{days_in_month, ymd_to_days};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConstructError {
InvalidDate { year: i32, month: u32, day: u32 },
InvalidTime {
hour: u32,
minute: u32,
second: u32,
nanosecond: u32,
},
}
impl std::fmt::Display for ConstructError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConstructError::InvalidDate { year, month, day } => {
write!(f, "Invalid date: {}-{:02}-{:02}", year, month, day)
}
ConstructError::InvalidTime {
hour,
minute,
second,
nanosecond,
} => write!(
f,
"Invalid time: {:02}:{:02}:{:02}.{:09}",
hour, minute, second, nanosecond
),
}
}
}
impl std::error::Error for ConstructError {}
#[inline]
fn validate_date(year: i32, month: u32, day: u32) -> Result<(), ConstructError> {
if month < 1 || month > 12 {
return Err(ConstructError::InvalidDate { year, month, day });
}
let max_day = days_in_month(year, month);
if day < 1 || day > max_day {
return Err(ConstructError::InvalidDate { year, month, day });
}
Ok(())
}
#[inline]
fn validate_time(
hour: u32,
minute: u32,
second: u32,
nanosecond: u32,
) -> Result<(), ConstructError> {
if hour >= 24 || minute >= 60 || second >= 60 || nanosecond >= 1_000_000_000 {
return Err(ConstructError::InvalidTime {
hour,
minute,
second,
nanosecond,
});
}
Ok(())
}
pub fn from_ymd(year: i32, month: u32, day: u32) -> Result<i64, ConstructError> {
validate_date(year, month, day)?;
let days = ymd_to_days(year, month, day);
Ok(days * 86400)
}
pub fn from_ymd_hms(
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
) -> Result<i64, ConstructError> {
validate_date(year, month, day)?;
validate_time(hour, minute, second, 0)?;
let days = ymd_to_days(year, month, day);
let seconds_in_day = hour * 3600 + minute * 60 + second;
Ok(days * 86400 + seconds_in_day as i64)
}
pub fn from_ymd_hms_nano(
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
nanosecond: u32,
) -> Result<i64, ConstructError> {
validate_date(year, month, day)?;
validate_time(hour, minute, second, nanosecond)?;
let days = ymd_to_days(year, month, day);
let seconds_in_day = hour * 3600 + minute * 60 + second;
Ok(days * 86_400_000_000_000 + seconds_in_day as i64 * 1_000_000_000 + nanosecond as i64)
}
#[inline]
pub fn from_timestamp_seconds(secs: i64) -> i64 {
secs
}
#[inline]
pub fn from_timestamp_millis(millis: i64) -> i64 {
millis
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_ymd_epoch() {
let ts = from_ymd(1970, 1, 1).unwrap();
assert_eq!(ts, 0);
}
#[test]
fn test_from_ymd() {
let ts = from_ymd(2000, 1, 1).unwrap();
assert_eq!(ts, 946684800);
}
#[test]
fn test_from_ymd_invalid_month() {
assert!(from_ymd(2000, 13, 1).is_err());
assert!(from_ymd(2000, 0, 1).is_err());
}
#[test]
fn test_from_ymd_invalid_day() {
assert!(from_ymd(2000, 2, 30).is_err()); assert!(from_ymd(2001, 2, 29).is_err()); assert!(from_ymd(2000, 4, 31).is_err()); }
#[test]
fn test_from_ymd_leap_day() {
assert!(from_ymd(2000, 2, 29).is_ok());
assert!(from_ymd(2004, 2, 29).is_ok());
}
#[test]
fn test_from_ymd_hms() {
let ts = from_ymd_hms(1970, 1, 1, 0, 0, 0).unwrap();
assert_eq!(ts, 0);
let ts = from_ymd_hms(2000, 1, 1, 12, 34, 56).unwrap();
assert_eq!(ts, 946730096);
}
#[test]
fn test_from_ymd_hms_invalid_time() {
assert!(from_ymd_hms(2000, 1, 1, 24, 0, 0).is_err());
assert!(from_ymd_hms(2000, 1, 1, 12, 60, 0).is_err());
assert!(from_ymd_hms(2000, 1, 1, 12, 34, 60).is_err());
}
#[test]
fn test_from_ymd_hms_nano() {
let ts = from_ymd_hms_nano(1970, 1, 1, 0, 0, 0, 123456789).unwrap();
assert_eq!(ts, 123456789);
let ts = from_ymd_hms_nano(1970, 1, 1, 0, 0, 1, 0).unwrap();
assert_eq!(ts, 1_000_000_000);
}
#[test]
fn test_from_ymd_hms_nano_invalid() {
assert!(from_ymd_hms_nano(2000, 1, 1, 12, 34, 56, 1_000_000_000).is_err());
}
}