use crate::{
calendars::Calendar,
constants,
datetime::CFDatetime,
datetimes::traits::IsLeap,
duration::CFDuration,
parser::{parse_cf_time, Unit},
};
use std::time::Duration;
pub fn get_timestamp_from_ymd<T: IsLeap>(
year: i64,
month: u8,
day: u8,
) -> Result<i64, crate::errors::Error> {
let mut timestamp: i64 = 0;
let mut current_year: i64 = year;
loop {
if current_year == constants::UNIX_DEFAULT_YEAR {
break;
}
let year_to_look_at = current_year - (current_year > constants::UNIX_DEFAULT_YEAR) as i64;
let seconds_in_year: i64 = if T::is_leap(year_to_look_at) {
constants::SECONDS_PER_YEAR_LEAP
} else {
constants::SECONDS_PER_YEAR_NON_LEAP
};
if current_year > constants::UNIX_DEFAULT_YEAR {
timestamp += seconds_in_year;
current_year -= 1;
} else {
timestamp -= seconds_in_year;
current_year += 1;
}
}
let mut current_month = 0;
loop {
if current_month + 1 == month {
break;
}
if T::is_leap(year) {
timestamp += constants::DAYS_PER_MONTH_LEAP[(current_month) as usize] as i64
* constants::SECS_PER_DAY as i64;
} else {
timestamp += constants::DAYS_PER_MONTH[(current_month) as usize] as i64
* constants::SECS_PER_DAY as i64;
}
current_month += 1;
}
timestamp += (day as i64 - 1) * constants::SECS_PER_DAY as i64;
Ok(timestamp)
}
pub fn get_hms_from_timestamp(timestamp: i64) -> (u8, u8, u8) {
let _mod_sec = constants::SECS_PER_DAY as i64;
let seconds = (timestamp % constants::SECS_PER_DAY as i64 + constants::SECS_PER_DAY as i64)
% constants::SECS_PER_DAY as i64;
let sec = (seconds % 60) as u8;
let min = ((seconds / 60) % 60) as u8;
let hour = ((seconds / 3600) % 24) as u8;
(hour, min, sec)
}
pub fn get_ymd_hms_from_timestamp<T: IsLeap>(timestamp: i64) -> (i64, u8, u8, u8, u8, u8) {
let mut remaining_timestamp = timestamp;
let mut current_year = constants::UNIX_DEFAULT_YEAR;
let direction = if timestamp >= 0 { 1 } else { -1 };
loop {
let year_to_look_at = if current_year > constants::UNIX_DEFAULT_YEAR {
current_year
} else {
current_year - 1
};
let seconds_in_year: i64 = if T::is_leap(year_to_look_at) {
constants::SECONDS_PER_YEAR_LEAP
} else {
constants::SECONDS_PER_YEAR_NON_LEAP
};
let new_remaining = remaining_timestamp - direction * seconds_in_year;
if direction == 1 && (new_remaining < 0) {
break;
}
else if direction == -1 && (new_remaining >= 0) {
remaining_timestamp = new_remaining;
current_year += direction;
break;
}
remaining_timestamp = new_remaining;
current_year += direction;
}
let mut month: i64 = 0;
loop {
let days_in_month: i64 = if T::is_leap(current_year) {
constants::DAYS_PER_MONTH_LEAP[month as usize] as i64
} else {
constants::DAYS_PER_MONTH[month as usize] as i64
};
let seconds_in_month = days_in_month * constants::SECS_PER_DAY as i64;
if remaining_timestamp < seconds_in_month {
break;
}
remaining_timestamp -= seconds_in_month;
month += 1;
}
let day = (remaining_timestamp / (constants::SECS_PER_DAY as i64)) as u8;
let (hour, min, sec) = get_hms_from_timestamp(remaining_timestamp);
(current_year, month as u8 + 1, day + 1, hour, min, sec)
}
pub fn is_leap_gregorian(year: i64) -> bool {
let f_year = ((year >> 63) & 1) + year;
(f_year % 400 == 0) || ((f_year % 4 == 0) && (f_year % 100 != 0))
}
pub fn is_leap_julian(year: i64) -> bool {
(((year >> 63) & 1) + year) % 4 == 0
}
fn extract_seconds_and_nanoseconds(seconds: f32) -> (u64, u32) {
let duration = Duration::from_secs_f32(seconds);
let secs = duration.as_secs();
let nanosecs = duration.subsec_nanos();
(secs, nanosecs)
}
pub fn get_timestamp_from_hms(
hour: u8,
min: u8,
sec: f32,
) -> Result<(i64, u32), crate::errors::Error> {
if hour > 23 {
return Err(crate::errors::Error::InvalidTime(
format!("Hour {hour} is out of bounds").to_string(),
));
}
if min > 59 {
return Err(crate::errors::Error::InvalidTime(
format!("Minute {min} is out of bounds").to_string(),
));
}
if !(0.0..60.0).contains(&sec) {
return Err(crate::errors::Error::InvalidTime(
format!("Second {sec} is out of bounds").to_string(),
));
}
let (round_seconds, nanoseconds) = extract_seconds_and_nanoseconds(sec);
let total_seconds = (hour as u32 * constants::SECS_PER_HOUR
+ min as u32 * constants::SECS_PER_MINUTE
+ round_seconds as u32)
% constants::SECS_PER_DAY;
Ok((total_seconds as i64, nanoseconds))
}
pub fn get_datetime_and_unit_from_units(
units: &str,
calendar: Calendar,
) -> Result<(CFDatetime, Unit), crate::errors::Error> {
let parsed_cf_time = parse_cf_time(units)?;
let (year, month, day) = parsed_cf_time.datetime.ymd;
let (hour, minute, second) = match parsed_cf_time.datetime.hms {
Some(hms) => (hms.0, hms.1, hms.2),
None => (0, 0, 0.0),
};
let cf_datetime = CFDatetime::from_ymd_hms(year, month, day, hour, minute, second, calendar)?;
let unit = parsed_cf_time.unit;
Ok((cf_datetime, unit))
}
pub fn normalize_nanoseconds(nanoseconds: i64) -> (i64, u32) {
let mut remaining_seconds = nanoseconds / 1e9 as i64;
let remaining_nanoseconds: i64 = if remaining_seconds < 0 {
remaining_seconds -= 1;
(nanoseconds + (remaining_seconds.abs() * 1_000_000_000)) % 1_000_000_000
} else {
nanoseconds % 1e9 as i64
};
(remaining_seconds, remaining_nanoseconds as u32)
}
pub fn unit_to_encode(unit: &Unit, duration: CFDuration) -> f64 {
match unit {
Unit::Year => duration.num_years(), Unit::Month => duration.num_months(), Unit::Day => duration.num_days(), Unit::Hour => duration.num_hours(), Unit::Minute => duration.num_minutes(), Unit::Second => duration.num_seconds(), Unit::Millisecond => duration.num_milliseconds(), Unit::Microsecond => duration.num_microseconds(), Unit::Nanosecond => duration.num_nanoseconds(), }
}