use core::time::Duration as UnsignedDuration;
use crate::{
civil::{
datetime, Date, DateWith, Era, ISOWeekDate, Time, TimeWith, Weekday,
},
duration::{Duration, SDuration},
error::{civil::Error as E, Error, ErrorContext},
fmt::{
self,
temporal::{self, DEFAULT_DATETIME_PARSER},
},
shared::util::itime::IDateTime,
tz::TimeZone,
util::{b, round::Increment},
zoned::Zoned,
RoundMode, SignedDuration, Span, SpanRound, Unit,
};
#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct DateTime {
date: Date,
time: Time,
}
impl DateTime {
pub const MIN: DateTime = datetime(-9999, 1, 1, 0, 0, 0, 0);
pub const MAX: DateTime = datetime(9999, 12, 31, 23, 59, 59, 999_999_999);
pub const ZERO: DateTime = DateTime::from_parts(Date::ZERO, Time::MIN);
#[inline]
pub fn new(
year: i16,
month: i8,
day: i8,
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
) -> Result<DateTime, Error> {
let date = Date::new(year, month, day)?;
let time = Time::new(hour, minute, second, subsec_nanosecond)?;
Ok(DateTime { date, time })
}
#[inline]
pub const fn constant(
year: i16,
month: i8,
day: i8,
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
) -> DateTime {
let date = Date::constant(year, month, day);
let time = Time::constant(hour, minute, second, subsec_nanosecond);
DateTime { date, time }
}
#[inline]
pub const fn from_parts(date: Date, time: Time) -> DateTime {
DateTime { date, time }
}
#[inline]
pub fn with(self) -> DateTimeWith {
DateTimeWith::new(self)
}
#[inline]
pub fn year(self) -> i16 {
self.date().year()
}
#[inline]
pub fn era_year(self) -> (i16, Era) {
self.date().era_year()
}
#[inline]
pub fn month(self) -> i8 {
self.date().month()
}
#[inline]
pub fn day(self) -> i8 {
self.date().day()
}
#[inline]
pub fn hour(self) -> i8 {
self.time().hour()
}
#[inline]
pub fn minute(self) -> i8 {
self.time().minute()
}
#[inline]
pub fn second(self) -> i8 {
self.time().second()
}
#[inline]
pub fn millisecond(self) -> i16 {
self.time().millisecond()
}
#[inline]
pub fn microsecond(self) -> i16 {
self.time().microsecond()
}
#[inline]
pub fn nanosecond(self) -> i16 {
self.time().nanosecond()
}
#[inline]
pub fn subsec_nanosecond(self) -> i32 {
self.time().subsec_nanosecond()
}
#[inline]
pub fn weekday(self) -> Weekday {
self.date().weekday()
}
#[inline]
pub fn day_of_year(self) -> i16 {
self.date().day_of_year()
}
#[inline]
pub fn day_of_year_no_leap(self) -> Option<i16> {
self.date().day_of_year_no_leap()
}
#[inline]
pub fn start_of_day(&self) -> DateTime {
DateTime::from_parts(self.date(), Time::MIN)
}
#[inline]
pub fn end_of_day(&self) -> DateTime {
DateTime::from_parts(self.date(), Time::MAX)
}
#[inline]
pub fn first_of_month(self) -> DateTime {
DateTime::from_parts(self.date().first_of_month(), self.time())
}
#[inline]
pub fn last_of_month(self) -> DateTime {
DateTime::from_parts(self.date().last_of_month(), self.time())
}
#[inline]
pub fn days_in_month(self) -> i8 {
self.date().days_in_month()
}
#[inline]
pub fn first_of_year(self) -> DateTime {
DateTime::from_parts(self.date().first_of_year(), self.time())
}
#[inline]
pub fn last_of_year(self) -> DateTime {
DateTime::from_parts(self.date().last_of_year(), self.time())
}
#[inline]
pub fn days_in_year(self) -> i16 {
self.date().days_in_year()
}
#[inline]
pub fn in_leap_year(self) -> bool {
self.date().in_leap_year()
}
#[inline]
pub fn tomorrow(self) -> Result<DateTime, Error> {
Ok(DateTime::from_parts(self.date().tomorrow()?, self.time()))
}
#[inline]
pub fn yesterday(self) -> Result<DateTime, Error> {
Ok(DateTime::from_parts(self.date().yesterday()?, self.time()))
}
#[inline]
pub fn nth_weekday_of_month(
self,
nth: i8,
weekday: Weekday,
) -> Result<DateTime, Error> {
let date = self.date().nth_weekday_of_month(nth, weekday)?;
Ok(DateTime::from_parts(date, self.time()))
}
#[inline]
pub fn nth_weekday(
self,
nth: i32,
weekday: Weekday,
) -> Result<DateTime, Error> {
let date = self.date().nth_weekday(nth, weekday)?;
Ok(DateTime::from_parts(date, self.time()))
}
#[inline]
pub fn date(self) -> Date {
self.date
}
#[inline]
pub fn time(self) -> Time {
self.time
}
#[inline]
pub fn iso_week_date(self) -> ISOWeekDate {
self.date().iso_week_date()
}
#[inline]
pub fn in_tz(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> {
use crate::tz::AmbiguousOffset;
let dt = self;
let amb_ts = tz.to_ambiguous_timestamp(dt);
let (offset, ts, dt) = match amb_ts.offset() {
AmbiguousOffset::Unambiguous { offset } => {
let ts = offset.to_timestamp(dt)?;
(offset, ts, dt)
}
AmbiguousOffset::Gap { before, .. } => {
let ts = before.to_timestamp(dt)?;
let offset = tz.to_offset(ts);
let dt = offset.to_datetime(ts);
(offset, ts, dt)
}
AmbiguousOffset::Fold { before, .. } => {
let ts = before.to_timestamp(dt)?;
let offset = tz.to_offset(ts);
let dt = offset.to_datetime(ts);
(offset, ts, dt)
}
};
Ok(Zoned::from_parts(ts, tz, offset, dt))
}
#[inline]
pub fn checked_add<A: Into<DateTimeArithmetic>>(
self,
duration: A,
) -> Result<DateTime, Error> {
let duration: DateTimeArithmetic = duration.into();
duration.checked_add(self)
}
#[inline]
fn checked_add_span(self, span: &Span) -> Result<DateTime, Error> {
let (old_date, old_time) = (self.date(), self.time());
let units = span.units();
match (units.only_calendar().is_empty(), units.only_time().is_empty())
{
(true, true) => Ok(self),
(false, true) => {
let new_date = old_date
.checked_add(span)
.context(E::FailedAddSpanDate)?;
Ok(DateTime::from_parts(new_date, old_time))
}
(true, false) => {
let (new_time, leftovers) = old_time
.overflowing_add(span)
.context(E::FailedAddSpanTime)?;
let new_date = old_date
.checked_add(leftovers)
.context(E::FailedAddSpanOverflowing)?;
Ok(DateTime::from_parts(new_date, new_time))
}
(false, false) => self.checked_add_span_general(span),
}
}
#[inline(never)]
#[cold]
fn checked_add_span_general(self, span: &Span) -> Result<DateTime, Error> {
let (old_date, old_time) = (self.date(), self.time());
let span_date = span.without_lower(Unit::Day);
let span_time = span.only_lower(Unit::Day);
let (new_time, leftovers) = old_time
.overflowing_add(&span_time)
.context(E::FailedAddSpanTime)?;
let new_date =
old_date.checked_add(span_date).context(E::FailedAddSpanDate)?;
let new_date = new_date
.checked_add(leftovers)
.context(E::FailedAddSpanOverflowing)?;
Ok(DateTime::from_parts(new_date, new_time))
}
#[inline]
fn checked_add_duration(
self,
duration: SignedDuration,
) -> Result<DateTime, Error> {
let (date, time) = (self.date(), self.time());
let (new_time, leftovers) = time.overflowing_add_duration(duration)?;
let new_date = date
.checked_add(leftovers)
.context(E::FailedAddDurationOverflowing)?;
Ok(DateTime::from_parts(new_date, new_time))
}
#[inline]
pub fn checked_sub<A: Into<DateTimeArithmetic>>(
self,
duration: A,
) -> Result<DateTime, Error> {
let duration: DateTimeArithmetic = duration.into();
duration.checked_neg().and_then(|dta| dta.checked_add(self))
}
#[inline]
pub fn saturating_add<A: Into<DateTimeArithmetic>>(
self,
duration: A,
) -> DateTime {
let duration: DateTimeArithmetic = duration.into();
self.checked_add(duration).unwrap_or_else(|_| {
if duration.is_negative() {
DateTime::MIN
} else {
DateTime::MAX
}
})
}
#[inline]
pub fn saturating_sub<A: Into<DateTimeArithmetic>>(
self,
duration: A,
) -> DateTime {
let duration: DateTimeArithmetic = duration.into();
let Ok(duration) = duration.checked_neg() else {
return DateTime::MIN;
};
self.saturating_add(duration)
}
#[inline]
pub fn until<A: Into<DateTimeDifference>>(
self,
other: A,
) -> Result<Span, Error> {
let args: DateTimeDifference = 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<DateTimeDifference>>(
self,
other: A,
) -> Result<Span, Error> {
let args: DateTimeDifference = 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: DateTime) -> SignedDuration {
SignedDuration::datetime_until(self, other)
}
#[inline]
pub fn duration_since(self, other: DateTime) -> SignedDuration {
SignedDuration::datetime_until(other, self)
}
#[inline]
pub fn round<R: Into<DateTimeRound>>(
self,
options: R,
) -> Result<DateTime, Error> {
let options: DateTimeRound = options.into();
options.round(self)
}
#[inline]
pub fn series(self, period: Span) -> DateTimeSeries {
DateTimeSeries { start: self, period, step: 0 }
}
#[inline]
fn to_duration(self) -> SignedDuration {
let mut dur =
SignedDuration::from_civil_days32(self.date().to_unix_epoch_day());
dur += self.time().to_duration();
dur
}
#[inline]
pub(crate) const fn to_idatetime_const(&self) -> IDateTime {
IDateTime {
date: self.date.to_idate_const(),
time: self.time.to_itime_const(),
}
}
#[inline]
pub(crate) const fn from_idatetime_const(idt: IDateTime) -> DateTime {
DateTime::from_parts(
Date::from_idate_const(idt.date),
Time::from_itime_const(idt.time),
)
}
}
impl DateTime {
#[inline]
pub fn strptime(
format: impl AsRef<[u8]>,
input: impl AsRef<[u8]>,
) -> Result<DateTime, Error> {
fmt::strtime::parse(format, input).and_then(|tm| tm.to_datetime())
}
#[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() }
}
}
impl Default for DateTime {
#[inline]
fn default() -> DateTime {
DateTime::ZERO
}
}
impl core::fmt::Debug for DateTime {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Display::fmt(self, f)
}
}
impl core::fmt::Display for DateTime {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use crate::fmt::StdFmtWrite;
let precision =
f.precision().map(|p| u8::try_from(p).unwrap_or(u8::MAX));
temporal::DateTimePrinter::new()
.precision(precision)
.print_datetime(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
}
}
impl core::str::FromStr for DateTime {
type Err = Error;
#[inline]
fn from_str(string: &str) -> Result<DateTime, Error> {
DEFAULT_DATETIME_PARSER.parse_datetime(string)
}
}
impl From<Date> for DateTime {
#[inline]
fn from(date: Date) -> DateTime {
date.to_datetime(Time::midnight())
}
}
impl From<Zoned> for DateTime {
#[inline]
fn from(zdt: Zoned) -> DateTime {
zdt.datetime()
}
}
impl<'a> From<&'a Zoned> for DateTime {
#[inline]
fn from(zdt: &'a Zoned) -> DateTime {
zdt.datetime()
}
}
impl core::ops::Add<Span> for DateTime {
type Output = DateTime;
#[inline]
fn add(self, rhs: Span) -> DateTime {
self.checked_add(rhs).expect("adding span to datetime overflowed")
}
}
impl core::ops::AddAssign<Span> for DateTime {
#[inline]
fn add_assign(&mut self, rhs: Span) {
*self = *self + rhs
}
}
impl core::ops::Sub<Span> for DateTime {
type Output = DateTime;
#[inline]
fn sub(self, rhs: Span) -> DateTime {
self.checked_sub(rhs)
.expect("subtracting span from datetime overflowed")
}
}
impl core::ops::SubAssign<Span> for DateTime {
#[inline]
fn sub_assign(&mut self, rhs: Span) {
*self = *self - rhs
}
}
impl core::ops::Sub for DateTime {
type Output = Span;
#[inline]
fn sub(self, rhs: DateTime) -> Span {
self.since(rhs).expect("since never fails when given DateTime")
}
}
impl core::ops::Add<SignedDuration> for DateTime {
type Output = DateTime;
#[inline]
fn add(self, rhs: SignedDuration) -> DateTime {
self.checked_add(rhs)
.expect("adding signed duration to datetime overflowed")
}
}
impl core::ops::AddAssign<SignedDuration> for DateTime {
#[inline]
fn add_assign(&mut self, rhs: SignedDuration) {
*self = *self + rhs
}
}
impl core::ops::Sub<SignedDuration> for DateTime {
type Output = DateTime;
#[inline]
fn sub(self, rhs: SignedDuration) -> DateTime {
self.checked_sub(rhs)
.expect("subtracting signed duration from datetime overflowed")
}
}
impl core::ops::SubAssign<SignedDuration> for DateTime {
#[inline]
fn sub_assign(&mut self, rhs: SignedDuration) {
*self = *self - rhs
}
}
impl core::ops::Add<UnsignedDuration> for DateTime {
type Output = DateTime;
#[inline]
fn add(self, rhs: UnsignedDuration) -> DateTime {
self.checked_add(rhs)
.expect("adding unsigned duration to datetime overflowed")
}
}
impl core::ops::AddAssign<UnsignedDuration> for DateTime {
#[inline]
fn add_assign(&mut self, rhs: UnsignedDuration) {
*self = *self + rhs
}
}
impl core::ops::Sub<UnsignedDuration> for DateTime {
type Output = DateTime;
#[inline]
fn sub(self, rhs: UnsignedDuration) -> DateTime {
self.checked_sub(rhs)
.expect("subtracting unsigned duration from datetime overflowed")
}
}
impl core::ops::SubAssign<UnsignedDuration> for DateTime {
#[inline]
fn sub_assign(&mut self, rhs: UnsignedDuration) {
*self = *self - rhs
}
}
#[cfg(feature = "serde")]
impl serde_core::Serialize for DateTime {
#[inline]
fn serialize<S: serde_core::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
impl<'de> serde_core::Deserialize<'de> for DateTime {
#[inline]
fn deserialize<D: serde_core::Deserializer<'de>>(
deserializer: D,
) -> Result<DateTime, D::Error> {
use serde_core::de;
struct DateTimeVisitor;
impl<'de> de::Visitor<'de> for DateTimeVisitor {
type Value = DateTime;
fn expecting(
&self,
f: &mut core::fmt::Formatter,
) -> core::fmt::Result {
f.write_str("a datetime string")
}
#[inline]
fn visit_bytes<E: de::Error>(
self,
value: &[u8],
) -> Result<DateTime, E> {
DEFAULT_DATETIME_PARSER
.parse_datetime(value)
.map_err(de::Error::custom)
}
#[inline]
fn visit_str<E: de::Error>(
self,
value: &str,
) -> Result<DateTime, E> {
self.visit_bytes(value.as_bytes())
}
}
deserializer.deserialize_str(DateTimeVisitor)
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for DateTime {
fn arbitrary(g: &mut quickcheck::Gen) -> DateTime {
let date = Date::arbitrary(g);
let time = Time::arbitrary(g);
DateTime::from_parts(date, time)
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = DateTime>> {
alloc::boxed::Box::new(
(self.date(), self.time())
.shrink()
.map(|(date, time)| DateTime::from_parts(date, time)),
)
}
}
#[derive(Clone, Debug)]
pub struct DateTimeSeries {
start: DateTime,
period: Span,
step: i64,
}
impl Iterator for DateTimeSeries {
type Item = DateTime;
#[inline]
fn next(&mut self) -> Option<DateTime> {
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)
}
}
impl core::iter::FusedIterator for DateTimeSeries {}
#[derive(Clone, Copy, Debug)]
pub struct DateTimeArithmetic {
duration: Duration,
}
impl DateTimeArithmetic {
#[inline]
fn checked_add(self, dt: DateTime) -> Result<DateTime, Error> {
match self.duration.to_signed()? {
SDuration::Span(span) => dt.checked_add_span(span),
SDuration::Absolute(sdur) => dt.checked_add_duration(sdur),
}
}
#[inline]
fn checked_neg(self) -> Result<DateTimeArithmetic, Error> {
let duration = self.duration.checked_neg()?;
Ok(DateTimeArithmetic { duration })
}
#[inline]
fn is_negative(&self) -> bool {
self.duration.is_negative()
}
}
impl From<Span> for DateTimeArithmetic {
fn from(span: Span) -> DateTimeArithmetic {
let duration = Duration::from(span);
DateTimeArithmetic { duration }
}
}
impl From<SignedDuration> for DateTimeArithmetic {
fn from(sdur: SignedDuration) -> DateTimeArithmetic {
let duration = Duration::from(sdur);
DateTimeArithmetic { duration }
}
}
impl From<UnsignedDuration> for DateTimeArithmetic {
fn from(udur: UnsignedDuration) -> DateTimeArithmetic {
let duration = Duration::from(udur);
DateTimeArithmetic { duration }
}
}
impl<'a> From<&'a Span> for DateTimeArithmetic {
fn from(span: &'a Span) -> DateTimeArithmetic {
DateTimeArithmetic::from(*span)
}
}
impl<'a> From<&'a SignedDuration> for DateTimeArithmetic {
fn from(sdur: &'a SignedDuration) -> DateTimeArithmetic {
DateTimeArithmetic::from(*sdur)
}
}
impl<'a> From<&'a UnsignedDuration> for DateTimeArithmetic {
fn from(udur: &'a UnsignedDuration) -> DateTimeArithmetic {
DateTimeArithmetic::from(*udur)
}
}
#[derive(Clone, Copy, Debug)]
pub struct DateTimeDifference {
datetime: DateTime,
round: SpanRound<'static>,
}
impl DateTimeDifference {
#[inline]
pub fn new(datetime: DateTime) -> DateTimeDifference {
let round = SpanRound::new().mode(RoundMode::Trunc);
DateTimeDifference { datetime, round }
}
#[inline]
pub fn smallest(self, unit: Unit) -> DateTimeDifference {
DateTimeDifference { round: self.round.smallest(unit), ..self }
}
#[inline]
pub fn largest(self, unit: Unit) -> DateTimeDifference {
DateTimeDifference { round: self.round.largest(unit), ..self }
}
#[inline]
pub fn mode(self, mode: RoundMode) -> DateTimeDifference {
DateTimeDifference { round: self.round.mode(mode), ..self }
}
#[inline]
pub fn increment(self, increment: i64) -> DateTimeDifference {
DateTimeDifference { round: self.round.increment(increment), ..self }
}
#[inline]
fn rounding_may_change_span(&self) -> bool {
self.round.rounding_may_change_span()
}
#[inline]
fn until_with_largest_unit(&self, dt1: DateTime) -> Result<Span, Error> {
let dt2 = self.datetime;
let largest = self
.round
.get_largest()
.unwrap_or_else(|| self.round.get_smallest().max(Unit::Day));
if largest <= Unit::Day {
let diff = dt2.to_duration() - dt1.to_duration();
return Span::from_invariant_duration(largest, diff);
}
let (d1, mut d2) = (dt1.date(), dt2.date());
let (t1, t2) = (dt1.time(), dt2.time());
let sign = b::Sign::from_ordinals(d2, d1);
let mut time_diff = t1.until_nanoseconds(t2);
if b::Sign::from(time_diff) == -sign {
if sign.is_positive() {
d2 = d2.yesterday().unwrap();
} else if sign.is_negative() {
d2 = d2.tomorrow().unwrap();
}
time_diff += b::NANOS_PER_CIVIL_DAY * sign;
}
let date_span = d1.until((largest, d2))?;
let time_span = Span::from_invariant_duration(
largest,
SignedDuration::from_nanos(time_diff),
)
.expect("difference between time always fits in span");
Ok(time_span
.years(date_span.get_years())
.months(date_span.get_months())
.weeks(date_span.get_weeks())
.days(date_span.get_days()))
}
}
impl From<DateTime> for DateTimeDifference {
#[inline]
fn from(dt: DateTime) -> DateTimeDifference {
DateTimeDifference::new(dt)
}
}
impl From<Date> for DateTimeDifference {
#[inline]
fn from(date: Date) -> DateTimeDifference {
DateTimeDifference::from(DateTime::from(date))
}
}
impl From<Zoned> for DateTimeDifference {
#[inline]
fn from(zdt: Zoned) -> DateTimeDifference {
DateTimeDifference::from(DateTime::from(zdt))
}
}
impl<'a> From<&'a Zoned> for DateTimeDifference {
#[inline]
fn from(zdt: &'a Zoned) -> DateTimeDifference {
DateTimeDifference::from(zdt.datetime())
}
}
impl From<(Unit, DateTime)> for DateTimeDifference {
#[inline]
fn from((largest, dt): (Unit, DateTime)) -> DateTimeDifference {
DateTimeDifference::from(dt).largest(largest)
}
}
impl From<(Unit, Date)> for DateTimeDifference {
#[inline]
fn from((largest, date): (Unit, Date)) -> DateTimeDifference {
DateTimeDifference::from(date).largest(largest)
}
}
impl From<(Unit, Zoned)> for DateTimeDifference {
#[inline]
fn from((largest, zdt): (Unit, Zoned)) -> DateTimeDifference {
DateTimeDifference::from((largest, DateTime::from(zdt)))
}
}
impl<'a> From<(Unit, &'a Zoned)> for DateTimeDifference {
#[inline]
fn from((largest, zdt): (Unit, &'a Zoned)) -> DateTimeDifference {
DateTimeDifference::from((largest, zdt.datetime()))
}
}
#[derive(Clone, Copy, Debug)]
pub struct DateTimeRound {
smallest: Unit,
mode: RoundMode,
increment: i64,
}
impl DateTimeRound {
#[inline]
pub fn new() -> DateTimeRound {
DateTimeRound {
smallest: Unit::Nanosecond,
mode: RoundMode::HalfExpand,
increment: 1,
}
}
#[inline]
pub fn smallest(self, unit: Unit) -> DateTimeRound {
DateTimeRound { smallest: unit, ..self }
}
#[inline]
pub fn mode(self, mode: RoundMode) -> DateTimeRound {
DateTimeRound { mode, ..self }
}
#[inline]
pub fn increment(self, increment: i64) -> DateTimeRound {
DateTimeRound { increment, ..self }
}
pub(crate) fn round(&self, dt: DateTime) -> Result<DateTime, Error> {
if self.smallest == Unit::Nanosecond && self.increment == 1 {
return Ok(dt);
}
let increment =
Increment::for_datetime(self.smallest, self.increment)?;
let time_nanos = dt.time().to_duration();
let sign = b::Sign::from(dt.date().year());
let time_rounded = increment.round(self.mode, time_nanos)?;
let (days, time_nanos) = time_rounded.as_civil_days_with_remainder();
let days = sign * days;
let time = Time::from_duration_unchecked(time_nanos);
let days_len = (i64::from(dt.date().day()) - 1) + days;
let start = dt.date().first_of_month();
let date = start
.checked_add(Span::new().days(days_len))
.context(E::FailedAddDays)?;
Ok(DateTime::from_parts(date, time))
}
pub(crate) fn get_smallest(&self) -> Unit {
self.smallest
}
pub(crate) fn get_mode(&self) -> RoundMode {
self.mode
}
pub(crate) fn get_increment(&self) -> i64 {
self.increment
}
}
impl Default for DateTimeRound {
#[inline]
fn default() -> DateTimeRound {
DateTimeRound::new()
}
}
impl From<Unit> for DateTimeRound {
#[inline]
fn from(unit: Unit) -> DateTimeRound {
DateTimeRound::default().smallest(unit)
}
}
impl From<(Unit, i64)> for DateTimeRound {
#[inline]
fn from((unit, increment): (Unit, i64)) -> DateTimeRound {
DateTimeRound::from(unit).increment(increment)
}
}
#[derive(Clone, Copy, Debug)]
pub struct DateTimeWith {
date_with: DateWith,
time_with: TimeWith,
}
impl DateTimeWith {
#[inline]
fn new(original: DateTime) -> DateTimeWith {
DateTimeWith {
date_with: original.date().with(),
time_with: original.time().with(),
}
}
#[inline]
pub fn build(self) -> Result<DateTime, Error> {
let date = self.date_with.build()?;
let time = self.time_with.build()?;
Ok(DateTime::from_parts(date, time))
}
#[inline]
pub fn date(self, date: Date) -> DateTimeWith {
DateTimeWith { date_with: date.with(), ..self }
}
#[inline]
pub fn time(self, time: Time) -> DateTimeWith {
DateTimeWith { time_with: time.with(), ..self }
}
#[inline]
pub fn year(self, year: i16) -> DateTimeWith {
DateTimeWith { date_with: self.date_with.year(year), ..self }
}
#[inline]
pub fn era_year(self, year: i16, era: Era) -> DateTimeWith {
DateTimeWith { date_with: self.date_with.era_year(year, era), ..self }
}
#[inline]
pub fn month(self, month: i8) -> DateTimeWith {
DateTimeWith { date_with: self.date_with.month(month), ..self }
}
#[inline]
pub fn day(self, day: i8) -> DateTimeWith {
DateTimeWith { date_with: self.date_with.day(day), ..self }
}
#[inline]
pub fn day_of_year(self, day: i16) -> DateTimeWith {
DateTimeWith { date_with: self.date_with.day_of_year(day), ..self }
}
#[inline]
pub fn day_of_year_no_leap(self, day: i16) -> DateTimeWith {
DateTimeWith {
date_with: self.date_with.day_of_year_no_leap(day),
..self
}
}
#[inline]
pub fn hour(self, hour: i8) -> DateTimeWith {
DateTimeWith { time_with: self.time_with.hour(hour), ..self }
}
#[inline]
pub fn minute(self, minute: i8) -> DateTimeWith {
DateTimeWith { time_with: self.time_with.minute(minute), ..self }
}
#[inline]
pub fn second(self, second: i8) -> DateTimeWith {
DateTimeWith { time_with: self.time_with.second(second), ..self }
}
#[inline]
pub fn millisecond(self, millisecond: i16) -> DateTimeWith {
DateTimeWith {
time_with: self.time_with.millisecond(millisecond),
..self
}
}
#[inline]
pub fn microsecond(self, microsecond: i16) -> DateTimeWith {
DateTimeWith {
time_with: self.time_with.microsecond(microsecond),
..self
}
}
#[inline]
pub fn nanosecond(self, nanosecond: i16) -> DateTimeWith {
DateTimeWith {
time_with: self.time_with.nanosecond(nanosecond),
..self
}
}
#[inline]
pub fn subsec_nanosecond(self, subsec_nanosecond: i32) -> DateTimeWith {
DateTimeWith {
time_with: self.time_with.subsec_nanosecond(subsec_nanosecond),
..self
}
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use crate::{
civil::{date, time},
span::span_eq,
RoundMode, ToSpan, Unit,
};
use super::*;
#[test]
fn from_temporal_docs() {
let dt = DateTime::from_parts(
date(1995, 12, 7),
time(3, 24, 30, 000_003_500),
);
let got = dt.round(Unit::Hour).unwrap();
let expected =
DateTime::from_parts(date(1995, 12, 7), time(3, 0, 0, 0));
assert_eq!(got, expected);
let got = dt.round((Unit::Minute, 30)).unwrap();
let expected =
DateTime::from_parts(date(1995, 12, 7), time(3, 30, 0, 0));
assert_eq!(got, expected);
let got = dt
.round(
DateTimeRound::new()
.smallest(Unit::Minute)
.increment(30)
.mode(RoundMode::Floor),
)
.unwrap();
let expected =
DateTime::from_parts(date(1995, 12, 7), time(3, 0, 0, 0));
assert_eq!(got, expected);
}
#[test]
fn since() {
let later = date(2024, 5, 9).at(2, 0, 0, 0);
let earlier = date(2024, 5, 8).at(3, 0, 0, 0);
span_eq!(later.since(earlier).unwrap(), 23.hours());
let later = date(2024, 5, 9).at(3, 0, 0, 0);
let earlier = date(2024, 5, 8).at(2, 0, 0, 0);
span_eq!(later.since(earlier).unwrap(), 1.days().hours(1));
let later = date(2024, 5, 9).at(2, 0, 0, 0);
let earlier = date(2024, 5, 10).at(3, 0, 0, 0);
span_eq!(later.since(earlier).unwrap(), -1.days().hours(1));
let later = date(2024, 5, 9).at(3, 0, 0, 0);
let earlier = date(2024, 5, 10).at(2, 0, 0, 0);
span_eq!(later.since(earlier).unwrap(), -23.hours());
}
#[test]
fn until() {
let a = date(9999, 12, 30).at(3, 0, 0, 0);
let b = date(9999, 12, 31).at(2, 0, 0, 0);
span_eq!(a.until(b).unwrap(), 23.hours());
let a = date(-9999, 1, 2).at(2, 0, 0, 0);
let b = date(-9999, 1, 1).at(3, 0, 0, 0);
span_eq!(a.until(b).unwrap(), -23.hours());
let a = date(1995, 12, 7).at(3, 24, 30, 3500);
let b = date(2019, 1, 31).at(15, 30, 0, 0);
span_eq!(
a.until(b).unwrap(),
8456.days()
.hours(12)
.minutes(5)
.seconds(29)
.milliseconds(999)
.microseconds(996)
.nanoseconds(500)
);
span_eq!(
a.until((Unit::Year, b)).unwrap(),
23.years()
.months(1)
.days(24)
.hours(12)
.minutes(5)
.seconds(29)
.milliseconds(999)
.microseconds(996)
.nanoseconds(500)
);
span_eq!(
b.until((Unit::Year, a)).unwrap(),
-23.years()
.months(1)
.days(24)
.hours(12)
.minutes(5)
.seconds(29)
.milliseconds(999)
.microseconds(996)
.nanoseconds(500)
);
span_eq!(
a.until((Unit::Nanosecond, b)).unwrap(),
730641929999996500i64.nanoseconds(),
);
let a = date(-9999, 1, 1).at(0, 0, 0, 0);
let b = date(9999, 12, 31).at(23, 59, 59, 999_999_999);
assert!(a.until((Unit::Nanosecond, b)).is_err());
span_eq!(
a.until((Unit::Microsecond, b)).unwrap(),
Span::new()
.microseconds(631_107_417_600_000_000i64 - 1)
.nanoseconds(999),
);
}
#[test]
fn until_month_lengths() {
let jan1 = date(2020, 1, 1).at(0, 0, 0, 0);
let feb1 = date(2020, 2, 1).at(0, 0, 0, 0);
let mar1 = date(2020, 3, 1).at(0, 0, 0, 0);
span_eq!(jan1.until(feb1).unwrap(), 31.days());
span_eq!(jan1.until((Unit::Month, feb1)).unwrap(), 1.month());
span_eq!(feb1.until(mar1).unwrap(), 29.days());
span_eq!(feb1.until((Unit::Month, mar1)).unwrap(), 1.month());
span_eq!(jan1.until(mar1).unwrap(), 60.days());
span_eq!(jan1.until((Unit::Month, mar1)).unwrap(), 2.months());
}
#[test]
fn datetime_size() {
#[cfg(debug_assertions)]
{
assert_eq!(12, core::mem::size_of::<DateTime>());
}
#[cfg(not(debug_assertions))]
{
assert_eq!(12, core::mem::size_of::<DateTime>());
}
}
#[test]
fn civil_datetime_deserialize_yaml() {
let expected = datetime(2024, 10, 31, 16, 33, 53, 123456789);
let deserialized: DateTime =
serde_yaml::from_str("2024-10-31 16:33:53.123456789").unwrap();
assert_eq!(deserialized, expected);
let deserialized: DateTime =
serde_yaml::from_slice("2024-10-31 16:33:53.123456789".as_bytes())
.unwrap();
assert_eq!(deserialized, expected);
let cursor = Cursor::new(b"2024-10-31 16:33:53.123456789");
let deserialized: DateTime = serde_yaml::from_reader(cursor).unwrap();
assert_eq!(deserialized, expected);
}
}