use core::time::Duration as UnsignedDuration;
use crate::{
civil::{DateTime, Era, ISOWeekDate, Time, Weekday},
duration::{Duration, SDuration},
error::{err, Error, ErrorContext},
fmt::{
self,
temporal::{DEFAULT_DATETIME_PARSER, DEFAULT_DATETIME_PRINTER},
},
tz::TimeZone,
util::{
common::{
days_in_month, days_in_month_unranged, is_leap_year,
saturate_day_in_month,
},
rangeint::{ri16, ri8, RFrom, RInto, TryRFrom},
t::{self, Constant, Day, Month, Sign, UnixEpochDays, Year, C},
},
RoundMode, SignedDuration, Span, SpanRound, Unit, Zoned,
};
#[derive(Clone, Copy, Hash)]
pub struct Date {
year: Year,
month: Month,
day: Day,
}
impl Date {
pub const MIN: Date = Date::constant(-9999, 1, 1);
pub const MAX: Date = Date::constant(9999, 12, 31);
pub const ZERO: Date = Date::constant(0, 1, 1);
#[inline]
pub fn new(year: i16, month: i8, day: i8) -> Result<Date, Error> {
let year = Year::try_new("year", year)?;
let month = Month::try_new("month", month)?;
let day = Day::try_new("day", day)?;
Date::new_ranged(year, month, day)
}
#[inline]
pub const fn constant(year: i16, month: i8, day: i8) -> Date {
if !Year::contains(year) {
panic!("invalid year");
}
if !Month::contains(month) {
panic!("invalid month");
}
if day > days_in_month_unranged(year, month) {
panic!("invalid day");
}
let year = Year::new_unchecked(year);
let month = Month::new_unchecked(month);
let day = Day::new_unchecked(day);
Date { year, month, day }
}
#[inline]
pub fn from_iso_week_date(weekdate: ISOWeekDate) -> Date {
let mut days = iso_week_start_from_year(weekdate.year_ranged());
let year = t::NoUnits16::rfrom(weekdate.year_ranged());
let week = t::NoUnits16::rfrom(weekdate.week_ranged());
let weekday = t::NoUnits16::rfrom(
weekdate.weekday().to_monday_zero_offset_ranged(),
);
let [week, weekday] = t::NoUnits16::vary_many(
[year, week, weekday],
|[year, week, weekday]| {
if year == 9999 {
if week >= 52 {
[week.min(C(52)), weekday.min(C(4))]
} else {
[week, weekday]
}
} else {
[week, weekday]
}
},
);
days += (UnixEpochDays::rfrom(week) - C(1)) * C(7);
days += weekday;
Date::from_unix_epoch_days(days)
}
#[inline]
pub fn with(self) -> DateWith {
DateWith::new(self)
}
#[inline]
pub fn year(self) -> i16 {
self.year_ranged().get()
}
#[inline]
pub fn era_year(self) -> (i16, Era) {
let year = self.year_ranged();
if year >= 1 {
(year.get(), Era::CE)
} else {
let year = -t::YearBCE::rfrom(year.min(C(0)));
let era_year = year + C(1);
(era_year.get(), Era::BCE)
}
}
#[inline]
pub fn month(self) -> i8 {
self.month_ranged().get()
}
#[inline]
pub fn day(self) -> i8 {
self.day_ranged().get()
}
#[inline]
pub fn weekday(self) -> Weekday {
weekday_from_unix_epoch_days(self.to_unix_epoch_days())
}
#[inline]
pub fn day_of_year(self) -> i16 {
type DayOfYear = ri16<1, 366>;
let days = C(1) + self.since_days_ranged(self.first_of_year());
DayOfYear::rfrom(days).get()
}
#[inline]
pub fn day_of_year_no_leap(self) -> Option<i16> {
let mut days = self.day_of_year();
if self.in_leap_year() {
if days == 60 {
return None;
} else if days > 60 {
days -= 1;
}
}
Some(days)
}
#[inline]
pub fn first_of_month(self) -> Date {
Date::new_ranged(self.year_ranged(), self.month_ranged(), C(1))
.expect("first day of month is always valid")
}
#[inline]
pub fn last_of_month(self) -> Date {
let max_day = self.days_in_month_ranged();
Date::new_ranged(self.year_ranged(), self.month_ranged(), max_day)
.expect("last day of month is always valid")
}
#[inline]
pub fn days_in_month(self) -> i8 {
self.days_in_month_ranged().get()
}
#[inline]
pub fn first_of_year(self) -> Date {
Date::new_ranged(self.year_ranged(), C(1), C(1))
.expect("first day of year is always valid")
}
#[inline]
pub fn last_of_year(self) -> Date {
Date::new_ranged(self.year_ranged(), C(12), C(31))
.expect("last day of year is always valid")
}
#[inline]
pub fn days_in_year(self) -> i16 {
if self.in_leap_year() {
366
} else {
365
}
}
#[inline]
pub fn in_leap_year(self) -> bool {
is_leap_year(self.year_ranged())
}
#[inline]
pub fn tomorrow(self) -> Result<Date, Error> {
let day = self.to_unix_epoch_days();
let next = day.try_checked_add("days", C(1))?;
Ok(Date::from_unix_epoch_days(next))
}
#[inline]
pub fn yesterday(self) -> Result<Date, Error> {
let day = self.to_unix_epoch_days();
let next = day.try_checked_sub("days", C(1))?;
Ok(Date::from_unix_epoch_days(next))
}
#[inline]
pub fn nth_weekday_of_month(
self,
nth: i8,
weekday: Weekday,
) -> Result<Date, Error> {
type Nth = ri8<-5, 5>;
let nth = Nth::try_new("nth", nth)?;
if nth == 0 {
Err(Error::specific("nth weekday", 0))
} else if nth > 0 {
let nth = nth.max(C(1));
let first_weekday = self.first_of_month().weekday();
let diff = weekday.since_ranged(first_weekday);
let day = Day::rfrom(diff) + C(1) + (nth - C(1)) * C(7);
Date::new_ranged(self.year_ranged(), self.month_ranged(), day)
} else {
let nth = nth.min(C(-1));
let last = self.last_of_month();
let last_weekday = last.weekday();
let diff = last_weekday.since_ranged(weekday);
let day = last.day_ranged()
- Day::rfrom(diff)
- (nth.abs() - C(1)) * C(7);
if day < 1 {
return Err(day.to_error_with_bounds(
"day",
1,
self.days_in_month(),
));
}
Date::new_ranged(self.year_ranged(), self.month_ranged(), day)
}
}
#[inline]
pub fn nth_weekday(
self,
nth: i32,
weekday: Weekday,
) -> Result<Date, Error> {
let nth = t::SpanWeeks::try_new("nth weekday", nth)?;
if nth == 0 {
Err(Error::specific("nth weekday", 0))
} else if nth > 0 {
let nth = nth.max(C(1));
let weekday_diff = weekday.since_ranged(self.weekday().next());
let diff = (nth - C(1)) * C(7) + weekday_diff;
let start = self.tomorrow()?.to_unix_epoch_days();
let end = start.try_checked_add("days", diff)?;
Ok(Date::from_unix_epoch_days(end))
} else {
let nth: t::SpanWeeks = nth.min(C(-1)).abs();
let weekday_diff = self.weekday().previous().since_ranged(weekday);
let diff = (nth - C(1)) * C(7) + weekday_diff;
let start = self.yesterday()?.to_unix_epoch_days();
let end = start.try_checked_sub("days", diff)?;
Ok(Date::from_unix_epoch_days(end))
}
}
#[inline]
pub fn to_iso_week_date(self) -> ISOWeekDate {
let days = t::NoUnits32::rfrom(self.to_unix_epoch_days());
let year = t::NoUnits32::rfrom(self.year_ranged());
let week_start = t::NoUnits32::vary([days, year], |[days, year]| {
let mut week_start =
t::NoUnits32::rfrom(iso_week_start_from_year(year));
if days < week_start {
week_start =
t::NoUnits32::rfrom(iso_week_start_from_year(year - C(1)));
} else {
let next_year_week_start =
t::NoUnits32::rfrom(iso_week_start_from_year(year + C(1)));
if days >= next_year_week_start {
week_start = next_year_week_start;
}
}
week_start
});
let weekday = weekday_from_unix_epoch_days(days);
let week = ((days - week_start) / C(7)) + C(1);
let year = Date::from_unix_epoch_days(
week_start
+ t::NoUnits32::rfrom(
Weekday::Thursday.since_ranged(Weekday::Monday),
),
)
.year_ranged();
ISOWeekDate::new_ranged(year, week, weekday)
.expect("all Dates infallibly convert to ISOWeekDates")
}
#[inline]
pub fn intz(self, time_zone_name: &str) -> Result<Zoned, Error> {
let tz = crate::tz::db().get(time_zone_name)?;
self.to_zoned(tz)
}
#[inline]
pub fn to_zoned(self, tz: TimeZone) -> Result<Zoned, Error> {
DateTime::from(self).to_zoned(tz)
}
#[inline]
pub const fn to_datetime(self, time: Time) -> DateTime {
DateTime::from_parts(self, time)
}
#[inline]
pub const fn at(
self,
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
) -> DateTime {
DateTime::from_parts(
self,
Time::constant(hour, minute, second, subsec_nanosecond),
)
}
#[inline]
pub fn checked_add<A: Into<DateArithmetic>>(
self,
duration: A,
) -> Result<Date, Error> {
let duration: DateArithmetic = duration.into();
duration.checked_add(self)
}
#[inline]
fn checked_add_span(self, span: Span) -> Result<Date, Error> {
let (month, years) =
month_add_overflowing(self.month, span.get_months_ranged());
let year = self
.year
.try_checked_add("years", years)?
.try_checked_add("years", span.get_years_ranged())?;
let date = Date::constrain_ranged(year, month, self.day);
let time_days = span
.only_lower(Unit::Day)
.to_invariant_nanoseconds()
.div_ceil(t::NANOS_PER_CIVIL_DAY);
let epoch_days = date.to_unix_epoch_days();
let days = epoch_days
.try_checked_add(
"days",
C(7) * UnixEpochDays::rfrom(span.get_weeks_ranged()),
)?
.try_checked_add(
"days",
UnixEpochDays::rfrom(span.get_days_ranged()),
)?
.try_checked_add("time", time_days)?;
Ok(Date::from_unix_epoch_days(days))
}
#[inline]
fn checked_add_duration(
self,
duration: SignedDuration,
) -> Result<Date, Error> {
let days = duration.as_hours() / 24;
let days =
UnixEpochDays::try_new("days", days).with_context(|| {
err!(
"{days} computed from duration {duration:?} overflows \
Jiff's datetime limits",
)
})?;
let days = self.to_unix_epoch_days().try_checked_add("days", days)?;
Ok(Date::from_unix_epoch_days(days))
}
#[inline]
pub fn checked_sub<A: Into<DateArithmetic>>(
self,
duration: A,
) -> Result<Date, Error> {
let duration: DateArithmetic = duration.into();
duration.checked_neg().and_then(|da| da.checked_add(self))
}
#[inline]
pub fn saturating_add<A: Into<DateArithmetic>>(self, duration: A) -> Date {
let duration: DateArithmetic = duration.into();
self.checked_add(duration).unwrap_or_else(|_| {
if duration.is_negative() {
Date::MIN
} else {
Date::MAX
}
})
}
#[inline]
pub fn saturating_sub<A: Into<DateArithmetic>>(self, duration: A) -> Date {
let duration: DateArithmetic = duration.into();
let Ok(duration) = duration.checked_neg() else { return Date::MIN };
self.saturating_add(duration)
}
#[inline]
pub fn until<A: Into<DateDifference>>(
self,
other: A,
) -> Result<Span, Error> {
let args: DateDifference = other.into();
let span = args.until_with_largest_unit(self)?;
if args.rounding_may_change_span() {
span.round(args.round.relative(self))
} else {
Ok(span)
}
}
#[inline]
pub fn since<A: Into<DateDifference>>(
self,
other: A,
) -> Result<Span, Error> {
let args: DateDifference = other.into();
let span = -args.until_with_largest_unit(self)?;
if args.rounding_may_change_span() {
span.round(args.round.relative(self))
} else {
Ok(span)
}
}
#[inline]
pub fn duration_until(self, other: Date) -> SignedDuration {
SignedDuration::date_until(self, other)
}
#[inline]
pub fn duration_since(self, other: Date) -> SignedDuration {
SignedDuration::date_until(other, self)
}
#[inline]
pub fn series(self, period: Span) -> DateSeries {
DateSeries { start: self, period, step: 0 }
}
}
impl Date {
#[inline]
pub fn strptime(
format: impl AsRef<[u8]>,
input: impl AsRef<[u8]>,
) -> Result<Date, Error> {
fmt::strtime::parse(format, input).and_then(|tm| tm.to_date())
}
#[inline]
pub fn strftime<'f, F: 'f + ?Sized + AsRef<[u8]>>(
&self,
format: &'f F,
) -> fmt::strtime::Display<'f> {
fmt::strtime::Display { fmt: format.as_ref(), tm: (*self).into() }
}
}
const DAYS_IN_ERA: Constant = Constant(146_097);
const DAYS_FROM_0000_01_01_TO_1970_01_01: Constant = Constant(719_468);
impl Date {
#[inline]
pub(crate) fn new_ranged(
year: impl RInto<Year>,
month: impl RInto<Month>,
day: impl RInto<Day>,
) -> Result<Date, Error> {
let (year, month, day) = (year.rinto(), month.rinto(), day.rinto());
let max_day = days_in_month(year, month);
if day > max_day {
return Err(day.to_error_with_bounds("day", 1, max_day));
}
Ok(Date { year, month, day })
}
#[inline]
fn constrain_ranged(
year: impl RInto<Year>,
month: impl RInto<Month>,
day: impl RInto<Day>,
) -> Date {
let (year, month, mut day) =
(year.rinto(), month.rinto(), day.rinto());
day = saturate_day_in_month(year, month, day);
Date { year, month, day }
}
#[inline]
pub(crate) fn year_ranged(self) -> Year {
self.year
}
#[inline]
pub(crate) fn month_ranged(self) -> Month {
self.month
}
#[inline]
pub(crate) fn day_ranged(self) -> Day {
self.day
}
#[inline]
pub(crate) fn days_in_month_ranged(self) -> Day {
days_in_month(self.year_ranged(), self.month_ranged())
}
#[inline]
pub(crate) fn since_days_ranged(self, other: Date) -> t::SpanDays {
-self.until_days_ranged(other)
}
#[inline]
pub(crate) fn until_days_ranged(self, other: Date) -> t::SpanDays {
if self == other {
return C(0).rinto();
}
let start = self.to_unix_epoch_days();
let end = other.to_unix_epoch_days();
(end - start).rinto()
}
#[inline]
pub(crate) fn to_unix_epoch_days(self) -> UnixEpochDays {
let year = UnixEpochDays::rfrom(self.year);
let month = UnixEpochDays::rfrom(self.month);
let day = UnixEpochDays::rfrom(self.day);
let year = UnixEpochDays::vary([month, year], |[month, year]| {
if month <= 2 {
year - C(1)
} else {
year
}
});
let month = UnixEpochDays::vary([month], |[month]| {
if month > 2 {
month - C(3)
} else {
month + C(9)
}
});
let era = year / C(400);
let year_of_era = year % C(400);
let day_of_year = (C(153) * month + C(2)) / C(5) + day - C(1);
let day_of_era = year_of_era * C(365) + year_of_era / C(4)
- year_of_era / C(100)
+ day_of_year;
let epoch_days = era * DAYS_IN_ERA + day_of_era
- DAYS_FROM_0000_01_01_TO_1970_01_01;
epoch_days
}
#[inline]
pub(crate) fn from_unix_epoch_days(
days: impl RInto<UnixEpochDays>,
) -> Date {
let days = days.rinto();
let days = days + DAYS_FROM_0000_01_01_TO_1970_01_01;
let era = days / DAYS_IN_ERA;
let day_of_era = days % DAYS_IN_ERA;
let year_of_era = (day_of_era - day_of_era / C(1_460)
+ day_of_era / C(36_524)
- day_of_era / (DAYS_IN_ERA - C(1)))
/ C(365);
let year = year_of_era + era * C(400);
let day_of_year = day_of_era
- (C(365) * year_of_era + year_of_era / C(4)
- year_of_era / C(100));
let month = (day_of_year * C(5) + C(2)) / C(153);
let day = day_of_year - (C(153) * month + C(2)) / C(5) + C(1);
let month = UnixEpochDays::vary([month], |[month]| {
if month < 10 {
month + C(3)
} else {
month - C(9)
}
});
let year = UnixEpochDays::vary([month, year], |[month, year]| {
if month <= 2 {
year + C(1)
} else {
year
}
});
Date { year: year.rinto(), month: month.rinto(), day: day.rinto() }
}
}
impl Eq for Date {}
impl PartialEq for Date {
#[inline]
fn eq(&self, other: &Date) -> bool {
self.day.get() == other.day.get()
&& self.month.get() == other.month.get()
&& self.year.get() == other.year.get()
}
}
impl Ord for Date {
#[inline]
fn cmp(&self, other: &Date) -> core::cmp::Ordering {
(self.year.get(), self.month.get(), self.day.get()).cmp(&(
other.year.get(),
other.month.get(),
other.day.get(),
))
}
}
impl PartialOrd for Date {
#[inline]
fn partial_cmp(&self, other: &Date) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Default for Date {
fn default() -> Date {
Date::ZERO
}
}
impl core::fmt::Debug for Date {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Display::fmt(self, f)
}
}
impl core::fmt::Display for Date {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use crate::fmt::StdFmtWrite;
DEFAULT_DATETIME_PRINTER
.print_date(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
}
}
impl core::str::FromStr for Date {
type Err = Error;
fn from_str(string: &str) -> Result<Date, Error> {
DEFAULT_DATETIME_PARSER.parse_date(string)
}
}
impl From<ISOWeekDate> for Date {
#[inline]
fn from(weekdate: ISOWeekDate) -> Date {
Date::from_iso_week_date(weekdate)
}
}
impl From<DateTime> for Date {
#[inline]
fn from(dt: DateTime) -> Date {
dt.date()
}
}
impl From<Zoned> for Date {
#[inline]
fn from(zdt: Zoned) -> Date {
zdt.datetime().date()
}
}
impl<'a> From<&'a Zoned> for Date {
#[inline]
fn from(zdt: &'a Zoned) -> Date {
zdt.datetime().date()
}
}
impl core::ops::Add<Span> for Date {
type Output = Date;
#[inline]
fn add(self, rhs: Span) -> Date {
self.checked_add(rhs).expect("adding span to date overflowed")
}
}
impl core::ops::AddAssign<Span> for Date {
#[inline]
fn add_assign(&mut self, rhs: Span) {
*self = *self + rhs;
}
}
impl core::ops::Sub<Span> for Date {
type Output = Date;
#[inline]
fn sub(self, rhs: Span) -> Date {
self.checked_sub(rhs).expect("subing span to date overflowed")
}
}
impl core::ops::SubAssign<Span> for Date {
#[inline]
fn sub_assign(&mut self, rhs: Span) {
*self = *self - rhs;
}
}
impl core::ops::Sub for Date {
type Output = Span;
#[inline]
fn sub(self, rhs: Date) -> Span {
self.since(rhs).expect("since never fails when given Date")
}
}
impl core::ops::Add<SignedDuration> for Date {
type Output = Date;
#[inline]
fn add(self, rhs: SignedDuration) -> Date {
self.checked_add(rhs)
.expect("adding signed duration to date overflowed")
}
}
impl core::ops::AddAssign<SignedDuration> for Date {
#[inline]
fn add_assign(&mut self, rhs: SignedDuration) {
*self = *self + rhs;
}
}
impl core::ops::Sub<SignedDuration> for Date {
type Output = Date;
#[inline]
fn sub(self, rhs: SignedDuration) -> Date {
self.checked_sub(rhs)
.expect("subing signed duration to date overflowed")
}
}
impl core::ops::SubAssign<SignedDuration> for Date {
#[inline]
fn sub_assign(&mut self, rhs: SignedDuration) {
*self = *self - rhs;
}
}
impl core::ops::Add<UnsignedDuration> for Date {
type Output = Date;
#[inline]
fn add(self, rhs: UnsignedDuration) -> Date {
self.checked_add(rhs)
.expect("adding unsigned duration to date overflowed")
}
}
impl core::ops::AddAssign<UnsignedDuration> for Date {
#[inline]
fn add_assign(&mut self, rhs: UnsignedDuration) {
*self = *self + rhs;
}
}
impl core::ops::Sub<UnsignedDuration> for Date {
type Output = Date;
#[inline]
fn sub(self, rhs: UnsignedDuration) -> Date {
self.checked_sub(rhs)
.expect("subing unsigned duration to date overflowed")
}
}
impl core::ops::SubAssign<UnsignedDuration> for Date {
#[inline]
fn sub_assign(&mut self, rhs: UnsignedDuration) {
*self = *self - rhs;
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Date {
#[inline]
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Date {
#[inline]
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<Date, D::Error> {
use serde::de;
struct DateVisitor;
impl<'de> de::Visitor<'de> for DateVisitor {
type Value = Date;
fn expecting(
&self,
f: &mut core::fmt::Formatter,
) -> core::fmt::Result {
f.write_str("a date string")
}
#[inline]
fn visit_bytes<E: de::Error>(
self,
value: &[u8],
) -> Result<Date, E> {
DEFAULT_DATETIME_PARSER
.parse_date(value)
.map_err(de::Error::custom)
}
#[inline]
fn visit_str<E: de::Error>(self, value: &str) -> Result<Date, E> {
self.visit_bytes(value.as_bytes())
}
}
deserializer.deserialize_bytes(DateVisitor)
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Date {
fn arbitrary(g: &mut quickcheck::Gen) -> Date {
let year = Year::arbitrary(g);
let month = Month::arbitrary(g);
let day = Day::arbitrary(g);
Date::constrain_ranged(year, month, day)
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Date>> {
alloc::boxed::Box::new(
(self.year_ranged(), self.month_ranged(), self.day_ranged())
.shrink()
.map(|(year, month, day)| {
Date::constrain_ranged(year, month, day)
}),
)
}
}
#[derive(Clone, Debug)]
pub struct DateSeries {
start: Date,
period: Span,
step: i64,
}
impl Iterator for DateSeries {
type Item = Date;
#[inline]
fn next(&mut self) -> Option<Date> {
let span = self.period.checked_mul(self.step).ok()?;
self.step = self.step.checked_add(1)?;
let date = self.start.checked_add(span).ok()?;
Some(date)
}
}
#[derive(Clone, Copy, Debug)]
pub struct DateArithmetic {
duration: Duration,
}
impl DateArithmetic {
#[inline]
fn checked_add(self, date: Date) -> Result<Date, Error> {
match self.duration.to_signed()? {
SDuration::Span(span) => date.checked_add_span(span),
SDuration::Absolute(sdur) => date.checked_add_duration(sdur),
}
}
#[inline]
fn checked_neg(self) -> Result<DateArithmetic, Error> {
let duration = self.duration.checked_neg()?;
Ok(DateArithmetic { duration })
}
#[inline]
fn is_negative(&self) -> bool {
self.duration.is_negative()
}
}
impl From<Span> for DateArithmetic {
fn from(span: Span) -> DateArithmetic {
let duration = Duration::from(span);
DateArithmetic { duration }
}
}
impl From<SignedDuration> for DateArithmetic {
fn from(sdur: SignedDuration) -> DateArithmetic {
let duration = Duration::from(sdur);
DateArithmetic { duration }
}
}
impl From<UnsignedDuration> for DateArithmetic {
fn from(udur: UnsignedDuration) -> DateArithmetic {
let duration = Duration::from(udur);
DateArithmetic { duration }
}
}
impl<'a> From<&'a Span> for DateArithmetic {
fn from(span: &'a Span) -> DateArithmetic {
DateArithmetic::from(*span)
}
}
impl<'a> From<&'a SignedDuration> for DateArithmetic {
fn from(sdur: &'a SignedDuration) -> DateArithmetic {
DateArithmetic::from(*sdur)
}
}
impl<'a> From<&'a UnsignedDuration> for DateArithmetic {
fn from(udur: &'a UnsignedDuration) -> DateArithmetic {
DateArithmetic::from(*udur)
}
}
#[derive(Clone, Copy, Debug)]
pub struct DateDifference {
date: Date,
round: SpanRound<'static>,
}
impl DateDifference {
#[inline]
pub fn new(date: Date) -> DateDifference {
let round = SpanRound::new().mode(RoundMode::Trunc);
DateDifference { date, round }
}
#[inline]
pub fn smallest(self, unit: Unit) -> DateDifference {
DateDifference { round: self.round.smallest(unit), ..self }
}
#[inline]
pub fn largest(self, unit: Unit) -> DateDifference {
DateDifference { round: self.round.largest(unit), ..self }
}
#[inline]
pub fn mode(self, mode: RoundMode) -> DateDifference {
DateDifference { round: self.round.mode(mode), ..self }
}
#[inline]
pub fn increment(self, increment: i64) -> DateDifference {
DateDifference { round: self.round.increment(increment), ..self }
}
#[inline]
fn rounding_may_change_span(&self) -> bool {
self.round.rounding_may_change_span_ignore_largest()
}
#[inline]
fn until_with_largest_unit(&self, d1: Date) -> Result<Span, Error> {
let d2 = self.date;
let largest = self
.round
.get_largest()
.unwrap_or_else(|| self.round.get_smallest().max(Unit::Day));
if largest < Unit::Day {
return Err(err!(
"rounding the span between two dates must use days \
or bigger for its units, but found {units}",
units = largest.plural(),
));
}
if largest <= Unit::Week {
let mut weeks = t::SpanWeeks::rfrom(C(0));
let mut days = d1.until_days_ranged(d2);
if largest == Unit::Week {
weeks = days.div_ceil(C(7)).rinto();
days = days.rem_ceil(C(7));
}
return Ok(Span::new().weeks_ranged(weeks).days_ranged(days));
}
let year1 = d1.year_ranged();
let month1 = d1.month_ranged();
let day1 = d1.day_ranged();
let mut year2 = d2.year_ranged();
let mut month2 = d2.month_ranged();
let day2 = d2.day_ranged();
let mut years =
t::SpanYears::rfrom(year2) - t::SpanYears::rfrom(year1);
let mut months =
t::SpanMonths::rfrom(month2) - t::SpanMonths::rfrom(month1);
let mut days = t::SpanDays::rfrom(day2) - t::SpanMonths::rfrom(day1);
if years != 0 || months != 0 {
let sign = if years != 0 {
Sign::rfrom(years.signum())
} else {
Sign::rfrom(months.signum())
};
let mut days_in_month1 =
t::SpanDays::rfrom(days_in_month(year2, month2));
let mut day_correct = t::SpanDays::N::<0>();
if days.signum() == -sign {
let original_days_in_month1 = days_in_month1;
let (y, m) = month_add_one(year2, month2, -sign).unwrap();
year2 = y;
month2 = m;
years =
t::SpanYears::rfrom(year2) - t::SpanYears::rfrom(year1);
months = t::SpanMonths::rfrom(month2)
- t::SpanMonths::rfrom(month1);
days_in_month1 = days_in_month(year2, month2).rinto();
day_correct = if sign < 0 {
-original_days_in_month1
} else {
days_in_month1
};
}
let day0_trunc = t::SpanDays::rfrom(day1.min(days_in_month1));
days = t::SpanDays::rfrom(day2) - day0_trunc + day_correct;
if years != 0 {
months = t::SpanMonths::rfrom(month2)
- t::SpanMonths::rfrom(month1);
if months.signum() == -sign {
let month_correct = if sign < 0 {
-t::MONTHS_PER_YEAR
} else {
t::MONTHS_PER_YEAR
};
year2 -= sign;
years = t::SpanYears::rfrom(year2)
- t::SpanYears::rfrom(year1);
months = t::SpanMonths::rfrom(month2)
- t::SpanMonths::rfrom(month1)
+ month_correct;
}
}
}
if largest == Unit::Month && years != 0 {
months = months.try_checked_add(
"months",
t::SpanMonths::rfrom(years) * t::MONTHS_PER_YEAR,
)?;
years = C(0).rinto();
}
Ok(Span::new()
.years_ranged(years)
.months_ranged(months)
.days_ranged(days))
}
}
impl From<Date> for DateDifference {
#[inline]
fn from(date: Date) -> DateDifference {
DateDifference::new(date)
}
}
impl From<DateTime> for DateDifference {
#[inline]
fn from(dt: DateTime) -> DateDifference {
DateDifference::from(Date::from(dt))
}
}
impl From<Zoned> for DateDifference {
#[inline]
fn from(zdt: Zoned) -> DateDifference {
DateDifference::from(Date::from(zdt))
}
}
impl<'a> From<&'a Zoned> for DateDifference {
#[inline]
fn from(zdt: &'a Zoned) -> DateDifference {
DateDifference::from(zdt.datetime())
}
}
impl From<(Unit, Date)> for DateDifference {
#[inline]
fn from((largest, date): (Unit, Date)) -> DateDifference {
DateDifference::from(date).largest(largest)
}
}
impl From<(Unit, DateTime)> for DateDifference {
#[inline]
fn from((largest, dt): (Unit, DateTime)) -> DateDifference {
DateDifference::from((largest, Date::from(dt)))
}
}
impl From<(Unit, Zoned)> for DateDifference {
#[inline]
fn from((largest, zdt): (Unit, Zoned)) -> DateDifference {
DateDifference::from((largest, Date::from(zdt)))
}
}
impl<'a> From<(Unit, &'a Zoned)> for DateDifference {
#[inline]
fn from((largest, zdt): (Unit, &'a Zoned)) -> DateDifference {
DateDifference::from((largest, zdt.datetime()))
}
}
#[derive(Clone, Copy, Debug)]
pub struct DateWith {
original: Date,
year: Option<DateWithYear>,
month: Option<i8>,
day: Option<DateWithDay>,
}
impl DateWith {
#[inline]
fn new(original: Date) -> DateWith {
DateWith { original, year: None, month: None, day: None }
}
#[inline]
pub fn build(self) -> Result<Date, Error> {
let year = match self.year {
None => self.original.year_ranged(),
Some(DateWithYear::Jiff(year)) => Year::try_new("year", year)?,
Some(DateWithYear::EraYear(year, Era::CE)) => {
let year_ce = t::YearCE::try_new("CE year", year)?;
t::Year::try_rfrom("CE year", year_ce)?
}
Some(DateWithYear::EraYear(year, Era::BCE)) => {
let year_bce = t::YearBCE::try_new("BCE year", year)?;
t::Year::try_rfrom("BCE year", -year_bce + C(1))?
}
};
let month = match self.month {
None => self.original.month_ranged(),
Some(month) => Month::try_new("month", month)?,
};
let day = match self.day {
None => self.original.day_ranged(),
Some(DateWithDay::OfMonth(day)) => Day::try_new("day", day)?,
Some(DateWithDay::OfYear(day)) => {
return day_of_year(year, day);
}
Some(DateWithDay::OfYearNoLeap(mut day)) => {
type DayOfYear = ri16<1, 365>;
let _ = DayOfYear::try_new("day-of-year", day)?;
if is_leap_year(year) && day >= 60 {
day += 1;
}
return day_of_year(year, day);
}
};
Date::new_ranged(year, month, day)
}
#[inline]
pub fn year(self, year: i16) -> DateWith {
DateWith { year: Some(DateWithYear::Jiff(year)), ..self }
}
#[inline]
pub fn era_year(self, year: i16, era: Era) -> DateWith {
DateWith { year: Some(DateWithYear::EraYear(year, era)), ..self }
}
#[inline]
pub fn month(self, month: i8) -> DateWith {
DateWith { month: Some(month), ..self }
}
#[inline]
pub fn day(self, day: i8) -> DateWith {
DateWith { day: Some(DateWithDay::OfMonth(day)), ..self }
}
#[inline]
pub fn day_of_year(self, day: i16) -> DateWith {
DateWith { day: Some(DateWithDay::OfYear(day)), ..self }
}
#[inline]
pub fn day_of_year_no_leap(self, day: i16) -> DateWith {
DateWith { day: Some(DateWithDay::OfYearNoLeap(day)), ..self }
}
}
#[derive(Clone, Copy, Debug)]
enum DateWithYear {
Jiff(i16),
EraYear(i16, Era),
}
#[derive(Clone, Copy, Debug)]
enum DateWithDay {
OfMonth(i8),
OfYear(i16),
OfYearNoLeap(i16),
}
fn iso_week_start_from_year(year: impl RInto<t::ISOYear>) -> UnixEpochDays {
let year = year.rinto();
let date_in_first_week = Date::new_ranged(year, C(1), C(4))
.expect("Jan 4 is valid for all valid years");
let diff_from_monday =
date_in_first_week.weekday().since_ranged(Weekday::Monday);
date_in_first_week.to_unix_epoch_days() - diff_from_monday
}
fn weekday_from_unix_epoch_days(days: impl RInto<UnixEpochDays>) -> Weekday {
let days = days.rinto();
Weekday::from_monday_zero_offset_ranged((days + C(3)) % C(7))
}
fn month_add_one(
year: impl RInto<Year>,
month: impl RInto<Month>,
sign: impl RInto<Sign>,
) -> Result<(Year, Month), Error> {
let mut year = year.rinto();
let mut month = month.rinto();
let delta = sign.rinto();
month += delta;
if month < 1 {
year -= C(1);
month += t::MONTHS_PER_YEAR;
} else if month > t::MONTHS_PER_YEAR {
year += C(1);
month -= t::MONTHS_PER_YEAR;
}
let year = Year::try_rfrom("year", year)?;
let month = Month::try_rfrom("year", month)?;
Ok((year, month))
}
fn month_add_overflowing(
month: impl RInto<t::Month>,
span: impl RInto<t::SpanMonths>,
) -> (t::Month, t::SpanYears) {
let month = t::SpanMonths::rfrom(month.rinto());
let span = span.rinto();
let total = month - C(1) + span;
let years = total / C(12);
let month = (total % C(12)) + C(1);
(month.rinto(), years.rinto())
}
fn day_of_year(year: Year, day: i16) -> Result<Date, Error> {
type DayOfYear = ri16<1, 366>;
let day = DayOfYear::try_new("day-of-year", day)?;
let span = Span::new().days_ranged(day - C(1));
let start = Date::new_ranged(year, C(1), C(1))?;
let end = start.checked_add(span)?;
if start.year() != end.year() {
debug_assert_eq!(day, 366);
debug_assert!(!start.in_leap_year());
return Err(Error::signed("day-of-year", day, 1, 365));
}
Ok(end)
}
#[cfg(test)]
mod tests {
use crate::{civil::date, tz::TimeZone, Timestamp, ToSpan};
use super::*;
#[test]
fn t_from_unix() {
fn date_from_timestamp(timestamp: Timestamp) -> Date {
timestamp.to_zoned(TimeZone::UTC).datetime().date()
}
assert_eq!(
date(1970, 1, 1),
date_from_timestamp(Timestamp::new(0, 0).unwrap()),
);
assert_eq!(
date(1969, 12, 31),
date_from_timestamp(Timestamp::new(-1, 0).unwrap()),
);
assert_eq!(
date(1969, 12, 31),
date_from_timestamp(Timestamp::new(-86_400, 0).unwrap()),
);
assert_eq!(
date(1969, 12, 30),
date_from_timestamp(Timestamp::new(-86_401, 0).unwrap()),
);
assert_eq!(
date(-9999, 1, 2),
date_from_timestamp(
Timestamp::new(t::UnixSeconds::MIN_REPR, 0).unwrap()
),
);
assert_eq!(
date(9999, 12, 30),
date_from_timestamp(
Timestamp::new(t::UnixSeconds::MAX_REPR, 0).unwrap()
),
);
}
#[test]
fn all_days_to_date_roundtrip() {
for rd in -100_000..=100_000 {
let rd = UnixEpochDays::new(rd).unwrap();
let date = Date::from_unix_epoch_days(rd);
let got = date.to_unix_epoch_days();
assert_eq!(rd, got, "for date {date:?}");
}
}
#[test]
fn all_date_to_days_roundtrip() {
use crate::util::common::days_in_month;
let year_range = 2000..=2500;
for year in year_range {
let year = Year::new(year).unwrap();
for month in Month::MIN_REPR..=Month::MAX_REPR {
let month = Month::new(month).unwrap();
for day in 1..=days_in_month(year, month).get() {
let date = date(year.get(), month.get(), day);
let rd = date.to_unix_epoch_days();
let got = Date::from_unix_epoch_days(rd);
assert_eq!(date, got, "for date {date:?}");
}
}
}
}
#[test]
fn all_date_to_iso_week_date_roundtrip() {
use crate::util::common::days_in_month;
let year_range = 2000..=2500;
for year in year_range {
let year = Year::new(year).unwrap();
for month in [1, 2, 4] {
let month = Month::new(month).unwrap();
for day in 20..=days_in_month(year, month).get() {
let date = date(year.get(), month.get(), day);
let wd = date.to_iso_week_date();
let got = wd.to_date();
assert_eq!(
date, got,
"for date {date:?}, and ISO week date {wd:?}"
);
}
}
}
}
#[test]
fn add_constrained() {
use crate::ToSpan;
let d1 = date(2023, 3, 31);
let d2 = d1.checked_add(1.months().days(1)).unwrap();
assert_eq!(d2, date(2023, 5, 1));
}
#[test]
fn since_years() {
let d1 = date(2023, 4, 15);
let d2 = date(2019, 2, 22);
let span = d1.since((Unit::Year, d2)).unwrap();
assert_eq!(span, 4.years().months(1).days(21));
let span = d2.since((Unit::Year, d1)).unwrap();
assert_eq!(span, -4.years().months(1).days(24));
let d1 = date(2023, 2, 22);
let d2 = date(2019, 4, 15);
let span = d1.since((Unit::Year, d2)).unwrap();
assert_eq!(span, 3.years().months(10).days(7));
let span = d2.since((Unit::Year, d1)).unwrap();
assert_eq!(span, -3.years().months(10).days(7));
let d1 = date(9999, 12, 31);
let d2 = date(-9999, 1, 1);
let span = d1.since((Unit::Year, d2)).unwrap();
assert_eq!(span, 19998.years().months(11).days(30));
let span = d2.since((Unit::Year, d1)).unwrap();
assert_eq!(span, -19998.years().months(11).days(30));
}
#[test]
fn since_months() {
let d1 = date(2024, 7, 24);
let d2 = date(2024, 2, 22);
let span = d1.since((Unit::Month, d2)).unwrap();
assert_eq!(span, 5.months().days(2));
let span = d2.since((Unit::Month, d1)).unwrap();
assert_eq!(span, -5.months().days(2));
assert_eq!(d2, d1.checked_sub(5.months().days(2)).unwrap());
assert_eq!(d1, d2.checked_sub(-5.months().days(2)).unwrap());
let d1 = date(2024, 7, 15);
let d2 = date(2024, 2, 22);
let span = d1.since((Unit::Month, d2)).unwrap();
assert_eq!(span, 4.months().days(22));
let span = d2.since((Unit::Month, d1)).unwrap();
assert_eq!(span, -4.months().days(23));
assert_eq!(d2, d1.checked_sub(4.months().days(22)).unwrap());
assert_eq!(d1, d2.checked_sub(-4.months().days(23)).unwrap());
let d1 = date(2023, 4, 15);
let d2 = date(2023, 2, 22);
let span = d1.since((Unit::Month, d2)).unwrap();
assert_eq!(span, 1.month().days(21));
let span = d2.since((Unit::Month, d1)).unwrap();
assert_eq!(span, -1.month().days(24));
assert_eq!(d2, d1.checked_sub(1.month().days(21)).unwrap());
assert_eq!(d1, d2.checked_sub(-1.month().days(24)).unwrap());
let d1 = date(2023, 4, 15);
let d2 = date(2019, 2, 22);
let span = d1.since((Unit::Month, d2)).unwrap();
assert_eq!(span, 49.months().days(21));
let span = d2.since((Unit::Month, d1)).unwrap();
assert_eq!(span, -49.months().days(24));
}
#[test]
fn since_weeks() {
let d1 = date(2024, 7, 15);
let d2 = date(2024, 6, 22);
let span = d1.since((Unit::Week, d2)).unwrap();
assert_eq!(span, 3.weeks().days(2));
let span = d2.since((Unit::Week, d1)).unwrap();
assert_eq!(span, -3.weeks().days(2));
}
#[test]
fn since_days() {
let d1 = date(2024, 7, 15);
let d2 = date(2024, 2, 22);
let span = d1.since((Unit::Day, d2)).unwrap();
assert_eq!(span, 144.days());
let span = d2.since((Unit::Day, d1)).unwrap();
assert_eq!(span, -144.days());
}
#[test]
fn until_month_lengths() {
let jan1 = date(2020, 1, 1);
let feb1 = date(2020, 2, 1);
let mar1 = date(2020, 3, 1);
assert_eq!(jan1.until(feb1).unwrap(), 31.days());
assert_eq!(jan1.until((Unit::Month, feb1)).unwrap(), 1.month());
assert_eq!(feb1.until(mar1).unwrap(), 29.days());
assert_eq!(feb1.until((Unit::Month, mar1)).unwrap(), 1.month());
assert_eq!(jan1.until(mar1).unwrap(), 60.days());
assert_eq!(jan1.until((Unit::Month, mar1)).unwrap(), 2.months());
}
#[test]
fn since_until_not_commutative() {
let d1 = date(2020, 4, 30);
let d2 = date(2020, 2, 29);
let since = d1.since((Unit::Month, d2)).unwrap();
assert_eq!(since, 2.months());
let until = d2.until((Unit::Month, d1)).unwrap();
assert_eq!(until, 2.months().days(1));
}
#[test]
fn until_weeks_round() {
use crate::{RoundMode, SpanRound};
let earlier = date(2019, 1, 8);
let later = date(2021, 9, 7);
let span = earlier.until((Unit::Week, later)).unwrap();
assert_eq!(span, 139.weeks());
let options = SpanRound::new()
.smallest(Unit::Week)
.mode(RoundMode::HalfExpand)
.relative(earlier.to_datetime(Time::midnight()));
let rounded = span.round(options).unwrap();
assert_eq!(rounded, 139.weeks());
}
#[test]
fn until_months_no_balance() {
let sp =
date(2023, 5, 31).until((Unit::Month, date(2024, 4, 30))).unwrap();
assert_eq!(sp, 10.months().days(30));
let sp =
date(2023, 5, 31).until((Unit::Month, date(2023, 6, 30))).unwrap();
assert_eq!(sp, 30.days());
}
#[test]
fn test_month_add() {
let add =
|year: i16, month: i8, delta: i8| -> Result<(i16, i8), Error> {
let year = Year::new(year).unwrap();
let month = Month::new(month).unwrap();
let delta = Sign::new(delta).unwrap();
let (year, month) = month_add_one(year, month, delta)?;
Ok((year.get(), month.get()))
};
assert_eq!(add(2024, 1, 1).unwrap(), (2024, 2));
assert_eq!(add(2024, 1, -1).unwrap(), (2023, 12));
assert_eq!(add(2024, 12, 1).unwrap(), (2025, 1));
assert_eq!(add(9999, 12, -1).unwrap(), (9999, 11));
assert_eq!(add(-9999, 1, 1).unwrap(), (-9999, 2));
assert!(add(9999, 12, 1).is_err());
assert!(add(-9999, 1, -1).is_err());
}
#[test]
fn test_month_add_overflowing() {
let month_add = |month, span| {
let month = t::Month::new(month).unwrap();
let span = t::SpanMonths::new(span).unwrap();
let (month, years) = month_add_overflowing(month, span);
(month.get(), years.get())
};
assert_eq!((1, 0), month_add(1, 0));
assert_eq!((12, 0), month_add(1, 11));
assert_eq!((1, 1), month_add(1, 12));
assert_eq!((2, 1), month_add(1, 13));
assert_eq!((9, 1), month_add(1, 20));
assert_eq!((12, 19998), month_add(12, t::SpanMonths::MAX_REPR));
assert_eq!((12, -1), month_add(1, -1));
assert_eq!((11, -1), month_add(1, -2));
assert_eq!((1, -1), month_add(1, -12));
assert_eq!((12, -2), month_add(1, -13));
}
#[test]
fn date_size() {
#[cfg(debug_assertions)]
{
assert_eq!(12, core::mem::size_of::<Date>());
}
#[cfg(not(debug_assertions))]
{
assert_eq!(4, core::mem::size_of::<Date>());
}
}
quickcheck::quickcheck! {
fn prop_checked_add_then_sub(
d1: Date,
span: Span
) -> quickcheck::TestResult {
let span = if span.largest_unit() <= Unit::Day {
span
} else {
let round = SpanRound::new().largest(Unit::Day).relative(d1);
let Ok(span) = span.round(round) else {
return quickcheck::TestResult::discard();
};
span
};
let Ok(d2) = d1.checked_add(span) else {
return quickcheck::TestResult::discard();
};
let got = d2.checked_sub(span).unwrap();
quickcheck::TestResult::from_bool(d1 == got)
}
fn prop_checked_sub_then_add(
d1: Date,
span: Span
) -> quickcheck::TestResult {
let span = if span.largest_unit() <= Unit::Day {
span
} else {
let round = SpanRound::new().largest(Unit::Day).relative(d1);
let Ok(span) = span.round(round) else {
return quickcheck::TestResult::discard();
};
span
};
let Ok(d2) = d1.checked_sub(span) else {
return quickcheck::TestResult::discard();
};
let got = d2.checked_add(span).unwrap();
quickcheck::TestResult::from_bool(d1 == got)
}
fn prop_since_then_add(d1: Date, d2: Date) -> bool {
let span = d1.since(d2).unwrap();
let got = d2.checked_add(span).unwrap();
d1 == got
}
fn prop_until_then_sub(d1: Date, d2: Date) -> bool {
let span = d1.until(d2).unwrap();
let got = d2.checked_sub(span).unwrap();
d1 == got
}
}
}