use core::time::Duration as UnsignedDuration;
use crate::{
duration::{Duration, SDuration},
error::{err, Error, ErrorContext},
fmt::{
self,
temporal::{self, DEFAULT_DATETIME_PARSER},
},
tz::{Offset, TimeZone},
util::{
rangeint::{RFrom, RInto},
round::increment,
t::{
self, FractionalNanosecond, NoUnits, NoUnits128, UnixMicroseconds,
UnixMilliseconds, UnixNanoseconds, UnixSeconds, C,
},
},
zoned::Zoned,
RoundMode, SignedDuration, Span, SpanRound, Unit,
};
#[derive(Clone, Copy)]
pub struct Timestamp {
second: UnixSeconds,
nanosecond: FractionalNanosecond,
}
impl Timestamp {
pub const MIN: Timestamp = Timestamp {
second: UnixSeconds::MIN_SELF,
nanosecond: FractionalNanosecond::N::<0>(),
};
pub const MAX: Timestamp = Timestamp {
second: UnixSeconds::MAX_SELF,
nanosecond: FractionalNanosecond::MAX_SELF,
};
pub const UNIX_EPOCH: Timestamp = Timestamp {
second: UnixSeconds::N::<0>(),
nanosecond: FractionalNanosecond::N::<0>(),
};
#[cfg(feature = "std")]
pub fn now() -> Timestamp {
Timestamp::try_from(crate::now::system_time())
.expect("system time is valid")
}
#[inline]
pub fn new(second: i64, nanosecond: i32) -> Result<Timestamp, Error> {
Timestamp::new_ranged(
UnixSeconds::try_new("second", second)?,
FractionalNanosecond::try_new("nanosecond", nanosecond)?,
)
}
#[inline]
pub fn from_second(second: i64) -> Result<Timestamp, Error> {
Timestamp::new(second, 0)
}
#[inline]
pub fn from_millisecond(millisecond: i64) -> Result<Timestamp, Error> {
let millisecond = UnixMilliseconds::try_new128(
"millisecond timestamp",
millisecond,
)?;
Ok(Timestamp::from_millisecond_ranged(millisecond))
}
#[inline]
pub fn from_microsecond(microsecond: i64) -> Result<Timestamp, Error> {
let microsecond = UnixMicroseconds::try_new128(
"microsecond timestamp",
microsecond,
)?;
Ok(Timestamp::from_microsecond_ranged(microsecond))
}
#[inline]
pub fn from_nanosecond(nanosecond: i128) -> Result<Timestamp, Error> {
let nanosecond =
UnixNanoseconds::try_new128("nanosecond timestamp", nanosecond)?;
Ok(Timestamp::from_nanosecond_ranged(nanosecond))
}
#[inline]
pub fn from_jiff_duration(
duration: SignedDuration,
) -> Result<Timestamp, Error> {
Timestamp::new(duration.as_secs(), duration.subsec_nanos())
}
#[inline]
pub fn as_second(self) -> i64 {
self.as_second_ranged().get()
}
#[inline]
pub fn as_millisecond(self) -> i64 {
self.as_millisecond_ranged().get()
}
#[inline]
pub fn as_microsecond(self) -> i64 {
self.as_microsecond_ranged().get()
}
#[inline]
pub fn as_nanosecond(self) -> i128 {
self.as_nanosecond_ranged().get()
}
#[inline]
pub fn subsec_millisecond(self) -> i32 {
self.subsec_millisecond_ranged().get()
}
#[inline]
pub fn subsec_microsecond(self) -> i32 {
self.subsec_microsecond_ranged().get()
}
#[inline]
pub fn subsec_nanosecond(self) -> i32 {
self.subsec_nanosecond_ranged().get()
}
#[inline]
pub fn as_jiff_duration(self) -> SignedDuration {
SignedDuration::from_timestamp(self)
}
#[inline]
pub fn signum(self) -> i8 {
if self.is_zero() {
0
} else if self.as_second() > 0 || self.subsec_nanosecond() > 0 {
1
} else {
-1
}
}
#[inline]
pub fn is_zero(self) -> bool {
self.as_second() == 0 && self.subsec_nanosecond() == 0
}
#[inline]
pub fn intz(self, time_zone_name: &str) -> Result<Zoned, Error> {
let tz = crate::tz::db().get(time_zone_name)?;
Ok(self.to_zoned(tz))
}
#[inline]
pub fn to_zoned(self, tz: TimeZone) -> Zoned {
Zoned::new(self, tz)
}
#[inline]
pub fn checked_add<A: Into<TimestampArithmetic>>(
self,
duration: A,
) -> Result<Timestamp, Error> {
let duration: TimestampArithmetic = duration.into();
duration.checked_add(self)
}
#[inline]
fn checked_add_span(self, span: Span) -> Result<Timestamp, Error> {
if let Some(err) = span.smallest_non_time_non_zero_unit_error() {
return Err(err);
}
if span.is_zero() {
return Ok(self);
}
if let Some(span_seconds) = span.to_invariant_seconds() {
let time_seconds = self.as_second_ranged();
let sum = time_seconds
.try_checked_add("span", span_seconds)
.with_context(|| err!("adding {span} to {self} overflowed"))?;
return Ok(Timestamp::from_second_ranged(sum));
}
let time_nanos = self.as_nanosecond_ranged();
let span_nanos = span.to_invariant_nanoseconds();
let sum = time_nanos
.try_checked_add("span", span_nanos)
.with_context(|| err!("adding {span} to {self} overflowed"))?;
Ok(Timestamp::from_nanosecond_ranged(sum))
}
#[inline]
fn checked_add_duration(
self,
duration: SignedDuration,
) -> Result<Timestamp, Error> {
let start = self.as_jiff_duration();
let end = start.checked_add(duration).ok_or_else(|| {
err!("overflow when adding {duration:?} to {self}")
})?;
Timestamp::from_jiff_duration(end)
}
#[inline]
pub fn checked_sub<A: Into<TimestampArithmetic>>(
self,
duration: A,
) -> Result<Timestamp, Error> {
let duration: TimestampArithmetic = duration.into();
duration.checked_neg().and_then(|ta| ta.checked_add(self))
}
#[inline]
pub fn saturating_add<A: Into<TimestampArithmetic>>(
self,
duration: A,
) -> Timestamp {
let duration: TimestampArithmetic = duration.into();
match duration.saturating_add(self) {
Ok(ts) => ts,
Err(err) => {
panic!(
"saturating Timestamp arithmetic \
requires only time units: {err}"
)
}
}
}
#[inline]
pub fn saturating_sub<A: Into<TimestampArithmetic>>(
self,
duration: A,
) -> Timestamp {
let duration: TimestampArithmetic = duration.into();
let Ok(duration) = duration.checked_neg() else {
return Timestamp::MIN;
};
self.saturating_add(duration)
}
#[inline]
pub fn until<A: Into<TimestampDifference>>(
self,
other: A,
) -> Result<Span, Error> {
let args: TimestampDifference = 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<TimestampDifference>>(
self,
other: A,
) -> Result<Span, Error> {
let args: TimestampDifference = 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: Timestamp) -> SignedDuration {
SignedDuration::timestamp_until(self, other)
}
#[inline]
pub fn duration_since(self, other: Timestamp) -> SignedDuration {
SignedDuration::timestamp_until(other, self)
}
#[inline]
pub fn round<R: Into<TimestampRound>>(
self,
options: R,
) -> Result<Timestamp, Error> {
let options: TimestampRound = options.into();
options.round(self)
}
#[inline]
pub fn series(self, period: Span) -> TimestampSeries {
TimestampSeries { start: self, period, step: 0 }
}
}
impl Timestamp {
#[inline]
pub fn strptime(
format: impl AsRef<[u8]>,
input: impl AsRef<[u8]>,
) -> Result<Timestamp, Error> {
fmt::strtime::parse(format, input).and_then(|tm| tm.to_timestamp())
}
#[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() }
}
#[inline]
pub fn display_with_offset(
&self,
offset: Offset,
) -> TimestampDisplayWithOffset {
TimestampDisplayWithOffset { timestamp: *self, offset }
}
}
impl Timestamp {
#[deprecated(
since = "0.1.5",
note = "use Timestamp::from_jiff_duration instead"
)]
#[inline]
pub fn from_duration(
duration: core::time::Duration,
) -> Result<Timestamp, Error> {
#[allow(deprecated)]
Timestamp::from_signed_duration(1, duration)
}
#[deprecated(
since = "0.1.5",
note = "use Timestamp::from_jiff_duration instead"
)]
#[inline]
pub fn from_signed_duration(
sign: i8,
duration: core::time::Duration,
) -> Result<Timestamp, Error> {
let sign = sign.signum();
let seconds = i64::try_from(duration.as_secs()).map_err(|_| {
Error::unsigned(
"duration seconds",
duration.as_secs(),
UnixSeconds::MIN_REPR,
UnixSeconds::MAX_REPR,
)
})?;
let nanos = i32::try_from(duration.subsec_nanos())
.expect("nanoseconds in duration are less than 1,000,000,000");
Timestamp::new(seconds * i64::from(sign), nanos * i32::from(sign))
}
#[deprecated(
since = "0.1.5",
note = "use Timestamp::as_signed_duration instead"
)]
#[inline]
pub fn as_duration(self) -> (i8, core::time::Duration) {
let second = u64::try_from(self.as_second().abs())
.expect("absolute value of seconds fits in u64");
let nanosecond = u32::try_from(self.subsec_nanosecond().abs())
.expect("nanosecond always fit in a u32");
(self.signum(), core::time::Duration::new(second, nanosecond))
}
}
impl Timestamp {
#[inline]
pub(crate) fn new_ranged(
second: impl RInto<UnixSeconds>,
nanosecond: impl RInto<FractionalNanosecond>,
) -> Result<Timestamp, Error> {
let (second, nanosecond) = (second.rinto(), nanosecond.rinto());
if second == UnixSeconds::MIN_REPR && nanosecond < 0 {
return Err(Error::signed(
"seconds and nanoseconds",
nanosecond,
0,
0,
));
}
if second.signum() == nanosecond.signum()
|| second == 0
|| nanosecond == 0
{
return Ok(Timestamp { second, nanosecond });
}
let second = second.without_bounds();
let nanosecond = nanosecond.without_bounds();
let [delta_second, delta_nanosecond] = t::NoUnits::vary_many(
[second, nanosecond],
|[second, nanosecond]| {
if second < 0 && nanosecond > 0 {
[C(1), (-t::NANOS_PER_SECOND).rinto()]
} else if second > 0 && nanosecond < 0 {
[C(-1), t::NANOS_PER_SECOND.rinto()]
} else {
[C(0), C(0)]
}
},
);
Ok(Timestamp {
second: (second + delta_second).rinto(),
nanosecond: (nanosecond + delta_nanosecond).rinto(),
})
}
#[inline]
fn from_second_ranged(second: UnixSeconds) -> Timestamp {
Timestamp { second, nanosecond: FractionalNanosecond::N::<0>() }
}
#[inline]
fn from_millisecond_ranged(millisecond: UnixMilliseconds) -> Timestamp {
let second =
UnixSeconds::rfrom(millisecond.div_ceil(t::MILLIS_PER_SECOND));
let nanosecond = FractionalNanosecond::rfrom(
millisecond.rem_ceil(t::MILLIS_PER_SECOND) * t::NANOS_PER_MILLI,
);
Timestamp { second, nanosecond }
}
#[inline]
fn from_microsecond_ranged(microsecond: UnixMicroseconds) -> Timestamp {
let second =
UnixSeconds::rfrom(microsecond.div_ceil(t::MICROS_PER_SECOND));
let nanosecond = FractionalNanosecond::rfrom(
microsecond.rem_ceil(t::MICROS_PER_SECOND) * t::NANOS_PER_MICRO,
);
Timestamp { second, nanosecond }
}
#[inline]
pub(crate) fn from_nanosecond_ranged(
nanosecond: UnixNanoseconds,
) -> Timestamp {
let second =
UnixSeconds::rfrom(nanosecond.div_ceil(t::NANOS_PER_SECOND));
let nanosecond = nanosecond.rem_ceil(t::NANOS_PER_SECOND).rinto();
Timestamp { second, nanosecond }
}
#[inline]
pub(crate) fn as_second_ranged(self) -> UnixSeconds {
self.second
}
#[inline]
fn as_millisecond_ranged(self) -> UnixMilliseconds {
let second = NoUnits::rfrom(self.as_second_ranged());
let nanosecond = NoUnits::rfrom(self.subsec_nanosecond_ranged());
let [second, nanosecond] =
NoUnits::vary_many([second, nanosecond], |[second, nanosecond]| {
if second == UnixSeconds::MIN_REPR && nanosecond < 0 {
[second, C(0).rinto()]
} else {
[second, nanosecond]
}
});
UnixMilliseconds::rfrom(
(second * t::MILLIS_PER_SECOND)
+ (nanosecond.div_ceil(t::NANOS_PER_MILLI)),
)
}
#[inline]
fn as_microsecond_ranged(self) -> UnixMicroseconds {
let second = NoUnits::rfrom(self.as_second_ranged());
let nanosecond = NoUnits::rfrom(self.subsec_nanosecond_ranged());
let [second, nanosecond] =
NoUnits::vary_many([second, nanosecond], |[second, nanosecond]| {
if second == UnixSeconds::MIN_REPR && nanosecond < 0 {
[second, C(0).rinto()]
} else {
[second, nanosecond]
}
});
UnixMicroseconds::rfrom(
(second * t::MICROS_PER_SECOND)
+ (nanosecond.div_ceil(t::NANOS_PER_MICRO)),
)
}
#[inline]
pub(crate) fn as_nanosecond_ranged(self) -> UnixNanoseconds {
let second = NoUnits128::rfrom(self.as_second_ranged());
let nanosecond = NoUnits128::rfrom(self.subsec_nanosecond_ranged());
let [second, nanosecond] = NoUnits128::vary_many(
[second, nanosecond],
|[second, nanosecond]| {
if second == UnixSeconds::MIN_REPR && nanosecond < 0 {
[second, C(0).rinto()]
} else {
[second, nanosecond]
}
},
);
UnixNanoseconds::rfrom(second * t::NANOS_PER_SECOND + nanosecond)
}
#[inline]
fn subsec_millisecond_ranged(self) -> t::FractionalMillisecond {
let millis =
self.subsec_nanosecond_ranged().div_ceil(t::NANOS_PER_MILLI);
t::FractionalMillisecond::rfrom(millis)
}
#[inline]
fn subsec_microsecond_ranged(self) -> t::FractionalMicrosecond {
let micros =
self.subsec_nanosecond_ranged().div_ceil(t::NANOS_PER_MICRO);
t::FractionalMicrosecond::rfrom(micros)
}
#[inline]
pub(crate) fn subsec_nanosecond_ranged(self) -> FractionalNanosecond {
self.nanosecond
}
}
impl Default for Timestamp {
#[inline]
fn default() -> Timestamp {
Timestamp::UNIX_EPOCH
}
}
impl core::fmt::Debug for Timestamp {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Display::fmt(self, f)
}
}
impl core::fmt::Display for Timestamp {
#[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_timestamp(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
}
}
impl core::str::FromStr for Timestamp {
type Err = Error;
#[inline]
fn from_str(string: &str) -> Result<Timestamp, Error> {
DEFAULT_DATETIME_PARSER.parse_timestamp(string)
}
}
impl Eq for Timestamp {}
impl PartialEq for Timestamp {
#[inline]
fn eq(&self, rhs: &Timestamp) -> bool {
self.as_second_ranged().get() == rhs.as_second_ranged().get()
&& self.subsec_nanosecond_ranged().get()
== rhs.subsec_nanosecond_ranged().get()
}
}
impl Ord for Timestamp {
#[inline]
fn cmp(&self, rhs: &Timestamp) -> core::cmp::Ordering {
(self.as_second_ranged().get(), self.subsec_nanosecond_ranged().get())
.cmp(&(
rhs.as_second_ranged().get(),
rhs.subsec_nanosecond_ranged().get(),
))
}
}
impl PartialOrd for Timestamp {
#[inline]
fn partial_cmp(&self, rhs: &Timestamp) -> Option<core::cmp::Ordering> {
Some(self.cmp(rhs))
}
}
impl core::ops::Add<Span> for Timestamp {
type Output = Timestamp;
#[inline]
fn add(self, rhs: Span) -> Timestamp {
self.checked_add(rhs).expect("adding span to timestamp overflowed")
}
}
impl core::ops::AddAssign<Span> for Timestamp {
#[inline]
fn add_assign(&mut self, rhs: Span) {
*self = *self + rhs
}
}
impl core::ops::Sub<Span> for Timestamp {
type Output = Timestamp;
#[inline]
fn sub(self, rhs: Span) -> Timestamp {
self.checked_sub(rhs)
.expect("subtracting span from timestamp overflowed")
}
}
impl core::ops::SubAssign<Span> for Timestamp {
#[inline]
fn sub_assign(&mut self, rhs: Span) {
*self = *self - rhs
}
}
impl core::ops::Sub for Timestamp {
type Output = Span;
#[inline]
fn sub(self, rhs: Timestamp) -> Span {
self.since(rhs).expect("since never fails when given Timestamp")
}
}
impl core::ops::Add<SignedDuration> for Timestamp {
type Output = Timestamp;
#[inline]
fn add(self, rhs: SignedDuration) -> Timestamp {
self.checked_add(rhs)
.expect("adding signed duration to timestamp overflowed")
}
}
impl core::ops::AddAssign<SignedDuration> for Timestamp {
#[inline]
fn add_assign(&mut self, rhs: SignedDuration) {
*self = *self + rhs
}
}
impl core::ops::Sub<SignedDuration> for Timestamp {
type Output = Timestamp;
#[inline]
fn sub(self, rhs: SignedDuration) -> Timestamp {
self.checked_sub(rhs)
.expect("subtracting signed duration from timestamp overflowed")
}
}
impl core::ops::SubAssign<SignedDuration> for Timestamp {
#[inline]
fn sub_assign(&mut self, rhs: SignedDuration) {
*self = *self - rhs
}
}
impl core::ops::Add<UnsignedDuration> for Timestamp {
type Output = Timestamp;
#[inline]
fn add(self, rhs: UnsignedDuration) -> Timestamp {
self.checked_add(rhs)
.expect("adding unsigned duration to timestamp overflowed")
}
}
impl core::ops::AddAssign<UnsignedDuration> for Timestamp {
#[inline]
fn add_assign(&mut self, rhs: UnsignedDuration) {
*self = *self + rhs
}
}
impl core::ops::Sub<UnsignedDuration> for Timestamp {
type Output = Timestamp;
#[inline]
fn sub(self, rhs: UnsignedDuration) -> Timestamp {
self.checked_sub(rhs)
.expect("subtracting unsigned duration from timestamp overflowed")
}
}
impl core::ops::SubAssign<UnsignedDuration> for Timestamp {
#[inline]
fn sub_assign(&mut self, rhs: UnsignedDuration) {
*self = *self - rhs
}
}
impl From<Zoned> for Timestamp {
#[inline]
fn from(zdt: Zoned) -> Timestamp {
zdt.timestamp()
}
}
impl<'a> From<&'a Zoned> for Timestamp {
#[inline]
fn from(zdt: &'a Zoned) -> Timestamp {
zdt.timestamp()
}
}
#[cfg(feature = "std")]
impl From<Timestamp> for std::time::SystemTime {
#[inline]
fn from(time: Timestamp) -> std::time::SystemTime {
let unix_epoch = std::time::SystemTime::UNIX_EPOCH;
let sdur = time.as_jiff_duration();
let dur = sdur.unsigned_abs();
if sdur.is_negative() {
unix_epoch.checked_sub(dur).expect("duration too big (negative)")
} else {
unix_epoch.checked_add(dur).expect("duration too big (positive)")
}
}
}
#[cfg(feature = "std")]
impl TryFrom<std::time::SystemTime> for Timestamp {
type Error = Error;
#[inline]
fn try_from(
system_time: std::time::SystemTime,
) -> Result<Timestamp, Error> {
let unix_epoch = std::time::SystemTime::UNIX_EPOCH;
let dur = SignedDuration::system_until(unix_epoch, system_time)?;
Timestamp::from_jiff_duration(dur)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Timestamp {
#[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 Timestamp {
#[inline]
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<Timestamp, D::Error> {
use serde::de;
struct TimestampVisitor;
impl<'de> de::Visitor<'de> for TimestampVisitor {
type Value = Timestamp;
fn expecting(
&self,
f: &mut core::fmt::Formatter,
) -> core::fmt::Result {
f.write_str("a timestamp string")
}
#[inline]
fn visit_bytes<E: de::Error>(
self,
value: &[u8],
) -> Result<Timestamp, E> {
DEFAULT_DATETIME_PARSER
.parse_timestamp(value)
.map_err(de::Error::custom)
}
#[inline]
fn visit_str<E: de::Error>(
self,
value: &str,
) -> Result<Timestamp, E> {
self.visit_bytes(value.as_bytes())
}
}
deserializer.deserialize_bytes(TimestampVisitor)
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Timestamp {
fn arbitrary(g: &mut quickcheck::Gen) -> Timestamp {
use quickcheck::Arbitrary;
let seconds: UnixSeconds = Arbitrary::arbitrary(g);
let mut nanoseconds: FractionalNanosecond = Arbitrary::arbitrary(g);
if seconds == UnixSeconds::MIN_REPR && nanoseconds < 0 {
nanoseconds = C(0).rinto();
}
Timestamp::new_ranged(seconds, nanoseconds).unwrap_or_default()
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Self>> {
let second = self.as_second_ranged();
let nanosecond = self.subsec_nanosecond_ranged();
alloc::boxed::Box::new((second, nanosecond).shrink().filter_map(
|(second, nanosecond)| {
if second == UnixSeconds::MIN_REPR && nanosecond > 0 {
None
} else {
Timestamp::new_ranged(second, nanosecond).ok()
}
},
))
}
}
#[derive(Clone, Copy, Debug)]
pub struct TimestampDisplayWithOffset {
timestamp: Timestamp,
offset: Offset,
}
impl core::fmt::Display for TimestampDisplayWithOffset {
#[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_timestamp_with_offset(
&self.timestamp,
self.offset,
StdFmtWrite(f),
)
.map_err(|_| core::fmt::Error)
}
}
#[derive(Clone, Debug)]
pub struct TimestampSeries {
start: Timestamp,
period: Span,
step: i64,
}
impl Iterator for TimestampSeries {
type Item = Timestamp;
#[inline]
fn next(&mut self) -> Option<Timestamp> {
let span = self.period.checked_mul(self.step).ok()?;
self.step = self.step.checked_add(1)?;
let timestamp = self.start.checked_add(span).ok()?;
Some(timestamp)
}
}
#[derive(Clone, Copy, Debug)]
pub struct TimestampArithmetic {
duration: Duration,
}
impl TimestampArithmetic {
#[inline]
fn checked_add(self, ts: Timestamp) -> Result<Timestamp, Error> {
match self.duration.to_signed()? {
SDuration::Span(span) => ts.checked_add_span(span),
SDuration::Absolute(sdur) => ts.checked_add_duration(sdur),
}
}
#[inline]
fn saturating_add(self, ts: Timestamp) -> Result<Timestamp, Error> {
let Ok(signed) = self.duration.to_signed() else {
return Ok(Timestamp::MAX);
};
let result = match signed {
SDuration::Span(span) => {
if let Some(err) = span.smallest_non_time_non_zero_unit_error()
{
return Err(err);
}
ts.checked_add_span(span)
}
SDuration::Absolute(sdur) => ts.checked_add_duration(sdur),
};
Ok(result.unwrap_or_else(|_| {
if self.is_negative() {
Timestamp::MIN
} else {
Timestamp::MAX
}
}))
}
#[inline]
fn checked_neg(self) -> Result<TimestampArithmetic, Error> {
let duration = self.duration.checked_neg()?;
Ok(TimestampArithmetic { duration })
}
#[inline]
fn is_negative(&self) -> bool {
self.duration.is_negative()
}
}
impl From<Span> for TimestampArithmetic {
fn from(span: Span) -> TimestampArithmetic {
let duration = Duration::from(span);
TimestampArithmetic { duration }
}
}
impl From<SignedDuration> for TimestampArithmetic {
fn from(sdur: SignedDuration) -> TimestampArithmetic {
let duration = Duration::from(sdur);
TimestampArithmetic { duration }
}
}
impl From<UnsignedDuration> for TimestampArithmetic {
fn from(udur: UnsignedDuration) -> TimestampArithmetic {
let duration = Duration::from(udur);
TimestampArithmetic { duration }
}
}
impl<'a> From<&'a Span> for TimestampArithmetic {
fn from(span: &'a Span) -> TimestampArithmetic {
TimestampArithmetic::from(*span)
}
}
impl<'a> From<&'a SignedDuration> for TimestampArithmetic {
fn from(sdur: &'a SignedDuration) -> TimestampArithmetic {
TimestampArithmetic::from(*sdur)
}
}
impl<'a> From<&'a UnsignedDuration> for TimestampArithmetic {
fn from(udur: &'a UnsignedDuration) -> TimestampArithmetic {
TimestampArithmetic::from(*udur)
}
}
#[derive(Clone, Copy, Debug)]
pub struct TimestampDifference {
timestamp: Timestamp,
round: SpanRound<'static>,
}
impl TimestampDifference {
#[inline]
pub fn new(timestamp: Timestamp) -> TimestampDifference {
let round = SpanRound::new().mode(RoundMode::Trunc);
TimestampDifference { timestamp, round }
}
#[inline]
pub fn smallest(self, unit: Unit) -> TimestampDifference {
TimestampDifference { round: self.round.smallest(unit), ..self }
}
#[inline]
pub fn largest(self, unit: Unit) -> TimestampDifference {
TimestampDifference { round: self.round.largest(unit), ..self }
}
#[inline]
pub fn mode(self, mode: RoundMode) -> TimestampDifference {
TimestampDifference { round: self.round.mode(mode), ..self }
}
#[inline]
pub fn increment(self, increment: i64) -> TimestampDifference {
TimestampDifference { 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: Timestamp) -> Result<Span, Error> {
let t2 = self.timestamp;
let largest = self
.round
.get_largest()
.unwrap_or_else(|| self.round.get_smallest().max(Unit::Second));
if largest >= Unit::Day {
return Err(err!(
"unit {largest} is not supported when computing the \
difference between timestamps (must use units smaller \
than 'day')",
largest = largest.singular(),
));
}
let nano1 = t1.as_nanosecond_ranged().without_bounds();
let nano2 = t2.as_nanosecond_ranged().without_bounds();
let diff = nano2 - nano1;
Span::from_invariant_nanoseconds(largest, diff)
}
}
impl From<Timestamp> for TimestampDifference {
#[inline]
fn from(ts: Timestamp) -> TimestampDifference {
TimestampDifference::new(ts)
}
}
impl From<Zoned> for TimestampDifference {
#[inline]
fn from(zdt: Zoned) -> TimestampDifference {
TimestampDifference::new(Timestamp::from(zdt))
}
}
impl<'a> From<&'a Zoned> for TimestampDifference {
#[inline]
fn from(zdt: &'a Zoned) -> TimestampDifference {
TimestampDifference::from(Timestamp::from(zdt))
}
}
impl From<(Unit, Timestamp)> for TimestampDifference {
#[inline]
fn from((largest, ts): (Unit, Timestamp)) -> TimestampDifference {
TimestampDifference::from(ts).largest(largest)
}
}
impl From<(Unit, Zoned)> for TimestampDifference {
#[inline]
fn from((largest, zdt): (Unit, Zoned)) -> TimestampDifference {
TimestampDifference::from((largest, Timestamp::from(zdt)))
}
}
impl<'a> From<(Unit, &'a Zoned)> for TimestampDifference {
#[inline]
fn from((largest, zdt): (Unit, &'a Zoned)) -> TimestampDifference {
TimestampDifference::from((largest, Timestamp::from(zdt)))
}
}
#[derive(Clone, Copy, Debug)]
pub struct TimestampRound {
smallest: Unit,
mode: RoundMode,
increment: i64,
}
impl TimestampRound {
#[inline]
pub fn new() -> TimestampRound {
TimestampRound {
smallest: Unit::Nanosecond,
mode: RoundMode::HalfExpand,
increment: 1,
}
}
#[inline]
pub fn smallest(self, unit: Unit) -> TimestampRound {
TimestampRound { smallest: unit, ..self }
}
#[inline]
pub fn mode(self, mode: RoundMode) -> TimestampRound {
TimestampRound { mode, ..self }
}
#[inline]
pub fn increment(self, increment: i64) -> TimestampRound {
TimestampRound { increment, ..self }
}
pub(crate) fn round(
&self,
timestamp: Timestamp,
) -> Result<Timestamp, Error> {
let increment =
increment::for_timestamp(self.smallest, self.increment)?;
let nanosecond = timestamp.as_nanosecond_ranged().without_bounds();
let rounded = self.mode.round_by_unit_in_nanoseconds(
nanosecond,
self.smallest,
increment,
);
let nanosecond = UnixNanoseconds::rfrom(rounded);
Ok(Timestamp::from_nanosecond_ranged(nanosecond))
}
}
impl Default for TimestampRound {
#[inline]
fn default() -> TimestampRound {
TimestampRound::new()
}
}
impl From<Unit> for TimestampRound {
#[inline]
fn from(unit: Unit) -> TimestampRound {
TimestampRound::default().smallest(unit)
}
}
impl From<(Unit, i64)> for TimestampRound {
#[inline]
fn from((unit, increment): (Unit, i64)) -> TimestampRound {
TimestampRound::from(unit).increment(increment)
}
}
#[cfg(test)]
mod tests {
use crate::{civil, tz::Offset};
use super::*;
fn mktime(seconds: i64, nanos: i32) -> Timestamp {
Timestamp::new(seconds, nanos).unwrap()
}
fn mkdt(
year: i16,
month: i8,
day: i8,
hour: i8,
minute: i8,
second: i8,
nano: i32,
) -> civil::DateTime {
let date = civil::Date::new(year, month, day).unwrap();
let time = civil::Time::new(hour, minute, second, nano).unwrap();
civil::DateTime::from_parts(date, time)
}
#[test]
fn to_datetime_specific_examples() {
let tests = [
(
(UnixSeconds::MIN_REPR + 1, -999_999_999),
(-9999, 1, 2, 1, 59, 59, 1),
),
((-1, 1), (1969, 12, 31, 23, 59, 59, 1)),
];
for (t, dt) in tests {
let timestamp = mktime(t.0, t.1);
let datetime = mkdt(dt.0, dt.1, dt.2, dt.3, dt.4, dt.5, dt.6);
assert_eq!(
Offset::UTC.to_datetime(timestamp),
datetime,
"timestamp: {t:?}"
);
assert_eq!(
timestamp,
datetime.to_zoned(TimeZone::UTC).unwrap().timestamp(),
"datetime: {datetime:?}"
);
}
}
#[test]
fn to_datetime_many_seconds_in_some_days() {
let days = [
i64::from(t::UnixEpochDays::MIN_REPR),
-1000,
-5,
23,
2000,
i64::from(t::UnixEpochDays::MAX_REPR),
];
let seconds = [
-86_400, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 86_400,
];
let nanos = [0, 1, 5, 999_999_999];
for day in days {
let midpoint = day * 86_400;
for second in seconds {
let second = midpoint + second;
if !UnixSeconds::contains(second) {
continue;
}
for nano in nanos {
if second == UnixSeconds::MIN_REPR && nano != 0 {
continue;
}
let t = Timestamp::new(second, nano).unwrap();
let Ok(got) =
Offset::UTC.to_datetime(t).to_zoned(TimeZone::UTC)
else {
continue;
};
assert_eq!(t, got.timestamp());
}
}
}
}
#[test]
fn invalid_time() {
assert!(Timestamp::new(UnixSeconds::MIN_REPR, -1).is_err());
assert!(Timestamp::new(UnixSeconds::MIN_REPR, -999_999_999).is_err());
assert!(Timestamp::new(UnixSeconds::MIN_REPR, 1).is_ok());
assert!(Timestamp::new(UnixSeconds::MIN_REPR, 999_999_999).is_ok());
}
#[cfg(target_pointer_width = "64")]
#[test]
fn timestamp_size() {
#[cfg(debug_assertions)]
{
assert_eq!(40, core::mem::size_of::<Timestamp>());
}
#[cfg(not(debug_assertions))]
{
assert_eq!(16, core::mem::size_of::<Timestamp>());
}
}
#[test]
fn nanosecond_roundtrip_boundaries() {
let inst = Timestamp::MIN;
let nanos = inst.as_nanosecond_ranged();
assert_eq!(0, nanos % t::NANOS_PER_SECOND);
let got = Timestamp::from_nanosecond_ranged(nanos);
assert_eq!(inst, got);
let inst = Timestamp::MAX;
let nanos = inst.as_nanosecond_ranged();
assert_eq!(
FractionalNanosecond::MAX_SELF,
nanos % t::NANOS_PER_SECOND
);
let got = Timestamp::from_nanosecond_ranged(nanos);
assert_eq!(inst, got);
}
#[test]
#[should_panic]
fn timestamp_saturating_add() {
Timestamp::MIN.saturating_add(Span::new().days(1));
}
#[test]
#[should_panic]
fn timestamp_saturating_sub() {
Timestamp::MAX.saturating_sub(Span::new().days(1));
}
quickcheck::quickcheck! {
fn prop_unix_seconds_roundtrip(t: Timestamp) -> quickcheck::TestResult {
let dt = t.to_zoned(TimeZone::UTC).datetime();
let Ok(got) = dt.to_zoned(TimeZone::UTC) else {
return quickcheck::TestResult::discard();
};
quickcheck::TestResult::from_bool(t == got.timestamp())
}
fn prop_nanos_roundtrip_unix_ranged(t: Timestamp) -> bool {
let nanos = t.as_nanosecond_ranged();
let got = Timestamp::from_nanosecond_ranged(nanos);
t == got
}
fn prop_nanos_roundtrip_unix(t: Timestamp) -> bool {
let nanos = t.as_nanosecond();
let got = Timestamp::from_nanosecond(nanos).unwrap();
t == got
}
}
}