use std::ops;
use std::ops::Add;
use chrono::{Datelike, Timelike};
use num_integer::Integer;
#[cfg(feature = "serde1")]
extern crate serde;
#[cfg(feature = "serde1")]
use serde::{Deserialize, Serialize};
#[derive(Default)]
pub struct Builder {
years: i32,
months: i64,
months_f: f64,
days: i64,
hours: i64,
minutes: i64,
seconds: i64,
nanoseconds: i64,
year: Option<i32>,
month: Option<u32>,
day: Option<u32>,
weekday: Option<(chrono::Weekday, i64)>,
hour: Option<u32>,
minute: Option<u32>,
second: Option<u32>,
nanosecond: Option<u32>,
}
impl Builder {
#[inline]
pub fn new(&self) -> RelativeDelta {
let mut ddt = RelativeDelta {
years: self.years,
months: self.months,
months_f: self.months_f,
days: self.days,
hours: self.hours,
minutes: self.minutes,
seconds: self.seconds,
nanoseconds: self.nanoseconds,
year: self.year,
month: self.month,
day: self.day,
weekday: self.weekday,
hour: self.hour,
minute: self.minute,
second: self.second,
nanosecond: self.nanosecond,
};
Self::fix(&mut ddt);
ddt
}
#[inline]
pub fn and_yysmmsdds(&mut self, year: Option<i32>, years: i32, month: Option<u32>, months: i64, day: Option<u32>, days: i64) -> &mut Self {
self.year = year;
self.years = years;
self.month = month;
self.months = months;
self.day = day;
self.days = days;
self
}
#[inline]
pub fn and_hhsmmssss(&mut self, hour: Option<u32>, hours: i64, minute: Option<u32>, minutes: i64, second: Option<u32>, seconds: i64) -> &mut Self {
self.hour = hour;
self.hours = hours;
self.minute = minute;
self.minutes = minutes;
self.second = second;
self.seconds = seconds;
self
}
#[inline]
pub fn with_years(&self, years: i32) -> Self {
Self { years, ..*self }
}
#[inline]
pub fn with_months(&self, months: i64) -> Self {
Self { months, ..*self }
}
#[inline]
pub fn with_days(&self, days: i64) -> Self {
Self { days, ..*self }
}
#[inline]
pub fn with_hours(&self, hours: i64) -> Self {
Self { hours, ..*self }
}
#[inline]
pub fn with_minutes(&self, minutes: i64) -> Self {
Self { minutes, ..*self }
}
#[inline]
pub fn with_nanoseconds(&self, nanoseconds: i64) -> Self {
Self { nanoseconds, ..*self }
}
#[inline]
pub fn and_years(&mut self, years: i32) -> &mut Self {
self.years = years;
self
}
#[inline]
pub fn and_months(&mut self, months: i64) -> &mut Self {
self.months = months;
self
}
#[inline]
pub fn and_months_f(&mut self, months_f: f64) -> &mut Self {
self.months_f = months_f;
self
}
#[inline]
pub fn and_days(&mut self, days: i64) -> &mut Self {
self.days = days;
self
}
#[inline]
pub fn and_hours(&mut self, hours: i64) -> &mut Self {
self.hours = hours;
self
}
#[inline]
pub fn and_minutes(&mut self, minutes: i64) -> &mut Self {
self.minutes = minutes;
self
}
#[inline]
pub fn and_seconds(&mut self, seconds: i64) -> &mut Self {
self.seconds = seconds;
self
}
#[inline]
pub fn and_nanoseconds(&mut self, nanoseconds: i64) -> &mut Self {
self.nanoseconds = nanoseconds;
self
}
#[inline]
pub fn with_year(self, year: Option<i32>) -> Self {
Self { year, ..self }
}
#[inline]
pub fn with_month(self, month: Option<u32>) -> Self {
Self { month, ..self }
}
#[inline]
pub fn with_day(self, day: Option<u32>) -> Self {
Self { day, ..self }
}
#[inline]
pub fn with_hour(self, hour: Option<u32>) -> Self {
Self {
hour,
..self
}
}
#[inline]
pub fn and_year(&mut self, year: Option<i32>) -> &mut Self {
self.year = year;
self
}
#[inline]
pub fn and_month(&mut self, month: Option<u32>) -> &mut Self {
self.month = month;
self
}
#[inline]
pub fn and_day(&mut self, day: Option<u32>) -> &mut Self {
self.day = day;
self
}
#[inline]
pub fn and_hour(&mut self, hour: Option<u32>) -> &mut Self {
self.hour = hour;
self
}
#[inline]
pub fn and_minute(&mut self, minute: Option<u32>) -> &mut Self {
self.minute = minute;
self
}
#[inline]
pub fn and_second(&mut self, second: Option<u32>) -> &mut Self {
self.second = second;
self
}
#[inline]
pub fn and_nanosecond(&mut self, nanosecond: Option<u32>) -> &mut Self {
self.nanosecond = nanosecond;
self
}
#[inline]
pub fn and_weekday(&mut self, weekday_nth: Option<(chrono::Weekday,i64)>) -> &mut Self {
self.weekday = weekday_nth;
self
}
#[inline]
fn fix(ddt: &mut RelativeDelta) {
assert!(
ddt.month.map_or(true, |m| (1..=12).contains(&m)),
"invalid month {}",
ddt.month.unwrap()
);
assert!(
ddt.day.map_or(true, |d| (1..=31).contains(&d)),
"invalid day {}",
ddt.day.unwrap()
);
assert!(
ddt.hour.map_or(true, |h| (0..=23).contains(&h)),
"invalid hour {}",
ddt.hour.unwrap()
);
assert!(
ddt.minute.map_or(true, |m| (0..=59).contains(&m)),
"invalid minute {}",
ddt.minute.unwrap()
);
assert!(
ddt.second.map_or(true, |s| (0..=59).contains(&s)),
"invalid second {}",
ddt.second.unwrap()
);
assert!(
ddt.nanosecond
.map_or(true, |n| (0..=999_999_999).contains(&n)),
"invalid nanosecond {}",
ddt.nanosecond.unwrap()
);
if ddt.nanoseconds.abs() > 999_999_999 {
let s = ddt.nanoseconds.signum();
let (div, rem) = (ddt.nanoseconds * s).div_rem(&1_000_000_000);
ddt.nanoseconds = rem * s;
ddt.seconds += div * s;
}
if ddt.seconds.abs() > 59 {
let s = ddt.seconds.signum();
let (div, rem) = (ddt.seconds * s).div_rem(&60);
ddt.seconds = rem * s;
ddt.minutes += div * s;
}
if ddt.minutes.abs() > 59 {
let s = ddt.minutes.signum();
let (div, rem) = (ddt.minutes * s).div_rem(&60);
ddt.minutes = rem * s;
ddt.hours += div * s;
}
if ddt.hours.abs() > 23 {
let s = ddt.hours.signum();
let (div, rem) = (ddt.hours * s).div_rem(&24);
ddt.hours = rem * s;
ddt.days += div * s;
}
if ddt.months.abs() > 11 {
let s = ddt.months.signum();
let (div, rem) = (ddt.months * s).div_rem(&12);
ddt.months = rem * s;
ddt.years += (div * s) as i32;
}
}
fn normalize(
years: f64,
months: f64,
days: f64,
hours: f64,
minutes: f64,
seconds: f64,
nanoseconds: i64,
) -> Self {
let years_total = years;
let years = years.trunc();
let months_f = (years_total - years) * 12_f64;
let months_total = months_f + months;
let months = months_total.trunc();
let months_remainder = months_total - months;
let days_total = days;
let days = days_total.trunc();
let hours_f = (days_total - days) * 24_f64;
let hours_total = hours_f + hours;
let hours = hours_total.trunc();
let minutes_f = (hours_total - hours) * 60_f64;
let minutes_total = minutes_f + minutes;
let minutes = minutes_total.trunc();
let seconds_f = (minutes_total - minutes) * 60_f64;
let seconds_total = seconds_f + seconds;
let seconds = seconds_total.trunc();
let nanosecs_f = ((seconds_total - seconds) * 1_000_000_000_f64).trunc() as i64;
let nanosecs = nanosecs_f + nanoseconds;
Self {
years: years as i32,
months: months as i64,
months_f: months_remainder,
days: days as i64,
hours: hours as i64,
minutes: minutes as i64,
seconds: seconds as i64,
nanoseconds: nanosecs as i64,
..Self::default()
}
}
}
#[cfg(feature = "serde1")]
fn is_i32_zero(v: &i32) -> bool {
*v == 0
}
#[cfg(feature = "serde1")]
fn is_i64_zero(v: &i64) -> bool {
*v == 0
}
#[cfg(feature = "serde1")]
fn is_f64_zero(v: &f64) -> bool {
v.fract() == 0.0
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct RelativeDelta {
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "is_i32_zero"), serde(default))]
years: i32,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "is_i64_zero"), serde(default))]
months: i64,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "is_f64_zero"), serde(default))]
months_f: f64,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "is_i64_zero"), serde(default))]
days: i64,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "is_i64_zero"), serde(default))]
hours: i64,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "is_i64_zero"), serde(default))]
minutes: i64,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "is_i64_zero"), serde(default))]
seconds: i64,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "is_i64_zero"), serde(default))]
nanoseconds: i64,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"), serde(default))]
year: Option<i32>,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"), serde(default))]
month: Option<u32>,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"), serde(default))]
day: Option<u32>,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"), serde(default))]
hour: Option<u32>,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"), serde(default))]
minute: Option<u32>,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"), serde(default))]
second: Option<u32>,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"), serde(default))]
nanosecond: Option<u32>,
#[cfg_attr(feature = "serde1", serde(skip_serializing_if = "Option::is_none"), serde(default))]
weekday: Option<(chrono::Weekday, i64)>,
}
impl RelativeDelta {
#[inline]
pub fn ysmsdshsmsssns_f(
years: f64,
months: f64,
days: f64,
hours: f64,
minutes: f64,
seconds: f64,
nanoseconds: i64,
) -> Builder {
Builder::normalize(years, months, days, hours, minutes, seconds, nanoseconds)
}
#[inline]
pub fn yysmmsdds(
year: Option<i32>,
years: i32,
month: Option<u32>,
months: i64,
day: Option<u32>,
days: i64,
) -> Builder {
Builder { year, years, month, months, day, days, ..Default::default() }
}
#[inline]
pub fn hhsmmssss(
hour: Option<u32>,
hours: i64,
minute: Option<u32>,
minutes: i64,
second: Option<u32>,
seconds: i64,
) -> Builder {
Builder { hour, hours, minute, minutes, second, seconds, ..Default::default() }
}
#[inline]
pub fn with_years(years: i32) -> Builder {
Builder {
years,
..Default::default()
}
}
#[inline]
pub fn with_months(months: i64) -> Builder {
Builder {
months,
..Default::default()
}
}
#[inline]
pub fn with_days(days: i64) -> Builder {
Builder {
days,
..Default::default()
}
}
#[inline]
pub fn with_hours(hours: i64) -> Builder {
Builder {
hours,
..Default::default()
}
}
#[inline]
pub fn with_minutes(minutes: i64) -> Builder {
Builder {
minutes,
..Default::default()
}
}
#[inline]
pub fn with_seconds(seconds: i64) -> Builder {
Builder {
seconds,
..Default::default()
}
}
#[inline]
pub fn with_nanoseconds(nanoseconds: i64) -> Builder {
Builder {
nanoseconds,
..Default::default()
}
}
#[inline]
pub fn with_year(year: i32) -> Builder {
Builder {
year: Some(year),
..Default::default()
}
}
#[inline]
pub fn with_month(month: u32) -> Builder {
Builder {
month: Some(month),
..Default::default()
}
}
#[inline]
pub fn with_day(day: u32) -> Builder {
Builder {
day: Some(day),
..Default::default()
}
}
#[inline]
pub fn with_hour(hour: u32) -> Builder {
Builder {
hour: Some(hour),
..Default::default()
}
}
#[inline]
pub fn with_minute(minute: u32) -> Builder {
Builder {
minute: Some(minute),
..Default::default()
}
}
#[inline]
pub fn with_second(second: u32) -> Builder {
Builder {
second: Some(second),
..Default::default()
}
}
#[inline]
pub fn with_nanosecond(nanosecond: u32) -> Builder {
Builder {
nanosecond: Some(nanosecond),
..Default::default()
}
}
#[inline]
pub fn with_weekday(weekday: chrono::Weekday, nth: i64) -> Builder {
Builder {
weekday: Some((weekday, nth)),
..Default::default()
}
}
#[inline]
pub fn years(&self) -> i32 {
self.years
}
#[inline]
pub fn year(&self) -> Option<i32> {
self.year
}
#[inline]
pub fn months(&self) -> i64 {
self.months
}
#[inline]
pub fn month(&self) -> Option<u32> {
self.month
}
#[inline]
pub fn days(&self) -> i64 {
self.days
}
#[inline]
pub fn day(&self) -> Option<u32> {
self.day
}
#[inline]
pub fn hours(&self) -> i64 {
self.hours
}
#[inline]
pub fn hour(&self) -> Option<u32> {
self.hour
}
#[inline]
pub fn minutes(&self) -> i64 {
self.minutes
}
#[inline]
pub fn minute(&self) -> Option<u32> {
self.minute
}
#[inline]
pub fn seconds(&self) -> i64 {
self.seconds
}
#[inline]
pub fn second(&self) -> Option<u32> {
self.second
}
#[inline]
pub fn nanoseconds(&self) -> i64 {
self.nanoseconds
}
#[inline]
pub fn nanosecond(&self) -> Option<u32> {
self.nanosecond
}
#[inline]
pub fn weekday(&self) -> Option<(chrono::Weekday, i64)> {
self.weekday
}
#[inline]
pub fn total_months(&self) -> i64 {
(self.years as i64) * 12 + self.months
}
}
pub fn num_days_in_month(year: i32, month: u32) -> u32 {
let nd = if month == 12 {
chrono::NaiveDate::from_ymd(year + 1, 1, 1)
} else {
chrono::NaiveDate::from_ymd(year, month + 1, 1)
};
let r = nd
.signed_duration_since(chrono::NaiveDate::from_ymd(year, month, 1))
.num_days();
assert!((1..=31).contains(&r));
r as u32
}
impl_op_ex!(-|rhs: &RelativeDelta| -> RelativeDelta {
RelativeDelta {
years: -rhs.years,
months: -rhs.months,
days: -rhs.days,
hours: -rhs.hours,
minutes: -rhs.minutes,
seconds: -rhs.seconds,
nanoseconds: -rhs.nanoseconds,
..*rhs
}
});
impl_op_ex!(+ |lhs: &RelativeDelta, rhs: &RelativeDelta| -> RelativeDelta {
Builder {years: lhs.years + rhs.years, months: lhs.months + rhs.months, days: lhs.days + rhs.days, hours: lhs.hours + rhs.hours, minutes: lhs.minutes + rhs.minutes, seconds: lhs.seconds + rhs.seconds, nanoseconds: lhs.nanoseconds + rhs.nanoseconds, ..Default::default()}.new()
});
impl_op_ex!(- |lhs: &RelativeDelta, rhs: &RelativeDelta| -> RelativeDelta {
-rhs + lhs
});
impl<Tz: chrono::TimeZone> Add<&chrono::DateTime<Tz>> for &RelativeDelta {
type Output = chrono::DateTime<Tz>;
fn add(self, rhs: &chrono::DateTime<Tz>) -> Self::Output {
let mut year = self.year.unwrap_or(rhs.year()) + self.years;
let month = self.month.unwrap_or(rhs.month()) as i64 + self.months;
let (mut extra_years, mut relative_month) = month.div_rem(&12);
if relative_month <= 0 {
extra_years -= 1;
relative_month = 12 + relative_month;
}
assert!(
(1..=12).contains(&relative_month),
"relative month was {}",
relative_month
);
year += extra_years as i32;
let real_month = relative_month as u32;
let day = num_days_in_month(year, real_month).min(self.day.unwrap_or(rhs.day()));
let hour = self.hour.unwrap_or(rhs.hour());
let minute = self.minute.unwrap_or(rhs.minute());
let second = self.second.unwrap_or(rhs.second());
let nanosecond = self.nanosecond.unwrap_or(rhs.nanosecond());
let datetime = rhs
.timezone()
.ymd(year, real_month, day)
.and_hms_nano(hour, minute, second, nanosecond);
let ret = datetime
+ chrono::Duration::days(self.days)
+ chrono::Duration::hours(self.hours)
+ chrono::Duration::minutes(self.minutes)
+ chrono::Duration::seconds(self.seconds)
+ chrono::Duration::nanoseconds(self.nanoseconds);
if let Some((weekday, nth)) = self.weekday {
let mut jumpdays = (nth.abs() - 1) * 7;
if nth > 0 {
jumpdays += (7 - ret.weekday().num_days_from_monday()
+ weekday.num_days_from_monday()) as i64;
} else {
jumpdays += ((ret.weekday().num_days_from_monday()
- weekday.num_days_from_monday())
% 7) as i64;
jumpdays *= -1;
}
ret + chrono::Duration::days(jumpdays)
}
else {
ret
}
}
}
impl<Tz: chrono::TimeZone> Add<&chrono::DateTime<Tz>> for RelativeDelta {
type Output = chrono::DateTime<Tz>;
fn add(self, rhs: &chrono::DateTime<Tz>) -> Self::Output {
&self + rhs
}
}
impl<Tz: chrono::TimeZone> Add<chrono::DateTime<Tz>> for &RelativeDelta {
type Output = chrono::DateTime<Tz>;
fn add(self, rhs: chrono::DateTime<Tz>) -> Self::Output {
self + &rhs
}
}
impl<Tz: chrono::TimeZone> Add<chrono::DateTime<Tz>> for RelativeDelta {
type Output = chrono::DateTime<Tz>;
fn add(self, rhs: chrono::DateTime<Tz>) -> Self::Output {
&self + &rhs
}
}
impl<Tz: chrono::TimeZone> Add<&RelativeDelta> for &chrono::DateTime<Tz> {
type Output = chrono::DateTime<Tz>;
fn add(self, rhs: &RelativeDelta) -> Self::Output {
rhs + self
}
}
impl<Tz: chrono::TimeZone> Add<RelativeDelta> for &chrono::DateTime<Tz> {
type Output = chrono::DateTime<Tz>;
fn add(self, rhs: RelativeDelta) -> Self::Output {
rhs + self
}
}
impl<Tz: chrono::TimeZone> Add<&RelativeDelta> for chrono::DateTime<Tz> {
type Output = chrono::DateTime<Tz>;
fn add(self, rhs: &RelativeDelta) -> Self::Output {
rhs + self
}
}
impl<Tz: chrono::TimeZone> Add<RelativeDelta> for chrono::DateTime<Tz> {
type Output = chrono::DateTime<Tz>;
fn add(self, rhs: RelativeDelta) -> Self::Output {
rhs + self
}
}
impl<Tz: chrono::TimeZone> ops::Sub<&RelativeDelta> for &chrono::DateTime<Tz> {
type Output = chrono::DateTime<Tz>;
fn sub(self, rhs: &RelativeDelta) -> Self::Output {
self + (-rhs)
}
}
impl<Tz: chrono::TimeZone> ops::Sub<RelativeDelta> for &chrono::DateTime<Tz> {
type Output = chrono::DateTime<Tz>;
fn sub(self, rhs: RelativeDelta) -> Self::Output {
self - &rhs
}
}
impl<Tz: chrono::TimeZone> ops::Sub<&RelativeDelta> for chrono::DateTime<Tz> {
type Output = chrono::DateTime<Tz>;
fn sub(self, rhs: &RelativeDelta) -> Self::Output {
&self - rhs
}
}
impl<Tz: chrono::TimeZone> ops::Sub<RelativeDelta> for chrono::DateTime<Tz> {
type Output = chrono::DateTime<Tz>;
fn sub(self, rhs: RelativeDelta) -> Self::Output {
&self - &rhs
}
}
fn mul(lhs: &RelativeDelta, rhs: f64) -> RelativeDelta {
let years = lhs.years as f64 * rhs;
let months = lhs.months as f64 * rhs;
let days = lhs.days as f64 * rhs;
let hours = lhs.hours as f64 * rhs;
let minutes = lhs.minutes as f64 * rhs;
let seconds = lhs.seconds as f64 * rhs;
let nanoseconds = lhs.nanoseconds as f64 * rhs;
let mut rddt_mul = RelativeDelta::ysmsdshsmsssns_f(
years,
months,
days,
hours,
minutes,
seconds,
nanoseconds as i64,
);
rddt_mul.year = lhs.year;
rddt_mul.month = lhs.month;
rddt_mul.day = lhs.day;
rddt_mul.hour = lhs.hour;
rddt_mul.minute = lhs.minute;
rddt_mul.second = lhs.second;
rddt_mul.nanosecond = lhs.nanosecond;
rddt_mul.new()
}
impl_op_ex_commutative!(*|lhs: &RelativeDelta, rhs: f64| -> RelativeDelta { mul(lhs, rhs) });
impl_op_ex!(/ |lhs: &RelativeDelta, rhs: f64| -> RelativeDelta {
let reciprocal = 1_f64 / rhs;
lhs * reciprocal
});
impl_op_ex!(/ |lhs: &RelativeDelta, rhs: f32| -> RelativeDelta {
lhs / (rhs as f64)
});
impl_op_ex!(/ |lhs: &RelativeDelta, rhs: usize| -> RelativeDelta {
lhs / (rhs as f64)
});
impl From<RelativeDelta> for Option<chrono::NaiveDateTime> {
fn from(rddt: RelativeDelta) -> Self {
match (rddt.year, rddt.month, rddt.day) {
(Some(year), Some(month), Some(day)) => {
Some(chrono::NaiveDate::from_ymd(year, month, day).and_hms_nano(
rddt.hour.unwrap_or(0),
rddt.minute.unwrap_or(0),
rddt.second.unwrap_or(0),
rddt.nanosecond.unwrap_or(0),
))
}
_ => None,
}
}
}