use core::time::Duration as UnsignedDuration;
use crate::{
civil::{Date, DateTime},
duration::{Duration, SDuration},
error::{civil::Error as E, unit::UnitConfigError, Error},
fmt::{
self,
temporal::{self, DEFAULT_DATETIME_PARSER},
},
shared::util::itime::{ITime, ITimeNanosecond, ITimeSecond},
util::{b, constant, round::Increment},
RoundMode, SignedDuration, Span, SpanRound, Unit, Zoned,
};
#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Time {
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
}
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 = b::Hour::check(hour)?;
let minute = b::Minute::check(minute)?;
let second = b::Second::check(second)?;
let subsec_nanosecond = b::SubsecNanosecond::check(subsec_nanosecond)?;
Ok(Time::new_unchecked(hour, minute, second, subsec_nanosecond))
}
#[inline]
const fn new_unchecked(
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
) -> Time {
Time { hour, minute, second, subsec_nanosecond }
}
#[inline]
pub const fn constant(
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
) -> Time {
let hour =
constant::unwrapr!(b::Hour::checkc(hour as i64), "invalid hour",);
let minute = constant::unwrapr!(
b::Minute::checkc(minute as i64),
"invalid minute",
);
let second = constant::unwrapr!(
b::Second::checkc(second as i64),
"invalid second",
);
let subsec = constant::unwrapr!(
b::SubsecNanosecond::checkc(subsec_nanosecond as i64),
"invalid nanosecond",
);
Time::new_unchecked(hour, minute, second, subsec)
}
#[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
}
#[inline]
pub fn minute(self) -> i8 {
self.minute
}
#[inline]
pub fn second(self) -> i8 {
self.second
}
#[inline]
pub fn millisecond(self) -> i16 {
(self.subsec_nanosecond() / b::NANOS_PER_MILLI_32) as i16
}
#[inline]
pub fn microsecond(self) -> i16 {
((self.subsec_nanosecond() / b::NANOS_PER_MICRO_32)
% b::MICROS_PER_MILLI_32) as i16
}
#[inline]
pub fn nanosecond(self) -> i16 {
(self.subsec_nanosecond() % b::NANOS_PER_MICRO_32) as i16
}
#[inline]
pub fn subsec_nanosecond(self) -> i32 {
self.subsec_nanosecond
}
#[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 sum = self
.to_nanosecond()
.wrapping_add(
i64::from(span.get_hours()).wrapping_mul(b::NANOS_PER_HOUR),
)
.wrapping_add(span.get_minutes().wrapping_mul(b::NANOS_PER_MIN))
.wrapping_add(span.get_seconds().wrapping_mul(b::NANOS_PER_SEC))
.wrapping_add(
span.get_milliseconds().wrapping_mul(b::NANOS_PER_MILLI),
)
.wrapping_add(
span.get_microseconds().wrapping_mul(b::NANOS_PER_MICRO),
)
.wrapping_add(span.get_nanoseconds());
let civil_day_nanosecond = sum.rem_euclid(b::NANOS_PER_CIVIL_DAY);
Time::from_nanosecond_unchecked(civil_day_nanosecond)
}
#[inline]
fn wrapping_add_signed_duration(self, duration: SignedDuration) -> Time {
let start = i128::from(self.to_nanosecond());
let duration = duration.as_nanos();
let end = start
.wrapping_add(duration)
.rem_euclid(b::NANOS_PER_CIVIL_DAY as i128)
as i64;
Time::from_nanosecond_unchecked(end)
}
#[inline]
fn wrapping_add_unsigned_duration(
self,
duration: UnsignedDuration,
) -> Time {
let start = i128::from(self.to_nanosecond());
let duration = i128::try_from(duration.as_nanos()).unwrap();
let end = (start.wrapping_add(duration)
% (b::NANOS_PER_CIVIL_DAY as i128)) as i64;
Time::from_nanosecond_unchecked(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 = self.to_nanosecond();
let duration = i128::try_from(duration.as_nanos()).unwrap();
let duration = (duration % b::NANOS_PER_CIVIL_DAY as i128) as i64;
let end =
start.wrapping_sub(duration).rem_euclid(b::NANOS_PER_CIVIL_DAY);
Time::from_nanosecond_unchecked(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 span.smallest_non_time_non_zero_unit_error().is_some() {
return Err(Error::from(E::OverflowTimeNanoseconds));
}
Ok(time)
}
#[inline]
fn checked_add_duration(
self,
duration: SignedDuration,
) -> Result<Time, Error> {
let start = self.to_nanosecond();
let duration =
duration.as_nanos64().ok_or(E::OverflowTimeNanoseconds)?;
let end =
start.checked_add(duration).ok_or(E::OverflowTimeNanoseconds)?;
let end = b::CivilDayNanosecond::check(end)?;
Ok(Time::from_nanosecond_unchecked(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 = span.to_invariant_duration().as_nanos();
let time = i128::from(self.to_nanosecond());
let sum = span + time;
let days = sum.div_euclid(i128::from(b::NANOS_PER_CIVIL_DAY)) as i64;
let rem = sum.rem_euclid(i128::from(b::NANOS_PER_CIVIL_DAY)) as i64;
let span = Span::new().try_days(days)?;
Ok((Time::from_nanosecond_unchecked(rem), span))
}
#[inline]
pub(crate) fn overflowing_add_duration(
self,
duration: SignedDuration,
) -> Result<(Time, SignedDuration), Error> {
if self.subsec_nanosecond() != 0 || duration.subsec_nanos() != 0 {
return self.overflowing_add_duration_general(duration);
}
let start = i64::from(self.to_second());
let duration_secs = duration.as_secs();
let Some(sum) = start.checked_add(duration_secs) else {
return self.overflowing_add_duration_general(duration);
};
let days = b::SpanDays::check(sum.div_euclid(b::SECS_PER_CIVIL_DAY))?;
let rem = sum.rem_euclid(b::SECS_PER_CIVIL_DAY) as i32;
let time = Time::from_second_unchecked(rem);
Ok((time, SignedDuration::from_civil_days32(days)))
}
#[inline(never)]
#[cold]
fn overflowing_add_duration_general(
self,
duration: SignedDuration,
) -> Result<(Time, SignedDuration), Error> {
let start = i128::from(self.to_nanosecond());
let duration = duration.as_nanos();
let sum = start + duration;
let days = b::SpanDays::check128(
sum.div_euclid(i128::from(b::NANOS_PER_CIVIL_DAY)),
)?;
let rem = sum.rem_euclid(i128::from(b::NANOS_PER_CIVIL_DAY)) as i64;
let time = Time::from_nanosecond_unchecked(rem);
Ok((time, SignedDuration::from_civil_days32(days)))
}
#[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]
fn with_subsec_parts_unchecked(
self,
millisecond: i16,
microsecond: i16,
nanosecond: i16,
) -> Time {
let subsec_nanosecond = (i32::from(millisecond)
* b::NANOS_PER_MILLI_32)
+ (i32::from(microsecond) * b::NANOS_PER_MICRO_32)
+ i32::from(nanosecond);
Time { subsec_nanosecond, ..self }
}
#[inline]
pub(crate) fn until_nanoseconds(self, other: Time) -> i64 {
other.to_nanosecond() - self.to_nanosecond()
}
#[inline]
pub(crate) fn to_second(&self) -> i32 {
self.to_itime_const().to_second().second
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn from_second_unchecked(second: i32) -> Time {
debug_assert!(b::CivilDaySecond::check(second).is_ok());
Time::from_itime_const(ITimeSecond { second }.to_time())
}
#[inline]
pub(crate) fn to_nanosecond(&self) -> i64 {
self.to_itime_const().to_nanosecond().nanosecond
}
#[inline]
pub(crate) fn to_duration(&self) -> SignedDuration {
let mut dur = SignedDuration::from_hours(i64::from(self.hour()));
dur += SignedDuration::from_mins(i64::from(self.minute()));
dur += SignedDuration::from_secs(i64::from(self.second()));
dur += SignedDuration::new_without_nano_overflow(
0,
self.subsec_nanosecond(),
);
dur
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn from_nanosecond_unchecked(nanosecond: i64) -> Time {
debug_assert!(b::CivilDayNanosecond::check(nanosecond).is_ok());
Time::from_itime_const(ITimeNanosecond { nanosecond }.to_time())
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn from_duration_unchecked(dur: SignedDuration) -> Time {
let second = dur.as_secs();
debug_assert!(b::CivilDaySecond::check(second).is_ok());
let mut time = ITimeSecond { second: second as i32 }.to_time();
time.subsec_nanosecond = dur.subsec_nanos();
Time::from_itime_const(time)
}
#[inline]
pub(crate) const fn to_itime_const(&self) -> ITime {
ITime {
hour: self.hour,
minute: self.minute,
second: self.second,
subsec_nanosecond: self.subsec_nanosecond,
}
}
#[inline]
pub(crate) const fn from_itime_const(itime: ITime) -> Time {
Time {
hour: itime.hour,
minute: itime.minute,
second: itime.second,
subsec_nanosecond: itime.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_core::Serialize for Time {
#[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 Time {
#[inline]
fn deserialize<D: serde_core::Deserializer<'de>>(
deserializer: D,
) -> Result<Time, D::Error> {
use serde_core::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_str(TimeVisitor)
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Time {
fn arbitrary(g: &mut quickcheck::Gen) -> Time {
let hour = b::Hour::arbitrary(g);
let minute = b::Minute::arbitrary(g);
let second = b::Second::arbitrary(g);
let subsec_nanosecond = b::SubsecNanosecond::arbitrary(g);
Time::new(hour, minute, second, subsec_nanosecond).unwrap()
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Time>> {
alloc::boxed::Box::new(
(
self.hour(),
self.minute(),
self.second(),
self.subsec_nanosecond(),
)
.shrink()
.filter_map(
|(hour, minute, second, subsec_nanosecond)| {
Time::new(hour, minute, second, subsec_nanosecond).ok()
},
),
)
}
}
#[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)
}
}
impl core::iter::FusedIterator for TimeSeries {}
#[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()
}
#[inline]
fn until_with_largest_unit(&self, t1: Time) -> Result<Span, Error> {
if self.round.get_smallest() >= Unit::Day {
return Err(Error::from(UnitConfigError::CivilTime {
given: self.round.get_smallest(),
}));
}
let largest = self.round.get_largest().unwrap_or(Unit::Hour);
if largest >= Unit::Day {
return Err(Error::from(UnitConfigError::CivilTime {
given: largest,
}));
}
let t2 = self.time;
if t1 == t2 {
return Ok(Span::new());
}
let start = t1.to_duration();
let end = t2.to_duration();
let span = Span::from_invariant_duration(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 }
}
fn round(&self, t: Time) -> Result<Time, Error> {
let increment = Increment::for_time(self.smallest, self.increment)?;
let rounded = increment.round(self.mode, t.to_duration())?;
if rounded.as_secs() == i64::from(b::CivilDaySecond::MAX + 1) {
return Ok(Time::MIN);
}
Ok(Time::from_duration_unchecked(rounded))
}
}
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(),
Some(hour) => b::Hour::check(hour)?,
};
let minute = match self.minute {
None => self.original.minute(),
Some(minute) => b::Minute::check(minute)?,
};
let second = match self.second {
None => self.original.second(),
Some(second) => b::Second::check(second)?,
};
let millisecond = match self.millisecond {
None => self.original.millisecond(),
Some(millisecond) => b::Millisecond::check(millisecond)?,
};
let microsecond = match self.microsecond {
None => self.original.microsecond(),
Some(microsecond) => b::Microsecond::check(microsecond)?,
};
let nanosecond = match self.nanosecond {
None => self.original.nanosecond(),
Some(nanosecond) => b::Nanosecond::check(nanosecond)?,
};
let subsec_nanosecond = match self.subsec_nanosecond {
None => self.original.subsec_nanosecond(),
Some(subsec_nanosecond) => {
if self.millisecond.is_some() {
return Err(Error::from(E::IllegalTimeWithMillisecond));
}
if self.microsecond.is_some() {
return Err(Error::from(E::IllegalTimeWithMicrosecond));
}
if self.nanosecond.is_some() {
return Err(Error::from(E::IllegalTimeWithNanosecond));
}
b::SubsecNanosecond::check(subsec_nanosecond)?
}
};
if self.subsec_nanosecond.is_some() {
Ok(Time::new_unchecked(hour, minute, second, subsec_nanosecond))
} else {
Ok(Time::new_unchecked(hour, minute, second, 0)
.with_subsec_parts_unchecked(
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 std::io::Cursor;
use crate::{civil::time, span::span_eq, 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()
);
}
#[cfg(not(miri))]
quickcheck::quickcheck! {
fn prop_ordering_same_as_civil_nanosecond(
ns1: i64,
ns2: i64
) -> quickcheck::TestResult {
let Ok(ns1) = b::CivilDayNanosecond::check(ns1) else {
return quickcheck::TestResult::discard();
};
let Ok(ns2) = b::CivilDayNanosecond::check(ns2) else {
return quickcheck::TestResult::discard();
};
let t1 = Time::from_nanosecond_unchecked(ns1);
let t2 = Time::from_nanosecond_unchecked(ns2);
quickcheck::TestResult::from_bool(t1.cmp(&t2) == ns1.cmp(&ns2))
}
fn prop_checked_add_then_sub(
time: Time,
nano_span: i64
) -> quickcheck::TestResult {
let Ok(nano_span) = b::CivilDayNanosecond::check(nano_span) else {
return quickcheck::TestResult::discard();
};
let span = Span::new().nanoseconds(nano_span);
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: i64
) -> quickcheck::TestResult {
let Ok(nano_span) = b::CivilDayNanosecond::check(nano_span) else {
return quickcheck::TestResult::discard();
};
let span = Span::new().nanoseconds(nano_span);
let sum = time.wrapping_add(span);
let diff = sum.wrapping_sub(span);
quickcheck::TestResult::from_bool(time == diff)
}
fn prop_checked_add_equals_wrapping_add(
time: Time,
nano_span: i64
) -> quickcheck::TestResult {
let Ok(nano_span) = b::CivilDayNanosecond::check(nano_span) else {
return quickcheck::TestResult::discard();
};
let span = Span::new().nanoseconds(nano_span);
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: i64
) -> quickcheck::TestResult {
let Ok(nano_span) = b::CivilDayNanosecond::check(nano_span) else {
return quickcheck::TestResult::discard();
};
let span = Span::new().nanoseconds(nano_span);
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));
span_eq!(span, 1.days());
}
#[test]
fn overflowing_add_overflows() {
let t1 = time(23, 30, 0, 0);
let span = Span::new()
.hours(b::SpanHours::MAX)
.minutes(b::SpanMinutes::MAX)
.seconds(b::SpanSeconds::MAX)
.milliseconds(b::SpanMilliseconds::MAX)
.microseconds(b::SpanMicroseconds::MAX)
.nanoseconds(b::SpanNanoseconds::MAX);
assert!(t1.overflowing_add(&span).is_err());
}
#[test]
fn overflowing_add_span_negative() {
let t1 = time(0, 0, 0, 0);
let (t2, span) = t1.overflowing_add(&1.second()).unwrap();
assert_eq!(t2, time(0, 0, 1, 0));
span_eq!(span, 0.days());
let (t2, span) = t1.overflowing_add(&-1.second()).unwrap();
assert_eq!(t2, time(23, 59, 59, 0));
span_eq!(span, -1.days());
let (t2, span) = t1.overflowing_add(&1.nanosecond()).unwrap();
assert_eq!(t2, time(0, 0, 0, 1));
span_eq!(span, 0.days());
let (t2, span) = t1.overflowing_add(&-1.nanosecond()).unwrap();
assert_eq!(t2, time(23, 59, 59, 999_999_999));
span_eq!(span, -1.days());
let (t2, span) =
t1.overflowing_add(&1.second().nanoseconds(2)).unwrap();
assert_eq!(t2, time(0, 0, 1, 2));
span_eq!(span, 0.days());
let (t2, span) =
t1.overflowing_add(&-1.second().nanoseconds(2)).unwrap();
assert_eq!(t2, time(23, 59, 58, 999_999_998));
span_eq!(span, -1.days());
}
#[test]
fn overflowing_add_duration_negative() {
let t1 = time(0, 0, 0, 0);
let (t2, dur) =
t1.overflowing_add_duration(SignedDuration::from_secs(1)).unwrap();
assert_eq!(t2, time(0, 0, 1, 0));
assert_eq!(dur, SignedDuration::ZERO);
let (t2, dur) = t1
.overflowing_add_duration(SignedDuration::from_secs(-1))
.unwrap();
assert_eq!(t2, time(23, 59, 59, 0));
assert_eq!(dur, SignedDuration::from_hours(-24));
let (t2, dur) = t1
.overflowing_add_duration(SignedDuration::from_nanos(1))
.unwrap();
assert_eq!(t2, time(0, 0, 0, 1));
assert_eq!(dur, SignedDuration::ZERO);
let (t2, dur) = t1
.overflowing_add_duration(SignedDuration::from_nanos(-1))
.unwrap();
assert_eq!(t2, time(23, 59, 59, 999_999_999));
assert_eq!(dur, SignedDuration::from_hours(-24));
let (t2, dur) =
t1.overflowing_add_duration(SignedDuration::new(1, 2)).unwrap();
assert_eq!(t2, time(0, 0, 1, 2));
assert_eq!(dur, SignedDuration::ZERO);
let (t2, dur) =
t1.overflowing_add_duration(SignedDuration::new(-1, -2)).unwrap();
assert_eq!(t2, time(23, 59, 58, 999_999_998));
assert_eq!(dur, SignedDuration::from_hours(-24));
}
#[test]
fn time_size() {
#[cfg(debug_assertions)]
{
assert_eq!(8, 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 = i128::from(time(15, 30, 8, 999_999_999).to_nanosecond());
let expected = max.rem_euclid(i128::from(b::NANOS_PER_CIVIL_DAY));
assert_eq!(got, expected);
}
#[test]
fn wrapping_sub_signed_duration_max() {
let max = -SignedDuration::MAX.as_nanos();
let got = i128::from(time(8, 29, 52, 1).to_nanosecond());
let expected = max.rem_euclid(i128::from(b::NANOS_PER_CIVIL_DAY));
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 = i128::from(time(16, 59, 44, 1).to_nanosecond());
let expected = max.rem_euclid(i128::from(b::NANOS_PER_CIVIL_DAY));
assert_eq!(got, expected);
}
#[test]
fn civil_time_deserialize_yaml() {
let expected = time(16, 35, 4, 987654321);
let deserialized: Time =
serde_yaml::from_str("16:35:04.987654321").unwrap();
assert_eq!(deserialized, expected);
let deserialized: Time =
serde_yaml::from_slice("16:35:04.987654321".as_bytes()).unwrap();
assert_eq!(deserialized, expected);
let cursor = Cursor::new(b"16:35:04.987654321");
let deserialized: Time = serde_yaml::from_reader(cursor).unwrap();
assert_eq!(deserialized, expected);
}
}