use std::ops::AddAssign;
use chrono::{DateTime, Datelike, NaiveDate, NaiveTime, TimeDelta, TimeZone, Utc};
pub struct Calendar {
year: i32,
month: u32,
day: u32,
time: NaiveTime,
}
impl Calendar {
pub fn from_time<Tz: TimeZone>(time: &DateTime<Utc>, zone: &Tz) -> Calendar {
let time = time.with_timezone(zone);
let date = time.date_naive();
let time = time.time();
return Self { year: date.year(), month: date.month(), day: date.day(), time };
}
pub fn num_months_to(&self, calendar: &Calendar) -> Option<i64> {
let years = calendar.year as i64 - self.year as i64;
let mut months = calendar.month as i64 - self.month as i64 + years * 12;
if (calendar.day, calendar.time) < (self.day, self.time) {
months -= 1;
}
if months > 0 {
return Some(months);
} else {
return None;
}
}
pub fn num_years_to(&self, calendar: &Calendar) -> Option<i64> {
let mut years = calendar.year as i64 - self.year as i64;
if (calendar.month, calendar.day, calendar.time) < (self.month, self.day, self.time) {
years -= 1;
}
if years > 0 {
return Some(years);
} else {
return None;
}
}
pub fn subtract_month<Tz: TimeZone>(&mut self, count: i64, zone: &Tz) -> DateTime<Utc> {
for _ in 0..count {
self.month -= 1;
if self.month == 0 {
self.month = 12;
self.year -= 1;
}
}
return self.create_time(zone);
}
pub fn subtract_year<Tz: TimeZone>(&mut self, count: i64, zone: &Tz) -> DateTime<Utc> {
for _ in 0..count {
self.year -= 1;
}
return self.create_time(zone);
}
fn create_time<Tz: TimeZone>(&mut self, zone: &Tz) -> DateTime<Utc> {
loop {
if let Some(date) = NaiveDate::from_ymd_opt(self.year, self.month, self.day) {
let time = date.and_time(self.time);
let time = time.and_local_timezone(zone.clone());
if let Some(time) = time.latest() {
return time.to_utc();
} else {
self.increment_hour();
}
} else {
self.increment_day();
}
}
}
fn increment_hour(&mut self) {
self.time.add_assign(TimeDelta::hours(1));
}
fn increment_day(&mut self) {
self.day += 1;
if self.day > 31 {
self.day = 1;
self.month += 1;
if self.month > 12 {
self.month = 1;
self.year += 1;
}
}
}
}
#[cfg(test)]
mod tests {
use chrono::{DateTime, Datelike, TimeZone, Timelike, Utc};
use chrono_tz::America;
use pretty_assertions::assert_eq;
use crate::calendar::Calendar;
#[test]
fn test_subtracts_month_with_carried_year() {
let func = |calendar: &mut Calendar| { calendar.subtract_month(1, &America::New_York) };
assert_calendar(func, 2024, 1, 29, 12, 0, 0, 2023, 12, 29, 12, 0, 0);
assert_calendar(func, 2024, 1, 30, 12, 0, 0, 2023, 12, 30, 12, 0, 0);
assert_calendar(func, 2024, 2, 1, 12, 0, 0, 2024, 1, 1, 12, 0, 0);
assert_calendar(func, 2024, 2, 2, 12, 0, 0, 2024, 1, 2, 12, 0, 0);
}
#[test]
fn test_subtracts_month_with_shorter_month() {
let func = |calendar: &mut Calendar| { calendar.subtract_month(10, &America::New_York) };
assert_calendar(func, 2023, 12, 26, 12, 0, 0, 2023, 2, 26, 12, 0, 0);
assert_calendar(func, 2023, 12, 27, 12, 0, 0, 2023, 2, 27, 12, 0, 0);
assert_calendar(func, 2023, 12, 28, 12, 0, 0, 2023, 2, 28, 12, 0, 0);
assert_calendar(func, 2023, 12, 29, 12, 0, 0, 2023, 3, 1, 12, 0, 0);
assert_calendar(func, 2023, 12, 30, 12, 0, 0, 2023, 3, 1, 12, 0, 0);
assert_calendar(func, 2023, 12, 31, 12, 0, 0, 2023, 3, 1, 12, 0, 0);
assert_calendar(func, 2024, 1, 1, 12, 0, 0, 2023, 3, 1, 12, 0, 0);
assert_calendar(func, 2024, 1, 2, 12, 0, 0, 2023, 3, 2, 12, 0, 0);
assert_calendar(func, 2024, 1, 3, 12, 0, 0, 2023, 3, 3, 12, 0, 0);
}
#[test]
fn test_subtracts_month_with_clocks_forward() {
let func = |calendar: &mut Calendar| { calendar.subtract_month(1, &America::New_York) };
assert_calendar(func, 2024, 4, 10, 4, 30, 0, 2024, 3, 10, 5, 30, 0); assert_calendar(func, 2024, 4, 10, 5, 30, 0, 2024, 3, 10, 6, 30, 0); assert_calendar(func, 2024, 4, 10, 6, 30, 0, 2024, 3, 10, 7, 30, 0); assert_calendar(func, 2024, 4, 10, 7, 30, 0, 2024, 3, 10, 7, 30, 0); assert_calendar(func, 2024, 4, 10, 8, 30, 0, 2024, 3, 10, 8, 30, 0); }
#[test]
fn test_subtracts_month_with_clocks_backward() {
let func = |calendar: &mut Calendar| { calendar.subtract_month(1, &America::New_York) };
assert_calendar(func, 2024, 12, 3, 5, 30, 0, 2024, 11, 3, 4, 30, 0); assert_calendar(func, 2024, 12, 3, 6, 30, 0, 2024, 11, 3, 6, 30, 0); assert_calendar(func, 2024, 12, 3, 7, 30, 0, 2024, 11, 3, 7, 30, 0); assert_calendar(func, 2024, 12, 3, 8, 30, 0, 2024, 11, 3, 8, 30, 0); assert_calendar(func, 2024, 12, 3, 9, 30, 0, 2024, 11, 3, 9, 30, 0); }
fn assert_calendar<F>(
mut func: F,
initial_year: i32,
initial_month: u32,
initial_day: u32,
initial_hour: u32,
initial_min: u32,
initial_sec: u32,
expect_year: i32,
expect_month: u32,
expect_day: u32,
expect_hour: u32,
expect_min: u32,
expect_sec: u32,
) where F: FnMut(&mut Calendar) -> DateTime<Utc> {
let time = Utc.with_ymd_and_hms(
initial_year,
initial_month,
initial_day,
initial_hour,
initial_min,
initial_sec,
).unwrap();
let mut calendar = Calendar::from_time(&time, &America::New_York);
let time = func(&mut calendar);
assert_eq!(expect_year, time.year(), "year");
assert_eq!(expect_month, time.month(), "month");
assert_eq!(expect_day, time.day(), "day");
assert_eq!(expect_hour, time.hour(), "hour");
assert_eq!(expect_min, time.minute(), "minute");
assert_eq!(expect_sec, time.second(), "second");
}
}