use std::ops;
use crate::core::{duration_from_midnight, Tz};
use chrono::{NaiveDate, NaiveTime, Utc};
const DAY_SECS: i64 = 24 * 60 * 60;
pub(crate) fn date_from_ordinal(ordinal: i64) -> NaiveDate {
chrono::DateTime::<Utc>::from_timestamp(ordinal * DAY_SECS, 0)
.unwrap()
.date_naive()
}
pub(crate) fn days_since_unix_epoch(date: &chrono::DateTime<Utc>) -> i64 {
date.timestamp() / DAY_SECS
}
pub(crate) fn is_leap_year(year: i32) -> bool {
year.trailing_zeros() >= 2 && (year % 25 != 0 || year.trailing_zeros() >= 4)
}
pub(crate) fn get_year_len(year: i32) -> u16 {
if is_leap_year(year) {
366
} else {
365
}
}
pub(crate) trait DifferentSigns {
fn different_sign(a: Self, b: Self) -> bool;
}
macro_rules! impl_different_signs {
($($ty:ty),*) => {
$(
impl DifferentSigns for $ty {
fn different_sign(a: Self, b: Self) -> bool {
a > 0 && b < 0 || a < 0 && b > 0
}
})*
};
(@unsigned, $($ty:ty),*) => {
$(
impl DifferentSigns for $ty {
fn different_sign(_: Self, _: Self) -> bool {
false
}
})*
};
}
impl_different_signs!(isize, i32, i16);
impl_different_signs!(@unsigned, usize, u32, u16, u8);
pub(crate) fn pymod<T>(a: T, b: T) -> T
where
T: DifferentSigns + Copy + ops::Rem<Output = T> + ops::Add<Output = T>,
{
let r = a % b;
if T::different_sign(r, b) {
r + b
} else {
r
}
}
pub(crate) fn add_time_to_date(
tz: Tz,
date: NaiveDate,
time: NaiveTime,
) -> Option<chrono::DateTime<Tz>> {
if let Some(dt) = date.and_time(time).and_local_timezone(tz).single() {
return Some(dt);
}
let dt = date.and_hms_opt(0, 0, 0)?.and_local_timezone(tz).single()?;
let day_duration = duration_from_midnight(time);
dt.checked_add_signed(day_duration)
}
#[cfg(test)]
mod test {
use chrono::{Duration, TimeZone};
use super::*;
#[test]
fn naive_date_from_ordinal() {
let tests = [
(-1, NaiveDate::from_ymd_opt(1969, 12, 31).unwrap()),
(0, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()),
(1, NaiveDate::from_ymd_opt(1970, 1, 2).unwrap()),
(10, NaiveDate::from_ymd_opt(1970, 1, 11).unwrap()),
(365, NaiveDate::from_ymd_opt(1971, 1, 1).unwrap()),
(19877, NaiveDate::from_ymd_opt(2024, 6, 3).unwrap()),
];
for (days, expected) in tests {
assert_eq!(date_from_ordinal(days), expected, "seconds: {}", days);
}
}
#[test]
fn python_mod() {
assert_eq!(pymod(2, -3), -1);
assert_eq!(pymod(-2, 3), 1);
assert_eq!(pymod(-2, -3), -2);
assert_eq!(pymod(-3, -3), 0);
assert_eq!(pymod(0, 3), 0);
assert_eq!(pymod(1, 3), 1);
assert_eq!(pymod(2, 3), 2);
assert_eq!(pymod(3, 3), 0);
assert_eq!(pymod(4, 3), 1);
assert_eq!(pymod(6, 3), 0);
assert_eq!(pymod(-6, 3), 0);
assert_eq!(pymod(-6, -3), 0);
assert_eq!(pymod(6, -3), 0);
}
#[test]
fn leap_year() {
let tests = [
(2015, false),
(2016, true),
(2017, false),
(2018, false),
(2019, false),
(2020, true),
(2021, false),
];
for (year, expected_output) in tests {
let res = is_leap_year(year);
assert_eq!(res, expected_output);
}
}
#[test]
fn year_length() {
let tests = [(2015, 365), (2016, 366)];
for (year, expected_output) in tests {
let res = get_year_len(year);
assert_eq!(res, expected_output);
}
}
#[test]
fn adds_time_to_date() {
let tests = [
(
Tz::UTC,
NaiveDate::from_ymd_opt(2017, 1, 1).unwrap(),
NaiveTime::from_hms_opt(1, 15, 30).unwrap(),
Some(Tz::UTC.with_ymd_and_hms(2017, 1, 1, 1, 15, 30).unwrap()),
),
(
Tz::America__Vancouver,
NaiveDate::from_ymd_opt(2021, 3, 14).unwrap(),
NaiveTime::from_hms_opt(2, 22, 10).unwrap(),
Some(
Tz::America__Vancouver
.with_ymd_and_hms(2021, 3, 14, 0, 0, 0)
.unwrap()
+ Duration::hours(2)
+ Duration::minutes(22)
+ Duration::seconds(10),
),
),
(
Tz::America__New_York,
NaiveDate::from_ymd_opt(1997, 10, 26).unwrap(),
NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
Some(
Tz::America__New_York
.with_ymd_and_hms(1997, 10, 26, 9, 0, 0)
.unwrap(),
),
),
];
for (tz, date, time, expected_output) in tests {
let res = add_time_to_date(tz, date, time);
assert_eq!(res, expected_output);
}
}
}