use core::time::Duration as UnsignedDuration;
use crate::{
civil::{Date, DateTime},
duration::{Duration, SDuration},
error::{err, Error, ErrorContext},
fmt::{
self,
temporal::{self, DEFAULT_DATETIME_PARSER},
},
util::{
rangeint::{RFrom, RInto, TryRFrom},
round::increment,
t::{
self, CivilDayNanosecond, Hour, Microsecond, Millisecond, Minute,
Nanosecond, Second, SubsecNanosecond, C,
},
},
RoundMode, SignedDuration, Span, SpanRound, Unit, Zoned,
};
#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Time {
hour: Hour,
minute: Minute,
second: Second,
subsec_nanosecond: SubsecNanosecond,
}
impl Time {
pub const MIN: Time = Time::midnight();
pub const MAX: Time = Time::constant(23, 59, 59, 999_999_999);
#[inline]
pub fn new(
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
) -> Result<Time, Error> {
let hour = Hour::try_new("hour", hour)?;
let minute = Minute::try_new("minute", minute)?;
let second = Second::try_new("second", second)?;
let subsec_nanosecond =
SubsecNanosecond::try_new("subsec_nanosecond", subsec_nanosecond)?;
Ok(Time::new_ranged(hour, minute, second, subsec_nanosecond))
}
#[inline]
pub const fn constant(
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
) -> Time {
if !Hour::contains(hour) {
panic!("invalid hour");
}
if !Minute::contains(minute) {
panic!("invalid minute");
}
if !Second::contains(second) {
panic!("invalid second");
}
if !SubsecNanosecond::contains(subsec_nanosecond) {
panic!("invalid nanosecond");
}
let hour = Hour::new_unchecked(hour);
let minute = Minute::new_unchecked(minute);
let second = Second::new_unchecked(second);
let subsec_nanosecond =
SubsecNanosecond::new_unchecked(subsec_nanosecond);
Time { hour, minute, second, subsec_nanosecond }
}
#[inline]
pub const fn midnight() -> Time {
Time::constant(0, 0, 0, 0)
}
#[inline]
pub fn with(self) -> TimeWith {
TimeWith::new(self)
}
#[inline]
pub fn hour(self) -> i8 {
self.hour_ranged().get()
}
#[inline]
pub fn minute(self) -> i8 {
self.minute_ranged().get()
}
#[inline]
pub fn second(self) -> i8 {
self.second_ranged().get()
}
#[inline]
pub fn millisecond(self) -> i16 {
self.millisecond_ranged().get()
}
#[inline]
pub fn microsecond(self) -> i16 {
self.microsecond_ranged().get()
}
#[inline]
pub fn nanosecond(self) -> i16 {
self.nanosecond_ranged().get()
}
#[inline]
pub fn subsec_nanosecond(self) -> i32 {
self.subsec_nanosecond_ranged().get()
}
#[inline]
pub const fn to_datetime(self, date: Date) -> DateTime {
DateTime::from_parts(date, self)
}
#[inline]
pub const fn on(self, year: i16, month: i8, day: i8) -> DateTime {
DateTime::from_parts(Date::constant(year, month, day), self)
}
#[inline]
pub fn wrapping_add<A: Into<TimeArithmetic>>(self, duration: A) -> Time {
let duration: TimeArithmetic = duration.into();
duration.wrapping_add(self)
}
#[inline]
fn wrapping_add_span(self, span: Span) -> Time {
let mut sum = self.to_nanosecond().without_bounds();
sum = sum.wrapping_add(
span.get_hours_ranged()
.without_bounds()
.wrapping_mul(t::NANOS_PER_HOUR),
);
sum = sum.wrapping_add(
span.get_minutes_ranged()
.without_bounds()
.wrapping_mul(t::NANOS_PER_MINUTE),
);
sum = sum.wrapping_add(
span.get_seconds_ranged()
.without_bounds()
.wrapping_mul(t::NANOS_PER_SECOND),
);
sum = sum.wrapping_add(
span.get_milliseconds_ranged()
.without_bounds()
.wrapping_mul(t::NANOS_PER_MILLI),
);
sum = sum.wrapping_add(
span.get_microseconds_ranged()
.without_bounds()
.wrapping_mul(t::NANOS_PER_MICRO),
);
sum = sum.wrapping_add(span.get_nanoseconds_ranged().without_bounds());
let civil_day_nanosecond = sum % t::NANOS_PER_CIVIL_DAY;
Time::from_nanosecond(civil_day_nanosecond)
}
#[inline]
fn wrapping_add_signed_duration(self, duration: SignedDuration) -> Time {
let start = t::NoUnits128::rfrom(self.to_nanosecond());
let duration = t::NoUnits128::new_unchecked(duration.as_nanos());
let end = start.wrapping_add(duration) % t::NANOS_PER_CIVIL_DAY;
Time::from_nanosecond(end)
}
#[inline]
fn wrapping_add_unsigned_duration(
self,
duration: UnsignedDuration,
) -> Time {
let start = t::NoUnits128::rfrom(self.to_nanosecond());
let duration = i128::try_from(duration.as_nanos()).unwrap();
let duration = t::NoUnits128::new_unchecked(duration);
let duration = duration % t::NANOS_PER_CIVIL_DAY;
let end = start.wrapping_add(duration) % t::NANOS_PER_CIVIL_DAY;
Time::from_nanosecond(end)
}
#[inline]
pub fn wrapping_sub<A: Into<TimeArithmetic>>(self, duration: A) -> Time {
let duration: TimeArithmetic = duration.into();
duration.wrapping_sub(self)
}
#[inline]
fn wrapping_sub_unsigned_duration(
self,
duration: UnsignedDuration,
) -> Time {
let start = t::NoUnits128::rfrom(self.to_nanosecond());
let duration = i128::try_from(duration.as_nanos()).unwrap();
let duration = t::NoUnits128::new_unchecked(duration);
let end = start.wrapping_sub(duration) % t::NANOS_PER_CIVIL_DAY;
Time::from_nanosecond(end)
}
#[inline]
pub fn checked_add<A: Into<TimeArithmetic>>(
self,
duration: A,
) -> Result<Time, Error> {
let duration: TimeArithmetic = duration.into();
duration.checked_add(self)
}
#[inline]
fn checked_add_span(self, span: Span) -> Result<Time, Error> {
let (time, span) = self.overflowing_add(span)?;
if let Some(err) = span.smallest_non_time_non_zero_unit_error() {
return Err(err);
}
Ok(time)
}
#[inline]
fn checked_add_duration(
self,
duration: SignedDuration,
) -> Result<Time, Error> {
let original = duration;
let start = t::NoUnits128::rfrom(self.to_nanosecond());
let duration = t::NoUnits128::new_unchecked(duration.as_nanos());
let end = start.try_checked_add("nanoseconds", duration).unwrap();
let end = CivilDayNanosecond::try_rfrom("nanoseconds", end)
.with_context(|| {
err!(
"adding signed duration {duration:?}, equal to
{nanos} nanoseconds, to {time} overflowed",
duration = original,
nanos = original.as_nanos(),
time = self,
)
})?;
Ok(Time::from_nanosecond(end))
}
#[inline]
pub fn checked_sub<A: Into<TimeArithmetic>>(
self,
duration: A,
) -> Result<Time, Error> {
let duration: TimeArithmetic = duration.into();
duration.checked_neg().and_then(|ta| ta.checked_add(self))
}
#[inline]
pub fn saturating_add<A: Into<TimeArithmetic>>(self, duration: A) -> Time {
let duration: TimeArithmetic = duration.into();
self.checked_add(duration).unwrap_or_else(|_| {
if duration.is_negative() {
Time::MIN
} else {
Time::MAX
}
})
}
#[inline]
pub fn saturating_sub<A: Into<TimeArithmetic>>(self, duration: A) -> Time {
let duration: TimeArithmetic = duration.into();
let Ok(duration) = duration.checked_neg() else { return Time::MIN };
self.saturating_add(duration)
}
#[inline]
pub(crate) fn overflowing_add(
self,
span: Span,
) -> Result<(Time, Span), Error> {
if let Some(err) = span.smallest_non_time_non_zero_unit_error() {
return Err(err);
}
let span_nanos = span.to_invariant_nanoseconds();
let time_nanos = self.to_nanosecond();
let sum = span_nanos + time_nanos;
let days = t::SpanDays::try_new(
"overflowing-days",
sum.div_floor(t::NANOS_PER_CIVIL_DAY),
)?;
let time_nanos = sum.rem_floor(t::NANOS_PER_CIVIL_DAY);
let time = Time::from_nanosecond(time_nanos);
Ok((time, Span::new().days_ranged(days)))
}
#[inline]
pub(crate) fn overflowing_add_duration(
self,
duration: SignedDuration,
) -> Result<(Time, SignedDuration), Error> {
let start = t::NoUnits128::rfrom(self.to_nanosecond());
let duration = t::NoUnits96::new_unchecked(duration.as_nanos());
let sum = start.try_checked_add("nanoseconds", duration).unwrap();
let days = t::SpanDays::try_new(
"overflowing-days",
sum.div_floor(t::NANOS_PER_CIVIL_DAY),
)?;
let time_nanos = sum.rem_floor(t::NANOS_PER_CIVIL_DAY);
let time = Time::from_nanosecond(time_nanos);
let hours = i64::from(days).checked_mul(24).unwrap();
Ok((time, SignedDuration::from_hours(hours)))
}
#[inline]
pub fn until<A: Into<TimeDifference>>(
self,
other: A,
) -> Result<Span, Error> {
let args: TimeDifference = other.into();
let span = args.until_with_largest_unit(self)?;
if args.rounding_may_change_span() {
span.round(args.round)
} else {
Ok(span)
}
}
#[inline]
pub fn since<A: Into<TimeDifference>>(
self,
other: A,
) -> Result<Span, Error> {
let args: TimeDifference = other.into();
let span = -args.until_with_largest_unit(self)?;
if args.rounding_may_change_span() {
span.round(args.round)
} else {
Ok(span)
}
}
#[inline]
pub fn duration_until(self, other: Time) -> SignedDuration {
SignedDuration::time_until(self, other)
}
#[inline]
pub fn duration_since(self, other: Time) -> SignedDuration {
SignedDuration::time_until(other, self)
}
#[inline]
pub fn round<R: Into<TimeRound>>(self, options: R) -> Result<Time, Error> {
let options: TimeRound = options.into();
options.round(self)
}
#[inline]
pub fn series(self, period: Span) -> TimeSeries {
TimeSeries { start: self, period, step: 0 }
}
}
impl Time {
#[inline]
pub fn strptime(
format: impl AsRef<[u8]>,
input: impl AsRef<[u8]>,
) -> Result<Time, Error> {
fmt::strtime::parse(format, input).and_then(|tm| tm.to_time())
}
#[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 Time {
#[inline]
pub(crate) fn new_ranged(
hour: impl RInto<Hour>,
minute: impl RInto<Minute>,
second: impl RInto<Second>,
subsec_nanosecond: impl RInto<SubsecNanosecond>,
) -> Time {
Time {
hour: hour.rinto(),
minute: minute.rinto(),
second: second.rinto(),
subsec_nanosecond: subsec_nanosecond.rinto(),
}
}
#[inline]
fn with_subsec_parts_ranged(
self,
millisecond: impl RInto<Millisecond>,
microsecond: impl RInto<Microsecond>,
nanosecond: impl RInto<Nanosecond>,
) -> Time {
let millisecond = SubsecNanosecond::rfrom(millisecond.rinto());
let microsecond = SubsecNanosecond::rfrom(microsecond.rinto());
let nanosecond = SubsecNanosecond::rfrom(nanosecond.rinto());
let mut subsec_nanosecond =
millisecond * t::MICROS_PER_MILLI * t::NANOS_PER_MICRO;
subsec_nanosecond += microsecond * t::NANOS_PER_MICRO;
subsec_nanosecond += nanosecond;
Time { subsec_nanosecond: subsec_nanosecond.rinto(), ..self }
}
#[inline]
pub(crate) fn hour_ranged(self) -> Hour {
self.hour
}
#[inline]
pub(crate) fn minute_ranged(self) -> Minute {
self.minute
}
#[inline]
pub(crate) fn second_ranged(self) -> Second {
self.second
}
#[inline]
pub(crate) fn millisecond_ranged(self) -> Millisecond {
let micros = self.subsec_nanosecond_ranged() / t::NANOS_PER_MICRO;
let millis = micros / t::MICROS_PER_MILLI;
millis.rinto()
}
#[inline]
pub(crate) fn microsecond_ranged(self) -> Microsecond {
let micros = self.subsec_nanosecond_ranged() / t::NANOS_PER_MICRO;
let only_micros = micros % t::MICROS_PER_MILLI;
only_micros.rinto()
}
#[inline]
pub(crate) fn nanosecond_ranged(self) -> Nanosecond {
let only_nanos = self.subsec_nanosecond_ranged() % t::NANOS_PER_MICRO;
only_nanos.rinto()
}
#[inline]
pub(crate) fn subsec_nanosecond_ranged(self) -> SubsecNanosecond {
self.subsec_nanosecond
}
#[inline]
pub(crate) fn until_nanoseconds(self, other: Time) -> t::SpanNanoseconds {
let t1 = t::SpanNanoseconds::rfrom(self.to_nanosecond());
let t2 = t::SpanNanoseconds::rfrom(other.to_nanosecond());
t2 - t1
}
#[inline]
pub(crate) fn to_nanosecond(&self) -> CivilDayNanosecond {
let mut civil_day_nanosecond =
CivilDayNanosecond::rfrom(self.hour_ranged()) * t::NANOS_PER_HOUR;
civil_day_nanosecond +=
CivilDayNanosecond::rfrom(self.minute_ranged())
* t::NANOS_PER_MINUTE;
civil_day_nanosecond +=
CivilDayNanosecond::rfrom(self.second_ranged())
* t::NANOS_PER_SECOND;
civil_day_nanosecond +=
CivilDayNanosecond::rfrom(self.subsec_nanosecond_ranged());
civil_day_nanosecond
}
#[inline]
pub(crate) fn from_nanosecond(
nanosecond: impl RInto<CivilDayNanosecond>,
) -> Time {
let nanosecond = nanosecond.rinto();
let hour = nanosecond / t::NANOS_PER_HOUR;
let minute = (nanosecond % t::NANOS_PER_HOUR) / t::NANOS_PER_MINUTE;
let second = (nanosecond % t::NANOS_PER_MINUTE) / t::NANOS_PER_SECOND;
let subsec_nanosecond = nanosecond % t::NANOS_PER_SECOND;
Time::new_ranged(hour, minute, second, subsec_nanosecond)
}
}
impl Default for Time {
#[inline]
fn default() -> Time {
Time::midnight()
}
}
impl core::fmt::Debug for Time {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Display::fmt(self, f)
}
}
impl core::fmt::Display for Time {
#[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_time(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
}
}
impl core::str::FromStr for Time {
type Err = Error;
#[inline]
fn from_str(string: &str) -> Result<Time, Error> {
DEFAULT_DATETIME_PARSER.parse_time(string)
}
}
impl core::ops::Add<Span> for Time {
type Output = Time;
#[inline]
fn add(self, rhs: Span) -> Time {
self.wrapping_add(rhs)
}
}
impl core::ops::AddAssign<Span> for Time {
#[inline]
fn add_assign(&mut self, rhs: Span) {
*self = *self + rhs;
}
}
impl core::ops::Sub<Span> for Time {
type Output = Time;
#[inline]
fn sub(self, rhs: Span) -> Time {
self.wrapping_sub(rhs)
}
}
impl core::ops::SubAssign<Span> for Time {
#[inline]
fn sub_assign(&mut self, rhs: Span) {
*self = *self - rhs;
}
}
impl core::ops::Sub for Time {
type Output = Span;
#[inline]
fn sub(self, rhs: Time) -> Span {
self.since(rhs).expect("since never fails when given Time")
}
}
impl core::ops::Add<SignedDuration> for Time {
type Output = Time;
#[inline]
fn add(self, rhs: SignedDuration) -> Time {
self.wrapping_add(rhs)
}
}
impl core::ops::AddAssign<SignedDuration> for Time {
#[inline]
fn add_assign(&mut self, rhs: SignedDuration) {
*self = *self + rhs;
}
}
impl core::ops::Sub<SignedDuration> for Time {
type Output = Time;
#[inline]
fn sub(self, rhs: SignedDuration) -> Time {
self.wrapping_sub(rhs)
}
}
impl core::ops::SubAssign<SignedDuration> for Time {
#[inline]
fn sub_assign(&mut self, rhs: SignedDuration) {
*self = *self - rhs;
}
}
impl core::ops::Add<UnsignedDuration> for Time {
type Output = Time;
#[inline]
fn add(self, rhs: UnsignedDuration) -> Time {
self.wrapping_add(rhs)
}
}
impl core::ops::AddAssign<UnsignedDuration> for Time {
#[inline]
fn add_assign(&mut self, rhs: UnsignedDuration) {
*self = *self + rhs;
}
}
impl core::ops::Sub<UnsignedDuration> for Time {
type Output = Time;
#[inline]
fn sub(self, rhs: UnsignedDuration) -> Time {
self.wrapping_sub(rhs)
}
}
impl core::ops::SubAssign<UnsignedDuration> for Time {
#[inline]
fn sub_assign(&mut self, rhs: UnsignedDuration) {
*self = *self - rhs;
}
}
impl From<DateTime> for Time {
#[inline]
fn from(dt: DateTime) -> Time {
dt.time()
}
}
impl From<Zoned> for Time {
#[inline]
fn from(zdt: Zoned) -> Time {
zdt.datetime().time()
}
}
impl<'a> From<&'a Zoned> for Time {
#[inline]
fn from(zdt: &'a Zoned) -> Time {
zdt.datetime().time()
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Time {
#[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 Time {
#[inline]
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<Time, D::Error> {
use serde::de;
struct TimeVisitor;
impl<'de> de::Visitor<'de> for TimeVisitor {
type Value = Time;
fn expecting(
&self,
f: &mut core::fmt::Formatter,
) -> core::fmt::Result {
f.write_str("a time string")
}
#[inline]
fn visit_bytes<E: de::Error>(
self,
value: &[u8],
) -> Result<Time, E> {
DEFAULT_DATETIME_PARSER
.parse_time(value)
.map_err(de::Error::custom)
}
#[inline]
fn visit_str<E: de::Error>(self, value: &str) -> Result<Time, E> {
self.visit_bytes(value.as_bytes())
}
}
deserializer.deserialize_bytes(TimeVisitor)
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Time {
fn arbitrary(g: &mut quickcheck::Gen) -> Time {
let hour = Hour::arbitrary(g);
let minute = Minute::arbitrary(g);
let second = Second::arbitrary(g);
let subsec_nanosecond = SubsecNanosecond::arbitrary(g);
Time::new_ranged(hour, minute, second, subsec_nanosecond)
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Time>> {
alloc::boxed::Box::new(
(
self.hour_ranged(),
self.minute_ranged(),
self.second_ranged(),
self.subsec_nanosecond_ranged(),
)
.shrink()
.map(
|(hour, minute, second, subsec_nanosecond)| {
Time::new_ranged(
hour,
minute,
second,
subsec_nanosecond,
)
},
),
)
}
}
#[derive(Clone, Debug)]
pub struct TimeSeries {
start: Time,
period: Span,
step: i64,
}
impl Iterator for TimeSeries {
type Item = Time;
#[inline]
fn next(&mut self) -> Option<Time> {
let span = self.period.checked_mul(self.step).ok()?;
self.step = self.step.checked_add(1)?;
let time = self.start.checked_add(span).ok()?;
Some(time)
}
}
#[derive(Clone, Copy, Debug)]
pub struct TimeArithmetic {
duration: Duration,
}
impl TimeArithmetic {
#[inline]
fn wrapping_add(self, time: Time) -> Time {
match self.duration {
Duration::Span(span) => time.wrapping_add_span(span),
Duration::Signed(sdur) => time.wrapping_add_signed_duration(sdur),
Duration::Unsigned(udur) => {
time.wrapping_add_unsigned_duration(udur)
}
}
}
#[inline]
fn wrapping_sub(self, time: Time) -> Time {
match self.duration {
Duration::Span(span) => time.wrapping_add_span(span.negate()),
Duration::Signed(sdur) => {
if let Some(sdur) = sdur.checked_neg() {
time.wrapping_add_signed_duration(sdur)
} else {
let udur = UnsignedDuration::new(
i64::MIN.unsigned_abs(),
sdur.subsec_nanos().unsigned_abs(),
);
time.wrapping_add_unsigned_duration(udur)
}
}
Duration::Unsigned(udur) => {
time.wrapping_sub_unsigned_duration(udur)
}
}
}
#[inline]
fn checked_add(self, time: Time) -> Result<Time, Error> {
match self.duration.to_signed()? {
SDuration::Span(span) => time.checked_add_span(span),
SDuration::Absolute(sdur) => time.checked_add_duration(sdur),
}
}
#[inline]
fn checked_neg(self) -> Result<TimeArithmetic, Error> {
let duration = self.duration.checked_neg()?;
Ok(TimeArithmetic { duration })
}
#[inline]
fn is_negative(&self) -> bool {
self.duration.is_negative()
}
}
impl From<Span> for TimeArithmetic {
fn from(span: Span) -> TimeArithmetic {
let duration = Duration::from(span);
TimeArithmetic { duration }
}
}
impl From<SignedDuration> for TimeArithmetic {
fn from(sdur: SignedDuration) -> TimeArithmetic {
let duration = Duration::from(sdur);
TimeArithmetic { duration }
}
}
impl From<UnsignedDuration> for TimeArithmetic {
fn from(udur: UnsignedDuration) -> TimeArithmetic {
let duration = Duration::from(udur);
TimeArithmetic { duration }
}
}
impl<'a> From<&'a Span> for TimeArithmetic {
fn from(span: &'a Span) -> TimeArithmetic {
TimeArithmetic::from(*span)
}
}
impl<'a> From<&'a SignedDuration> for TimeArithmetic {
fn from(sdur: &'a SignedDuration) -> TimeArithmetic {
TimeArithmetic::from(*sdur)
}
}
impl<'a> From<&'a UnsignedDuration> for TimeArithmetic {
fn from(udur: &'a UnsignedDuration) -> TimeArithmetic {
TimeArithmetic::from(*udur)
}
}
#[derive(Clone, Copy, Debug)]
pub struct TimeDifference {
time: Time,
round: SpanRound<'static>,
}
impl TimeDifference {
#[inline]
pub fn new(time: Time) -> TimeDifference {
let round = SpanRound::new().mode(RoundMode::Trunc);
TimeDifference { time, round }
}
#[inline]
pub fn smallest(self, unit: Unit) -> TimeDifference {
TimeDifference { round: self.round.smallest(unit), ..self }
}
#[inline]
pub fn largest(self, unit: Unit) -> TimeDifference {
TimeDifference { round: self.round.largest(unit), ..self }
}
#[inline]
pub fn mode(self, mode: RoundMode) -> TimeDifference {
TimeDifference { round: self.round.mode(mode), ..self }
}
#[inline]
pub fn increment(self, increment: i64) -> TimeDifference {
TimeDifference { 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, t1: Time) -> Result<Span, Error> {
let t2 = self.time;
if t1 == t2 {
return Ok(Span::new());
}
let largest = self.round.get_largest().unwrap_or(Unit::Hour);
if largest > Unit::Hour {
return Err(err!(
"rounding the span between two times must use hours \
or smaller for its units, but found {units}",
units = largest.plural(),
));
}
let start = t1.to_nanosecond();
let end = t2.to_nanosecond();
let span = Span::from_invariant_nanoseconds(largest, end - start)
.expect("difference in civil times is always in bounds");
Ok(span)
}
}
impl From<Time> for TimeDifference {
#[inline]
fn from(time: Time) -> TimeDifference {
TimeDifference::new(time)
}
}
impl From<DateTime> for TimeDifference {
#[inline]
fn from(dt: DateTime) -> TimeDifference {
TimeDifference::from(Time::from(dt))
}
}
impl From<Zoned> for TimeDifference {
#[inline]
fn from(zdt: Zoned) -> TimeDifference {
TimeDifference::from(Time::from(zdt))
}
}
impl<'a> From<&'a Zoned> for TimeDifference {
#[inline]
fn from(zdt: &'a Zoned) -> TimeDifference {
TimeDifference::from(zdt.datetime())
}
}
impl From<(Unit, Time)> for TimeDifference {
#[inline]
fn from((largest, time): (Unit, Time)) -> TimeDifference {
TimeDifference::from(time).largest(largest)
}
}
impl From<(Unit, DateTime)> for TimeDifference {
#[inline]
fn from((largest, dt): (Unit, DateTime)) -> TimeDifference {
TimeDifference::from((largest, Time::from(dt)))
}
}
impl From<(Unit, Zoned)> for TimeDifference {
#[inline]
fn from((largest, zdt): (Unit, Zoned)) -> TimeDifference {
TimeDifference::from((largest, Time::from(zdt)))
}
}
impl<'a> From<(Unit, &'a Zoned)> for TimeDifference {
#[inline]
fn from((largest, zdt): (Unit, &'a Zoned)) -> TimeDifference {
TimeDifference::from((largest, zdt.datetime()))
}
}
#[derive(Clone, Copy, Debug)]
pub struct TimeRound {
smallest: Unit,
mode: RoundMode,
increment: i64,
}
impl TimeRound {
#[inline]
pub fn new() -> TimeRound {
TimeRound {
smallest: Unit::Nanosecond,
mode: RoundMode::HalfExpand,
increment: 1,
}
}
#[inline]
pub fn smallest(self, unit: Unit) -> TimeRound {
TimeRound { smallest: unit, ..self }
}
#[inline]
pub fn mode(self, mode: RoundMode) -> TimeRound {
TimeRound { mode, ..self }
}
#[inline]
pub fn increment(self, increment: i64) -> TimeRound {
TimeRound { increment, ..self }
}
pub(crate) fn round(&self, t: Time) -> Result<Time, Error> {
let increment = increment::for_time(self.smallest, self.increment)?;
let nanos = t.to_nanosecond();
let rounded = self.mode.round_by_unit_in_nanoseconds(
nanos,
self.smallest,
increment,
);
let limit =
t::NoUnits128::rfrom(t::CivilDayNanosecond::MAX_SELF) + C(1);
Ok(Time::from_nanosecond(rounded % limit))
}
}
impl Default for TimeRound {
#[inline]
fn default() -> TimeRound {
TimeRound::new()
}
}
impl From<Unit> for TimeRound {
#[inline]
fn from(unit: Unit) -> TimeRound {
TimeRound::default().smallest(unit)
}
}
impl From<(Unit, i64)> for TimeRound {
#[inline]
fn from((unit, increment): (Unit, i64)) -> TimeRound {
TimeRound::from(unit).increment(increment)
}
}
#[derive(Clone, Copy, Debug)]
pub struct TimeWith {
original: Time,
hour: Option<i8>,
minute: Option<i8>,
second: Option<i8>,
millisecond: Option<i16>,
microsecond: Option<i16>,
nanosecond: Option<i16>,
subsec_nanosecond: Option<i32>,
}
impl TimeWith {
#[inline]
fn new(original: Time) -> TimeWith {
TimeWith {
original,
hour: None,
minute: None,
second: None,
millisecond: None,
microsecond: None,
nanosecond: None,
subsec_nanosecond: None,
}
}
#[inline]
pub fn build(self) -> Result<Time, Error> {
let hour = match self.hour {
None => self.original.hour_ranged(),
Some(hour) => Hour::try_new("hour", hour)?,
};
let minute = match self.minute {
None => self.original.minute_ranged(),
Some(minute) => Minute::try_new("minute", minute)?,
};
let second = match self.second {
None => self.original.second_ranged(),
Some(second) => Second::try_new("second", second)?,
};
let millisecond = match self.millisecond {
None => self.original.millisecond_ranged(),
Some(millisecond) => {
Millisecond::try_new("millisecond", millisecond)?
}
};
let microsecond = match self.microsecond {
None => self.original.microsecond_ranged(),
Some(microsecond) => {
Microsecond::try_new("microsecond", microsecond)?
}
};
let nanosecond = match self.nanosecond {
None => self.original.nanosecond_ranged(),
Some(nanosecond) => Nanosecond::try_new("nanosecond", nanosecond)?,
};
let subsec_nanosecond = match self.subsec_nanosecond {
None => self.original.subsec_nanosecond_ranged(),
Some(subsec_nanosecond) => {
if self.millisecond.is_some() {
return Err(err!(
"cannot set both TimeWith::millisecond \
and TimeWith::subsec_nanosecond",
));
}
if self.microsecond.is_some() {
return Err(err!(
"cannot set both TimeWith::microsecond \
and TimeWith::subsec_nanosecond",
));
}
if self.nanosecond.is_some() {
return Err(err!(
"cannot set both TimeWith::nanosecond \
and TimeWith::subsec_nanosecond",
));
}
SubsecNanosecond::try_new(
"subsec_nanosecond",
subsec_nanosecond,
)?
}
};
if self.subsec_nanosecond.is_some() {
Ok(Time::new_ranged(hour, minute, second, subsec_nanosecond))
} else {
Ok(Time::new_ranged(hour, minute, second, C(0))
.with_subsec_parts_ranged(
millisecond,
microsecond,
nanosecond,
))
}
}
#[inline]
pub fn hour(self, hour: i8) -> TimeWith {
TimeWith { hour: Some(hour), ..self }
}
#[inline]
pub fn minute(self, minute: i8) -> TimeWith {
TimeWith { minute: Some(minute), ..self }
}
#[inline]
pub fn second(self, second: i8) -> TimeWith {
TimeWith { second: Some(second), ..self }
}
#[inline]
pub fn millisecond(self, millisecond: i16) -> TimeWith {
TimeWith { millisecond: Some(millisecond), ..self }
}
#[inline]
pub fn microsecond(self, microsecond: i16) -> TimeWith {
TimeWith { microsecond: Some(microsecond), ..self }
}
#[inline]
pub fn nanosecond(self, nanosecond: i16) -> TimeWith {
TimeWith { nanosecond: Some(nanosecond), ..self }
}
#[inline]
pub fn subsec_nanosecond(self, subsec_nanosecond: i32) -> TimeWith {
TimeWith { subsec_nanosecond: Some(subsec_nanosecond), ..self }
}
}
#[cfg(test)]
mod tests {
use crate::{civil::time, ToSpan};
use super::*;
#[test]
fn min() {
let t = Time::MIN;
assert_eq!(t.hour(), 0);
assert_eq!(t.minute(), 0);
assert_eq!(t.second(), 0);
assert_eq!(t.subsec_nanosecond(), 0);
}
#[test]
fn max() {
let t = Time::MAX;
assert_eq!(t.hour(), 23);
assert_eq!(t.minute(), 59);
assert_eq!(t.second(), 59);
assert_eq!(t.subsec_nanosecond(), 999_999_999);
}
#[test]
fn invalid() {
assert!(Time::new(24, 0, 0, 0).is_err());
assert!(Time::new(23, 60, 0, 0).is_err());
assert!(Time::new(23, 59, 60, 0).is_err());
assert!(Time::new(23, 59, 61, 0).is_err());
assert!(Time::new(-1, 0, 0, 0).is_err());
assert!(Time::new(0, -1, 0, 0).is_err());
assert!(Time::new(0, 0, -1, 0).is_err());
assert!(Time::new(0, 0, 0, 1_000_000_000).is_err());
assert!(Time::new(0, 0, 0, -1).is_err());
assert!(Time::new(23, 59, 59, 1_000_000_000).is_err());
assert!(Time::new(23, 59, 59, -1).is_err());
}
#[test]
fn rounding_cross_midnight() {
let t1 = time(23, 59, 59, 999_999_999);
let t2 = t1.round(Unit::Nanosecond).unwrap();
assert_eq!(t2, t1);
let t2 = t1.round(Unit::Millisecond).unwrap();
assert_eq!(t2, time(0, 0, 0, 0));
let t2 = t1.round(Unit::Microsecond).unwrap();
assert_eq!(t2, time(0, 0, 0, 0));
let t2 = t1.round(Unit::Millisecond).unwrap();
assert_eq!(t2, time(0, 0, 0, 0));
let t2 = t1.round(Unit::Second).unwrap();
assert_eq!(t2, time(0, 0, 0, 0));
let t2 = t1.round(Unit::Minute).unwrap();
assert_eq!(t2, time(0, 0, 0, 0));
let t2 = t1.round(Unit::Hour).unwrap();
assert_eq!(t2, time(0, 0, 0, 0));
let t1 = time(22, 15, 0, 0);
assert_eq!(
time(22, 30, 0, 0),
t1.round(TimeRound::new().smallest(Unit::Minute).increment(30))
.unwrap()
);
}
quickcheck::quickcheck! {
fn prop_ordering_same_as_civil_nanosecond(
civil_nanosecond1: CivilDayNanosecond,
civil_nanosecond2: CivilDayNanosecond
) -> bool {
let t1 = Time::from_nanosecond(civil_nanosecond1);
let t2 = Time::from_nanosecond(civil_nanosecond2);
t1.cmp(&t2) == civil_nanosecond1.cmp(&civil_nanosecond2)
}
fn prop_checked_add_then_sub(
time: Time,
nano_span: CivilDayNanosecond
) -> quickcheck::TestResult {
let span = Span::new().nanoseconds(nano_span.get());
let Ok(sum) = time.checked_add(span) else {
return quickcheck::TestResult::discard()
};
let diff = sum.checked_sub(span).unwrap();
quickcheck::TestResult::from_bool(time == diff)
}
fn prop_wrapping_add_then_sub(
time: Time,
nano_span: CivilDayNanosecond
) -> bool {
let span = Span::new().nanoseconds(nano_span.get());
let sum = time.wrapping_add(span);
let diff = sum.wrapping_sub(span);
time == diff
}
fn prop_checked_add_equals_wrapping_add(
time: Time,
nano_span: CivilDayNanosecond
) -> quickcheck::TestResult {
let span = Span::new().nanoseconds(nano_span.get());
let Ok(sum_checked) = time.checked_add(span) else {
return quickcheck::TestResult::discard()
};
let sum_wrapped = time.wrapping_add(span);
quickcheck::TestResult::from_bool(sum_checked == sum_wrapped)
}
fn prop_checked_sub_equals_wrapping_sub(
time: Time,
nano_span: CivilDayNanosecond
) -> quickcheck::TestResult {
let span = Span::new().nanoseconds(nano_span.get());
let Ok(diff_checked) = time.checked_sub(span) else {
return quickcheck::TestResult::discard()
};
let diff_wrapped = time.wrapping_sub(span);
quickcheck::TestResult::from_bool(diff_checked == diff_wrapped)
}
fn prop_until_then_add(t1: Time, t2: Time) -> bool {
let span = t1.until(t2).unwrap();
t1.checked_add(span).unwrap() == t2
}
fn prop_until_then_sub(t1: Time, t2: Time) -> bool {
let span = t1.until(t2).unwrap();
t2.checked_sub(span).unwrap() == t1
}
fn prop_since_then_add(t1: Time, t2: Time) -> bool {
let span = t1.since(t2).unwrap();
t2.checked_add(span).unwrap() == t1
}
fn prop_since_then_sub(t1: Time, t2: Time) -> bool {
let span = t1.since(t2).unwrap();
t1.checked_sub(span).unwrap() == t2
}
fn prop_until_is_since_negated(t1: Time, t2: Time) -> bool {
t1.until(t2).unwrap().get_nanoseconds()
== t1.since(t2).unwrap().negate().get_nanoseconds()
}
}
#[test]
fn overflowing_add() {
let t1 = time(23, 30, 0, 0);
let (t2, span) = t1.overflowing_add(5.hours()).unwrap();
assert_eq!(t2, time(4, 30, 0, 0));
assert_eq!(span, 1.days());
}
#[test]
fn overflowing_add_overflows() {
let t1 = time(23, 30, 0, 0);
let span = Span::new()
.hours(t::SpanHours::MAX_REPR)
.minutes(t::SpanMinutes::MAX_REPR)
.seconds(t::SpanSeconds::MAX_REPR)
.milliseconds(t::SpanMilliseconds::MAX_REPR)
.microseconds(t::SpanMicroseconds::MAX_REPR)
.nanoseconds(t::SpanNanoseconds::MAX_REPR);
assert!(t1.overflowing_add(span).is_err());
}
#[test]
fn time_size() {
#[cfg(debug_assertions)]
{
assert_eq!(24, core::mem::size_of::<Time>());
}
#[cfg(not(debug_assertions))]
{
assert_eq!(8, core::mem::size_of::<Time>());
}
}
#[test]
fn wrapping_sub_signed_duration_min() {
let max = -SignedDuration::MIN.as_nanos();
let got = time(15, 30, 8, 999_999_999).to_nanosecond();
let expected = max.rem_euclid(t::NANOS_PER_CIVIL_DAY.bound());
assert_eq!(got, expected);
}
#[test]
fn wrapping_sub_signed_duration_max() {
let max = -SignedDuration::MAX.as_nanos();
let got = time(8, 29, 52, 1).to_nanosecond();
let expected = max.rem_euclid(t::NANOS_PER_CIVIL_DAY.bound());
assert_eq!(got, expected);
}
#[test]
fn wrapping_sub_unsigned_duration_max() {
let max =
-i128::try_from(std::time::Duration::MAX.as_nanos()).unwrap();
let got = time(16, 59, 44, 1).to_nanosecond();
let expected = max.rem_euclid(t::NANOS_PER_CIVIL_DAY.bound());
assert_eq!(got, expected);
}
}