From 3dcb5b69911a61b002d7e8b7c775c9d8b88f4ef3 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Sun, 11 Jun 2023 01:45:15 -0400 Subject: [PATCH] Parse `DateTime` from Unix timestamp --- src/time/epoch.rs | 116 ++++++++++++++++++++++++++++++++++++++++++++++ src/time/mod.rs | 4 +- 2 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 src/time/epoch.rs diff --git a/src/time/epoch.rs b/src/time/epoch.rs new file mode 100644 index 0000000..2a45281 --- /dev/null +++ b/src/time/epoch.rs @@ -0,0 +1,116 @@ +use super::{DateTime, TimeZone}; + +pub const SECS_PER_DAY: i64 = 86400; + +fn days_since_epoch(ts: i64) -> i64 { + ts / SECS_PER_DAY +} + +fn is_leap(year: i64) -> bool { + year % 4 == 0 && (year % 100 != 0 || year % 16 == 0) +} + +fn year_from_ts_with_remainder(ts: i64) -> (i64, i64) { + let mut year = 1970; + let mut days = days_since_epoch(ts); + loop { + let days_in_year = if is_leap(year) { + 366 + } else { + 365 + }; + if days > days_in_year { + days -= days_in_year; + year += 1; + } else { + break; + } + } + (year, days) +} + +fn days_in_month(month: u8, year: i64) -> i64 { + assert!(month < 13); + match month { + 1 | 3 | 5 | 7 | 10 | 12 => 31, + 2 => { + if is_leap(year) { + 29 + } else { + 28 + } + }, + 4 | 6 | 9 | 11 => 30, + _ => unreachable!(), + } +} + +fn month_and_days_from_ordinal(days: i64, year: i64) -> (u8, i64) { + assert!( + if is_leap(year) { + days < 366 + } else { + days < 365 + } + ); + let mut days = days; + let mut month = 1; + while days_in_month(month, year) <= days { + days -= days_in_month(month, year); + month += 1; + } + (month, days) +} + +pub(super) fn date_time_from_ts(ts: i64) -> DateTime { + let (year, days) = year_from_ts_with_remainder(ts); + let (month, days) = month_and_days_from_ordinal(days, year); + let seconds = ts % SECS_PER_DAY; + println!("Seconds: {seconds}"); + let minutes = seconds / 60; + println!("Minutes: {minutes}"); + let second = seconds % 60; + let hour = minutes / 60; + let minute = minutes % 60; + DateTime { + year: year as u32, + month: month.try_into().unwrap(), + day: (days + 1).try_into().unwrap(), + hour: Some((hour + 1).try_into().unwrap()), + minute: Some((minute).try_into().unwrap()), + second: Some(second.try_into().unwrap()), + tz: Some(TimeZone::UTC), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn get_year() { + let ts = 1686459092; + let (y, r) = year_from_ts_with_remainder(ts); + assert_eq!(y, 2023); + assert_eq!(r, 161); + } + + #[test] + fn get_month_and_days() { + let ts = 1686459092; + let (year, days) = year_from_ts_with_remainder(ts); + let (month, days) = month_and_days_from_ordinal(days, year); + assert_eq!(month, 6); + assert_eq!(days, 10); + } + + #[test] + fn get_datetime() { + let ts = 1686462068; + let dt = date_time_from_ts(ts); + assert_eq!(dt.day, 11); + assert_eq!(dt.hour, Some(6)); + assert_eq!(dt.minute, Some(41)); + assert_eq!(dt.second, Some(8)); + } +} diff --git a/src/time/mod.rs b/src/time/mod.rs index 6d64680..5a7db17 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -1,13 +1,13 @@ //! A container representing a moment in ISO-8601 format Time -use std::{cmp::Ordering, fmt, str::FromStr}; +use std::{fmt, str::FromStr}; +pub mod epoch; mod error; mod parser; pub use {error::Error, parser::Parser}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use x509_parser::der_parser::ber::ber_read_element_header; #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]