use super::model::TimeScale;
use super::scales::julian_day_number;
use crate::constants::{SECONDS_PER_DAY, SECONDS_PER_WEEK};
#[must_use]
pub fn week_epoch_julian_day_number(system: TimeScale) -> Option<i64> {
match system {
TimeScale::Gpst | TimeScale::Gst | TimeScale::Qzsst => Some(julian_day_number(1980, 1, 6)),
TimeScale::Bdt => Some(julian_day_number(2006, 1, 1)),
TimeScale::Glonasst | TimeScale::Utc | TimeScale::Tai | TimeScale::Tt | TimeScale::Tdb => {
None
}
}
}
#[must_use]
pub fn week_from_calendar(system: TimeScale, year: i64, month: i64, day: i64) -> Option<u32> {
let epoch_jdn = week_epoch_julian_day_number(system)?;
let elapsed_days =
julian_day_number(year as i32, month as i32, day as i32).checked_sub(epoch_jdn)?;
if elapsed_days < 0 {
return None;
}
u32::try_from(elapsed_days / 7).ok()
}
#[must_use]
pub fn seconds_of_week_from_calendar(
year: i64,
month: i64,
day: i64,
hour: i64,
minute: i64,
second: i64,
) -> f64 {
const T: [i64; 12] = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4];
let y = if month < 3 { year - 1 } else { year };
let dow = (y + y / 4 - y / 100 + y / 400 + T[(month - 1) as usize] + day).rem_euclid(7);
dow as f64 * SECONDS_PER_DAY + (hour * 3600 + minute * 60 + second) as f64
}
#[must_use]
pub fn week_and_seconds_of_week(continuous_seconds: f64) -> (f64, f64) {
let week = (continuous_seconds / SECONDS_PER_WEEK).floor();
let seconds_of_week = continuous_seconds - week * SECONDS_PER_WEEK;
(week, seconds_of_week)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn week_and_seconds_of_week_splits_continuous_seconds() {
assert_eq!(week_and_seconds_of_week(0.0), (0.0, 0.0));
assert_eq!(week_and_seconds_of_week(SECONDS_PER_WEEK), (1.0, 0.0));
let (week, sow) = week_and_seconds_of_week(SECONDS_PER_WEEK * 3.0 + 123.5);
assert_eq!(week, 3.0);
assert!((sow - 123.5).abs() < 1e-9);
}
#[test]
fn week_epoch_metadata_is_centralized() {
assert_eq!(
week_epoch_julian_day_number(TimeScale::Gpst),
Some(julian_day_number(1980, 1, 6))
);
assert_eq!(
week_epoch_julian_day_number(TimeScale::Qzsst),
week_epoch_julian_day_number(TimeScale::Gpst)
);
assert_eq!(
week_epoch_julian_day_number(TimeScale::Bdt),
Some(julian_day_number(2006, 1, 1))
);
assert_eq!(week_epoch_julian_day_number(TimeScale::Glonasst), None);
}
#[test]
fn week_from_calendar_known_values() {
assert_eq!(week_from_calendar(TimeScale::Gpst, 1980, 1, 6), Some(0));
assert_eq!(week_from_calendar(TimeScale::Gpst, 1980, 1, 13), Some(1));
assert_eq!(week_from_calendar(TimeScale::Gpst, 1999, 8, 22), Some(1024));
assert_eq!(week_from_calendar(TimeScale::Bdt, 2006, 1, 1), Some(0));
assert_eq!(week_from_calendar(TimeScale::Gpst, 1979, 1, 1), None);
assert_eq!(week_from_calendar(TimeScale::Glonasst, 2020, 1, 1), None);
}
#[test]
fn seconds_of_week_sunday_origin() {
assert_eq!(seconds_of_week_from_calendar(1980, 1, 6, 0, 0, 0), 0.0);
assert_eq!(seconds_of_week_from_calendar(1980, 1, 6, 1, 2, 3), 3723.0);
assert_eq!(
seconds_of_week_from_calendar(1980, 1, 7, 0, 0, 0),
SECONDS_PER_DAY
);
}
}