use crate::MILLIS_PER_DAY;
const SECONDS_PER_DAY: i32 = 86400;
const DAYS_PER_CYCLE: i32 = 146097;
const DAYS_0000_TO_1970: i32 = (DAYS_PER_CYCLE * 5) - (30 * 365 + 7);
const SUPPORT_NEGATIVE_YEAR: bool = false;
static DAYS_PER_MONTH: [[i32; 12]; 2] = [
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
];
#[inline]
fn is_leap_year(year: i32) -> bool {
((year % 4) == 0) & ((year % 100) != 0) | ((year % 400) == 0)
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct EpochDays(i32);
impl EpochDays {
#[inline]
pub fn new(epoch_days: i32) -> Self {
Self(epoch_days)
}
#[inline]
pub fn days(&self) -> i32 {
self.0
}
#[inline]
pub fn from_ymd(year: i32, month: i32, day: i32) -> Self {
let y = year;
let m = month;
let mut total = 365 * y;
total += if y < 0 && SUPPORT_NEGATIVE_YEAR {
-(y / -4 - y / -100 + y / -400)
} else {
(y + 3) / 4 - (y + 99) / 100 + (y + 399) / 400
};
total += ((367 * m - 362) / 12);
total += day - 1;
total -= if m > 2 {
if !is_leap_year(year) {
2
} else {
1
}
} else {
0
};
Self(total - DAYS_0000_TO_1970)
}
#[inline]
pub fn to_ymd(&self) -> (i32, i32, i32) {
let epoch_days = self.0;
let mut zero_day = epoch_days + DAYS_0000_TO_1970;
zero_day -= 60; let mut adjust = 0;
if zero_day < 0 && SUPPORT_NEGATIVE_YEAR {
let adjust_cycles = (zero_day + 1) / DAYS_PER_CYCLE - 1;
adjust = adjust_cycles * 400;
zero_day += -adjust_cycles * DAYS_PER_CYCLE;
}
let mut year_est = (400 * zero_day + 591) / DAYS_PER_CYCLE;
let mut doy_est =
zero_day - (365 * year_est + year_est / 4 - year_est / 100 + year_est / 400);
if doy_est < 0 {
year_est -= 1;
doy_est = zero_day - (365 * year_est + year_est / 4 - year_est / 100 + year_est / 400);
}
year_est += adjust; let march_doy0 = doy_est;
let march_month0 = (march_doy0 * 5 + 2) / 153;
let month = (march_month0 + 2) % 12 + 1;
let dom = march_doy0 - (march_month0 * 306 + 5) / 10 + 1;
year_est += march_month0 / 10;
(year_est, month, dom)
}
#[inline]
pub fn from_timestamp_millis(ts: i64) -> Self {
Self::from_timestamp_millis_float(ts as f64)
}
#[inline]
pub fn from_timestamp_millis_float(ts: f64) -> Self {
let epoch_days = (ts * (1.0 / MILLIS_PER_DAY as f64)).floor();
Self(unsafe { epoch_days.to_int_unchecked() })
}
#[inline]
pub fn to_timestamp_millis(&self) -> i64 {
(self.0 as i64) * MILLIS_PER_DAY
}
#[inline]
pub fn to_timestamp_millis_float(&self) -> f64 {
(self.0 as f64) * (MILLIS_PER_DAY as f64)
}
#[inline]
pub fn add_months(&self, months: i32) -> Self {
let (mut y, mut m, mut d) = self.to_ymd();
let mut m0 = m - 1;
m0 += months;
y += m0.div_euclid(12);
m0 = m0.rem_euclid(12);
d = d.min(DAYS_PER_MONTH[is_leap_year(y) as usize][m0 as usize] as _);
m = m0 + 1;
Self::from_ymd(y, m, d)
}
#[inline]
pub fn date_trunc_month(&self) -> Self {
let (y, m, d) = self.to_ymd();
Self::from_ymd(y, m, 1)
}
#[inline]
pub fn date_trunc_year(&self) -> Self {
let (y, m, d) = self.to_ymd();
Self::from_ymd(y, 1, 1)
}
#[inline]
pub fn date_trunc_quarter(&self) -> Self {
let (y, m, d) = self.to_ymd();
Self::from_ymd(y, (m - 1) / 3 * 3 + 1, 1)
}
#[inline]
pub fn extract_year(&self) -> i32 {
self.to_ymd().0
}
#[inline]
pub fn extract_month(&self) -> i32 {
self.to_ymd().1
}
#[inline]
pub fn extract_quarter(&self) -> i32 {
(self.to_ymd().1 - 1) / 3 + 1
}
#[inline]
pub fn extract_day_of_month(&self) -> i32 {
self.to_ymd().2
}
}
#[cfg(test)]
mod tests {
use crate::epoch_days::is_leap_year;
use crate::EpochDays;
#[test]
fn test_is_leap_year() {
assert!(!is_leap_year(1900));
assert!(!is_leap_year(1999));
assert!(is_leap_year(2000));
assert!(!is_leap_year(2001));
assert!(!is_leap_year(2002));
assert!(!is_leap_year(2003));
assert!(is_leap_year(2004));
assert!(is_leap_year(2020));
}
#[test]
fn test_to_epoch_day() {
assert_eq!(0, EpochDays::from_ymd(1970, 1, 1).0);
assert_eq!(1, EpochDays::from_ymd(1970, 1, 2).0);
assert_eq!(365, EpochDays::from_ymd(1971, 1, 1).0);
assert_eq!(365 * 2, EpochDays::from_ymd(1972, 1, 1).0);
assert_eq!(365 * 2 + 366, EpochDays::from_ymd(1973, 1, 1).0);
assert_eq!(18998, EpochDays::from_ymd(2022, 1, 6).0);
assert_eq!(19198, EpochDays::from_ymd(2022, 7, 25).0);
}
#[test]
fn test_date_trunc_year_epoch_days() {
assert_eq!(18993, EpochDays::new(19198).date_trunc_year().days());
}
#[test]
fn test_extract_year() {
assert_eq!(2022, EpochDays::from_ymd(2022, 1, 1).extract_year());
assert_eq!(2022, EpochDays::from_ymd(2022, 8, 24).extract_year());
assert_eq!(2022, EpochDays::from_ymd(2022, 12, 31).extract_year());
}
#[test]
fn test_extract_month() {
assert_eq!(1, EpochDays::from_ymd(2000, 1, 1).extract_month());
assert_eq!(2, EpochDays::from_ymd(2000, 2, 1).extract_month());
assert_eq!(2, EpochDays::from_ymd(2000, 2, 29).extract_month());
assert_eq!(1, EpochDays::from_ymd(2022, 1, 1).extract_month());
assert_eq!(8, EpochDays::from_ymd(2022, 8, 24).extract_month());
assert_eq!(12, EpochDays::from_ymd(2022, 12, 31).extract_month());
}
#[test]
fn test_extract_day() {
assert_eq!(1, EpochDays::from_ymd(2000, 1, 1).extract_day_of_month());
assert_eq!(1, EpochDays::from_ymd(2000, 2, 1).extract_day_of_month());
assert_eq!(29, EpochDays::from_ymd(2000, 2, 29).extract_day_of_month());
assert_eq!(1, EpochDays::from_ymd(2000, 3, 1).extract_day_of_month());
}
#[test]
fn test_extract_quarter() {
assert_eq!(1, EpochDays::from_ymd(2000, 1, 1).extract_quarter());
assert_eq!(1, EpochDays::from_ymd(2000, 2, 1).extract_quarter());
assert_eq!(1, EpochDays::from_ymd(2000, 3, 31).extract_quarter());
assert_eq!(2, EpochDays::from_ymd(2000, 4, 1).extract_quarter());
assert_eq!(3, EpochDays::from_ymd(2000, 7, 1).extract_quarter());
assert_eq!(4, EpochDays::from_ymd(2000, 10, 1).extract_quarter());
assert_eq!(4, EpochDays::from_ymd(2000, 12, 31).extract_quarter());
}
}