use std::borrow::Cow;
use std::fmt::Debug;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use ordered_varint::Variable;
use serde::{Deserialize, Serialize};
use crate::key::time::limited::{BonsaiEpoch, UnixEpoch};
use crate::key::{ByteSource, CompositeKind, Key, KeyEncoding, KeyKind, KeyVisitor};
impl<'k> Key<'k> for Duration {
const CAN_OWN_BYTES: bool = false;
fn from_ord_bytes<'e>(bytes: ByteSource<'k, 'e>) -> Result<Self, Self::Error> {
let merged = u128::decode_variable(bytes.as_ref()).map_err(|_| TimeError::InvalidValue)?;
let seconds = u64::try_from(merged >> 30).map_err(|_| TimeError::DeltaNotRepresentable)?;
let nanos = u32::try_from(merged & (2_u128.pow(30) - 1)).unwrap();
Ok(Self::new(seconds, nanos))
}
}
impl KeyEncoding<Self> for Duration {
type Error = TimeError;
const LENGTH: Option<usize> = None;
fn describe<Visitor>(visitor: &mut Visitor)
where
Visitor: KeyVisitor,
{
visitor.visit_composite(
CompositeKind::Struct(Cow::Borrowed("std::time::Duration")),
1,
);
visitor.visit_type(KeyKind::Unsigned);
}
fn as_ord_bytes(&self) -> Result<Cow<'_, [u8]>, Self::Error> {
let merged = u128::from(self.as_secs()) << 30 | u128::from(self.subsec_nanos());
Ok(Cow::Owned(merged.to_variable_vec().unwrap()))
}
}
#[test]
fn duration_key_tests() {
assert_eq!(
Duration::ZERO,
Duration::from_ord_bytes(ByteSource::Borrowed(
&Duration::ZERO.as_ord_bytes().unwrap()
))
.unwrap()
);
assert_eq!(
Duration::MAX,
Duration::from_ord_bytes(ByteSource::Borrowed(&Duration::MAX.as_ord_bytes().unwrap()))
.unwrap()
);
}
impl<'k> Key<'k> for SystemTime {
const CAN_OWN_BYTES: bool = false;
fn from_ord_bytes<'e>(bytes: ByteSource<'k, 'e>) -> Result<Self, Self::Error> {
let since_epoch = Duration::from_ord_bytes(bytes)?;
UNIX_EPOCH
.checked_add(since_epoch)
.ok_or(TimeError::DeltaNotRepresentable)
}
}
impl KeyEncoding<Self> for SystemTime {
type Error = TimeError;
const LENGTH: Option<usize> = None;
fn describe<Visitor>(visitor: &mut Visitor)
where
Visitor: KeyVisitor,
{
visitor.visit_composite(
CompositeKind::Struct(Cow::Borrowed("std::time::SystemTime")),
1,
);
visitor.visit_type(KeyKind::Unsigned);
}
fn as_ord_bytes(&self) -> Result<Cow<'_, [u8]>, Self::Error> {
let since_epoch = self.duration_since(UNIX_EPOCH).unwrap();
match since_epoch.as_ord_bytes()? {
Cow::Owned(bytes) => Ok(Cow::Owned(bytes)),
Cow::Borrowed(_) => unreachable!(),
}
}
}
#[test]
fn system_time_tests() {
assert_eq!(
UNIX_EPOCH,
SystemTime::from_ord_bytes(ByteSource::Borrowed(&UNIX_EPOCH.as_ord_bytes().unwrap()))
.unwrap()
);
let now = SystemTime::now();
assert_eq!(
now,
SystemTime::from_ord_bytes(ByteSource::Borrowed(&now.as_ord_bytes().unwrap())).unwrap()
);
}
#[derive(thiserror::Error, Debug)]
#[error("the stored timestamp is outside the allowed range")]
pub struct DeltaNotRepresentable;
#[derive(thiserror::Error, Debug, Clone, Serialize, Deserialize)]
pub enum TimeError {
#[error("the stored timestamp is outside the allowed range")]
DeltaNotRepresentable,
#[error("invalid value")]
InvalidValue,
}
impl From<DeltaNotRepresentable> for TimeError {
fn from(_: DeltaNotRepresentable) -> Self {
Self::DeltaNotRepresentable
}
}
impl From<std::io::Error> for TimeError {
fn from(_: std::io::Error) -> Self {
Self::InvalidValue
}
}
pub mod limited {
use std::borrow::Cow;
use std::fmt::{self, Debug, Display, Write};
use std::hash::Hash;
use std::iter;
use std::marker::PhantomData;
use std::str::FromStr;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use derive_where::derive_where;
use ordered_varint::Variable;
use serde::{Deserialize, Serialize};
use crate::key::time::TimeError;
use crate::key::{ByteSource, CompositeKind, Key, KeyEncoding, KeyVisitor};
#[derive_where(Hash, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
pub struct LimitedResolutionDuration<Resolution: TimeResolution> {
representation: Resolution::Representation,
_resolution: PhantomData<Resolution>,
}
pub trait TimeResolution: Debug + Send + Sync {
type Representation: Variable
+ Serialize
+ for<'de> Deserialize<'de>
+ for<'k> Key<'k>
+ Display
+ Hash
+ Eq
+ PartialEq
+ Ord
+ PartialOrd
+ Clone
+ Copy
+ Send
+ Sync
+ Debug
+ Default;
const FORMAT_SUFFIX: &'static str;
fn repr_to_duration(value: Self::Representation) -> Result<SignedDuration, TimeError>;
fn duration_to_repr(duration: SignedDuration) -> Result<Self::Representation, TimeError>;
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SignedDuration {
Positive(Duration),
Negative(Duration),
}
impl SignedDuration {
#[must_use]
pub fn checked_add(self, other: Self) -> Option<Self> {
match (self, other) {
(SignedDuration::Positive(a), SignedDuration::Positive(b)) => {
a.checked_add(b).map(SignedDuration::Positive)
}
(SignedDuration::Negative(a), SignedDuration::Negative(b)) => {
a.checked_add(b).map(SignedDuration::Negative)
}
(SignedDuration::Positive(a), SignedDuration::Negative(b)) => {
if let Some(result) = a.checked_sub(b) {
Some(SignedDuration::Positive(result))
} else {
Some(SignedDuration::Negative(b - a))
}
}
(SignedDuration::Negative(a), SignedDuration::Positive(b)) => {
if let Some(result) = a.checked_sub(b) {
Some(SignedDuration::Negative(result))
} else {
Some(SignedDuration::Positive(b - a))
}
}
}
}
}
impl<Resolution> LimitedResolutionDuration<Resolution>
where
Resolution: TimeResolution,
{
pub const fn new(representation: Resolution::Representation) -> Self {
Self {
representation,
_resolution: PhantomData,
}
}
pub const fn representation(&self) -> Resolution::Representation {
self.representation
}
}
impl<Resolution: TimeResolution> Debug for LimitedResolutionDuration<Resolution> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}{}", self.representation, Resolution::FORMAT_SUFFIX)
}
}
impl<Resolution: TimeResolution> Display for LimitedResolutionDuration<Resolution> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.representation, Resolution::FORMAT_SUFFIX)
}
}
impl iter::Sum<SignedDuration> for Option<SignedDuration> {
fn sum<I: Iterator<Item = SignedDuration>>(mut iter: I) -> Self {
let first = iter.next();
iter.fold(first, |sum, duration| {
sum.and_then(|sum| sum.checked_add(duration))
})
}
}
impl iter::Sum<Self> for SignedDuration {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.sum::<Option<Self>>().expect("operation overflowed")
}
}
impl<'k, Resolution> Key<'k> for LimitedResolutionDuration<Resolution>
where
Resolution: TimeResolution,
{
const CAN_OWN_BYTES: bool = false;
fn from_ord_bytes<'e>(bytes: ByteSource<'k, 'e>) -> Result<Self, Self::Error> {
let representation =
<Resolution::Representation as Variable>::decode_variable(bytes.as_ref())
.map_err(|_| TimeError::InvalidValue)?;
Ok(Self {
representation,
_resolution: PhantomData,
})
}
}
impl<Resolution> KeyEncoding<Self> for LimitedResolutionDuration<Resolution>
where
Resolution: TimeResolution,
{
type Error = TimeError;
const LENGTH: Option<usize> = None;
fn describe<Visitor>(visitor: &mut Visitor)
where
Visitor: KeyVisitor,
{
visitor.visit_composite(
CompositeKind::Struct(Cow::Borrowed(
"bonsaidb::core::key::time::LimitedResolutionDuration",
)),
1,
);
<Resolution::Representation as KeyEncoding>::describe(visitor);
}
fn as_ord_bytes(&self) -> Result<Cow<'_, [u8]>, Self::Error> {
self.representation
.to_variable_vec()
.map(Cow::Owned)
.map_err(|_| TimeError::InvalidValue)
}
}
impl<Resolution> Default for LimitedResolutionDuration<Resolution>
where
Resolution: TimeResolution,
{
fn default() -> Self {
Self {
representation: <Resolution::Representation as Default>::default(),
_resolution: PhantomData,
}
}
}
impl<Resolution> Serialize for LimitedResolutionDuration<Resolution>
where
Resolution: TimeResolution,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.representation.serialize(serializer)
}
}
impl<'de, Resolution> Deserialize<'de> for LimitedResolutionDuration<Resolution>
where
Resolution: TimeResolution,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
<Resolution::Representation as Deserialize<'de>>::deserialize(deserializer)
.map(Self::new)
}
}
impl<Resolution> TryFrom<SignedDuration> for LimitedResolutionDuration<Resolution>
where
Resolution: TimeResolution,
{
type Error = TimeError;
fn try_from(duration: SignedDuration) -> Result<Self, Self::Error> {
Resolution::duration_to_repr(duration).map(|representation| Self {
representation,
_resolution: PhantomData,
})
}
}
impl<Resolution> TryFrom<LimitedResolutionDuration<Resolution>> for SignedDuration
where
Resolution: TimeResolution,
{
type Error = TimeError;
fn try_from(value: LimitedResolutionDuration<Resolution>) -> Result<Self, Self::Error> {
Resolution::repr_to_duration(value.representation)
}
}
impl<Resolution> TryFrom<Duration> for LimitedResolutionDuration<Resolution>
where
Resolution: TimeResolution,
{
type Error = TimeError;
fn try_from(duration: Duration) -> Result<Self, Self::Error> {
Self::try_from(SignedDuration::Positive(duration))
}
}
impl<Resolution> TryFrom<LimitedResolutionDuration<Resolution>> for Duration
where
Resolution: TimeResolution,
{
type Error = TimeError;
fn try_from(value: LimitedResolutionDuration<Resolution>) -> Result<Self, Self::Error> {
match SignedDuration::try_from(value) {
Ok(SignedDuration::Positive(value)) => Ok(value),
_ => Err(TimeError::DeltaNotRepresentable),
}
}
}
impl<Resolution> iter::Sum<LimitedResolutionDuration<Resolution>>
for Option<LimitedResolutionDuration<Resolution>>
where
Resolution: TimeResolution,
{
fn sum<I: Iterator<Item = LimitedResolutionDuration<Resolution>>>(mut iter: I) -> Self {
let first = iter
.next()
.and_then(|dur| Resolution::repr_to_duration(dur.representation).ok());
let duration = iter.fold(first, |sum, dur| {
sum.and_then(|sum| {
Resolution::repr_to_duration(dur.representation)
.ok()
.and_then(|dur| sum.checked_add(dur))
})
});
duration.and_then(|dur| {
Resolution::duration_to_repr(dur)
.ok()
.map(LimitedResolutionDuration::new)
})
}
}
impl<Resolution> iter::Sum<Self> for LimitedResolutionDuration<Resolution>
where
Resolution: TimeResolution,
{
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.sum::<Option<Self>>().expect("operation overflowed")
}
}
#[test]
fn limited_resolution_duration_sum() {
use super::Nanoseconds;
assert_eq!(
[
Nanoseconds::new(1),
Nanoseconds::new(2),
Nanoseconds::new(3),
]
.into_iter()
.sum::<Nanoseconds>(),
Nanoseconds::new(6)
);
assert_eq!(
[
Nanoseconds::new(1),
Nanoseconds::new(2),
Nanoseconds::new(3),
]
.into_iter()
.sum::<Option<Nanoseconds>>(),
Some(Nanoseconds::new(6))
);
assert_eq!(
[Nanoseconds::new(1), Nanoseconds::new(i64::MAX)]
.into_iter()
.sum::<Option<Nanoseconds>>(),
None
);
assert_eq!(
[Nanoseconds::new(i64::MAX), Nanoseconds::new(1)]
.into_iter()
.sum::<Option<Nanoseconds>>(),
None
);
assert_eq!(
[Nanoseconds::new(i64::MIN), Nanoseconds::new(-11)]
.into_iter()
.sum::<Option<Nanoseconds>>(),
None
);
assert_eq!(
[Nanoseconds::new(1), Nanoseconds::new(i64::MIN)]
.into_iter()
.sum::<Option<Nanoseconds>>(),
Some(Nanoseconds::new(i64::MIN + 1))
);
assert_eq!(
[Nanoseconds::new(i64::MIN), Nanoseconds::new(1)]
.into_iter()
.sum::<Option<Nanoseconds>>(),
Some(Nanoseconds::new(i64::MIN + 1))
);
}
#[derive(Debug)]
pub enum Nanoseconds {}
const I64_MIN_ABS_AS_U64: u64 = 9_223_372_036_854_775_808;
impl TimeResolution for Nanoseconds {
type Representation = i64;
const FORMAT_SUFFIX: &'static str = "ns";
fn repr_to_duration(value: Self::Representation) -> Result<SignedDuration, TimeError> {
if let Ok(unsigned) = u64::try_from(value) {
Ok(SignedDuration::Positive(Duration::from_nanos(unsigned)))
} else {
let positive = value
.checked_abs()
.and_then(|value| u64::try_from(value).ok())
.unwrap_or(I64_MIN_ABS_AS_U64);
Ok(SignedDuration::Negative(Duration::from_nanos(positive)))
}
}
fn duration_to_repr(duration: SignedDuration) -> Result<Self::Representation, TimeError> {
match duration {
SignedDuration::Positive(duration) => {
i64::try_from(duration.as_nanos()).map_err(|_| TimeError::DeltaNotRepresentable)
}
SignedDuration::Negative(duration) => {
let nanos = duration.as_nanos();
if let Ok(nanos) = i64::try_from(nanos) {
Ok(-nanos)
} else if nanos == u128::from(I64_MIN_ABS_AS_U64) {
Ok(i64::MIN)
} else {
Err(TimeError::DeltaNotRepresentable)
}
}
}
}
}
#[derive(Debug)]
pub enum Microseconds {}
impl TimeResolution for Microseconds {
type Representation = i64;
const FORMAT_SUFFIX: &'static str = "us";
fn repr_to_duration(value: Self::Representation) -> Result<SignedDuration, TimeError> {
if let Ok(unsigned) = u64::try_from(value) {
Ok(SignedDuration::Positive(Duration::from_micros(unsigned)))
} else {
let positive = value
.checked_abs()
.and_then(|value| u64::try_from(value).ok())
.unwrap_or(u64::MAX);
Ok(SignedDuration::Negative(Duration::from_micros(positive)))
}
}
fn duration_to_repr(duration: SignedDuration) -> Result<Self::Representation, TimeError> {
match duration {
SignedDuration::Positive(duration) => i64::try_from(duration.as_micros())
.map_err(|_| TimeError::DeltaNotRepresentable),
SignedDuration::Negative(duration) => {
let rounded_up = duration
.checked_add(Duration::from_nanos(999))
.ok_or(TimeError::DeltaNotRepresentable)?;
i64::try_from(rounded_up.as_micros())
.map(|repr| -repr)
.map_err(|_| TimeError::DeltaNotRepresentable)
}
}
}
}
#[derive(Debug)]
pub enum Milliseconds {}
impl TimeResolution for Milliseconds {
type Representation = i64;
const FORMAT_SUFFIX: &'static str = "ms";
fn repr_to_duration(value: Self::Representation) -> Result<SignedDuration, TimeError> {
if let Ok(unsigned) = u64::try_from(value) {
Ok(SignedDuration::Positive(Duration::from_millis(unsigned)))
} else {
let positive = value
.checked_abs()
.and_then(|value| u64::try_from(value).ok())
.unwrap_or(u64::MAX);
Ok(SignedDuration::Negative(Duration::from_millis(positive)))
}
}
fn duration_to_repr(duration: SignedDuration) -> Result<Self::Representation, TimeError> {
match duration {
SignedDuration::Positive(duration) => i64::try_from(duration.as_millis())
.map_err(|_| TimeError::DeltaNotRepresentable),
SignedDuration::Negative(duration) => {
let rounded_up = duration
.checked_add(Duration::from_nanos(999_999))
.ok_or(TimeError::DeltaNotRepresentable)?;
i64::try_from(rounded_up.as_millis())
.map(|repr| -repr)
.map_err(|_| TimeError::DeltaNotRepresentable)
}
}
}
}
#[derive(Debug)]
pub enum Seconds {}
impl TimeResolution for Seconds {
type Representation = i64;
const FORMAT_SUFFIX: &'static str = "s";
fn repr_to_duration(value: Self::Representation) -> Result<SignedDuration, TimeError> {
if let Ok(unsigned) = u64::try_from(value) {
Ok(SignedDuration::Positive(Duration::from_secs(unsigned)))
} else {
let positive = value
.checked_abs()
.and_then(|value| u64::try_from(value).ok())
.unwrap_or(u64::MAX);
Ok(SignedDuration::Negative(Duration::from_secs(positive)))
}
}
fn duration_to_repr(duration: SignedDuration) -> Result<Self::Representation, TimeError> {
match duration {
SignedDuration::Positive(duration) => {
i64::try_from(duration.as_secs()).map_err(|_| TimeError::DeltaNotRepresentable)
}
SignedDuration::Negative(duration) => {
let rounded_up = duration
.checked_add(Duration::from_nanos(999_999_999))
.ok_or(TimeError::DeltaNotRepresentable)?;
i64::try_from(rounded_up.as_secs())
.map(|repr| -repr)
.map_err(|_| TimeError::DeltaNotRepresentable)
}
}
}
}
#[derive(Debug)]
pub enum Minutes {}
impl TimeResolution for Minutes {
type Representation = i32;
const FORMAT_SUFFIX: &'static str = "m";
fn repr_to_duration(value: Self::Representation) -> Result<SignedDuration, TimeError> {
if let Ok(unsigned) = u64::try_from(value) {
Ok(SignedDuration::Positive(Duration::from_secs(unsigned * 60)))
} else {
let positive = u64::try_from(i64::from(value).abs()).unwrap();
Ok(SignedDuration::Negative(Duration::from_secs(positive * 60)))
}
}
fn duration_to_repr(duration: SignedDuration) -> Result<Self::Representation, TimeError> {
match duration {
SignedDuration::Positive(duration) => i32::try_from(duration.as_secs() / 60)
.map_err(|_| TimeError::DeltaNotRepresentable),
SignedDuration::Negative(duration) => i32::try_from((duration.as_secs() + 59) / 60)
.map(|repr| -repr)
.map_err(|_| TimeError::DeltaNotRepresentable),
}
}
}
#[derive(Debug)]
pub enum Hours {}
impl TimeResolution for Hours {
type Representation = i32;
const FORMAT_SUFFIX: &'static str = "h";
fn repr_to_duration(value: Self::Representation) -> Result<SignedDuration, TimeError> {
if let Ok(unsigned) = u64::try_from(value) {
Ok(SignedDuration::Positive(Duration::from_secs(
unsigned * 60 * 60,
)))
} else {
let positive = u64::try_from(i64::from(value).abs()).unwrap();
Ok(SignedDuration::Negative(Duration::from_secs(
positive * 60 * 60,
)))
}
}
fn duration_to_repr(duration: SignedDuration) -> Result<Self::Representation, TimeError> {
const FACTOR: u64 = 60 * 60;
match duration {
SignedDuration::Positive(duration) => i32::try_from(duration.as_secs() / FACTOR)
.map_err(|_| TimeError::DeltaNotRepresentable),
SignedDuration::Negative(duration) => {
i32::try_from((duration.as_secs() + FACTOR - 1) / FACTOR)
.map(|repr| -repr)
.map_err(|_| TimeError::DeltaNotRepresentable)
}
}
}
}
#[derive(Debug)]
pub enum Days {}
impl TimeResolution for Days {
type Representation = i32;
const FORMAT_SUFFIX: &'static str = "d";
fn repr_to_duration(value: Self::Representation) -> Result<SignedDuration, TimeError> {
if let Ok(unsigned) = u64::try_from(value) {
Ok(SignedDuration::Positive(Duration::from_secs(
unsigned * 24 * 60 * 60,
)))
} else {
let positive = u64::try_from(i64::from(value).abs()).unwrap();
Ok(SignedDuration::Negative(Duration::from_secs(
positive * 24 * 60 * 60,
)))
}
}
fn duration_to_repr(duration: SignedDuration) -> Result<Self::Representation, TimeError> {
const FACTOR: u64 = 24 * 60 * 60;
match duration {
SignedDuration::Positive(duration) => i32::try_from(duration.as_secs() / FACTOR)
.map_err(|_| TimeError::DeltaNotRepresentable),
SignedDuration::Negative(duration) => {
Ok(i32::try_from((duration.as_secs() + FACTOR - 1) / FACTOR)
.map_or(i32::MIN, |repr| -repr))
}
}
}
}
#[derive(Debug)]
pub enum Weeks {}
impl TimeResolution for Weeks {
type Representation = i32;
const FORMAT_SUFFIX: &'static str = "w";
fn repr_to_duration(value: Self::Representation) -> Result<SignedDuration, TimeError> {
if let Ok(unsigned) = u64::try_from(value) {
Ok(SignedDuration::Positive(Duration::from_secs(
unsigned * 7 * 24 * 60 * 60,
)))
} else {
let positive = u64::try_from(i64::from(value).abs()).unwrap();
Ok(SignedDuration::Negative(Duration::from_secs(
positive * 7 * 24 * 60 * 60,
)))
}
}
fn duration_to_repr(duration: SignedDuration) -> Result<Self::Representation, TimeError> {
const FACTOR: u64 = 7 * 24 * 60 * 60;
match duration {
SignedDuration::Positive(duration) => i32::try_from(duration.as_secs() / FACTOR)
.map_err(|_| TimeError::DeltaNotRepresentable),
SignedDuration::Negative(duration) => {
i32::try_from((duration.as_secs() + FACTOR - 1) / FACTOR)
.map(|repr| -repr)
.map_err(|_| TimeError::DeltaNotRepresentable)
}
}
}
}
#[test]
fn limited_resolution_duration_tests() {
fn test_limited<Resolution: TimeResolution>(
duration: Duration,
expected_step: Resolution::Representation,
) {
let limited = LimitedResolutionDuration::<Resolution>::try_from(duration).unwrap();
assert_eq!(limited.representation, expected_step);
let encoded = limited.as_ord_bytes().unwrap();
println!("Encoded {limited:?} to {} bytes", encoded.len());
let decoded =
LimitedResolutionDuration::from_ord_bytes(ByteSource::Borrowed(&encoded)).unwrap();
assert_eq!(limited, decoded);
}
fn test_eq_limited<Resolution: TimeResolution>(
duration: Duration,
expected_step: Resolution::Representation,
) {
test_limited::<Resolution>(duration, expected_step);
let limited = LimitedResolutionDuration::<Resolution>::try_from(duration).unwrap();
assert_eq!(duration, Duration::try_from(limited).unwrap());
}
let truncating_seconds = 7 * 24 * 60 * 60 + 24 * 60 * 60 + 60 * 60 + 60 + 1;
let truncating = Duration::new(u64::try_from(truncating_seconds).unwrap(), 987_654_321);
test_limited::<Weeks>(truncating, 1);
test_limited::<Days>(truncating, 8);
test_limited::<Hours>(truncating, 8 * 24 + 1);
test_limited::<Minutes>(truncating, 8 * 24 * 60 + 60 + 1);
test_limited::<Seconds>(truncating, 8 * 24 * 60 * 60 + 60 * 60 + 60 + 1);
test_limited::<Milliseconds>(truncating, truncating_seconds * 1_000 + 987);
test_limited::<Microseconds>(truncating, truncating_seconds * 1_000_000 + 987_654);
let forty_two_days = Duration::from_secs(42 * 24 * 60 * 60);
test_eq_limited::<Weeks>(forty_two_days, 6);
test_eq_limited::<Days>(forty_two_days, 42);
test_eq_limited::<Hours>(forty_two_days, 42 * 24);
test_eq_limited::<Minutes>(forty_two_days, 42 * 24 * 60);
test_eq_limited::<Seconds>(forty_two_days, 42 * 24 * 60 * 60);
test_eq_limited::<Milliseconds>(forty_two_days, 42 * 24 * 60 * 60 * 1_000);
test_eq_limited::<Microseconds>(forty_two_days, 42 * 24 * 60 * 60 * 1_000_000);
}
#[derive_where(Hash, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
pub struct LimitedResolutionTimestamp<Resolution: TimeResolution, Epoch: TimeEpoch>(
LimitedResolutionDuration<Resolution>,
PhantomData<Epoch>,
);
impl<Resolution, Epoch> Default for LimitedResolutionTimestamp<Resolution, Epoch>
where
Resolution: TimeResolution,
Epoch: TimeEpoch,
{
fn default() -> Self {
Self(LimitedResolutionDuration::default(), PhantomData)
}
}
impl<Resolution, Epoch> Serialize for LimitedResolutionTimestamp<Resolution, Epoch>
where
Resolution: TimeResolution,
Epoch: TimeEpoch,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de, Resolution, Epoch> Deserialize<'de> for LimitedResolutionTimestamp<Resolution, Epoch>
where
Resolution: TimeResolution,
Epoch: TimeEpoch,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
LimitedResolutionDuration::deserialize(deserializer)
.map(|duration| Self(duration, PhantomData))
}
}
pub trait TimeEpoch: Sized + Send + Sync {
fn name() -> &'static str;
fn epoch_offset() -> Duration;
}
impl<Resolution, Epoch> LimitedResolutionTimestamp<Resolution, Epoch>
where
Resolution: TimeResolution,
Epoch: TimeEpoch,
{
#[must_use]
pub fn now() -> Self {
Self::try_from(SystemTime::now()).expect("now should always be representable")
}
pub fn duration_since(
&self,
other: &impl AnyTimestamp,
) -> Result<Option<Duration>, TimeError> {
let self_delta = self.duration_since_unix_epoch()?;
let other_delta = other.duration_since_unix_epoch()?;
Ok(self_delta.checked_sub(other_delta))
}
pub fn duration_between(&self, other: &impl AnyTimestamp) -> Result<Duration, TimeError> {
let self_delta = self.duration_since_unix_epoch()?;
let other_delta = other.duration_since_unix_epoch()?;
if self_delta < other_delta {
Ok(other_delta - self_delta)
} else {
Ok(self_delta - other_delta)
}
}
pub const fn representation(&self) -> Resolution::Representation {
self.0.representation()
}
pub fn from_representation(representation: Resolution::Representation) -> Self {
Self::from(LimitedResolutionDuration::new(representation))
}
pub fn to_timestamp_string(&self) -> Result<String, TimeError> {
let mut string = String::new();
self.display(&mut string).map(|()| string)
}
fn display(&self, f: &mut impl Write) -> Result<(), TimeError> {
let since_epoch = self.duration_since_unix_epoch()?;
write!(f, "{}", since_epoch.as_secs()).map_err(|_| TimeError::InvalidValue)?;
if since_epoch.subsec_nanos() > 0 {
if since_epoch.subsec_nanos() % 1_000_000 == 0 {
write!(f, ".{:03}", since_epoch.subsec_millis())
.map_err(|_| TimeError::InvalidValue)
} else if since_epoch.subsec_nanos() % 1_000 == 0 {
write!(f, ".{:06}", since_epoch.subsec_micros())
.map_err(|_| TimeError::InvalidValue)
} else {
write!(f, ".{:09}", since_epoch.subsec_nanos())
.map_err(|_| TimeError::InvalidValue)
}
} else {
Ok(())
}
}
}
pub trait AnyTimestamp {
fn duration_since_unix_epoch(&self) -> Result<Duration, TimeError>;
}
impl AnyTimestamp for SystemTime {
fn duration_since_unix_epoch(&self) -> Result<Duration, TimeError> {
Ok(self.duration_since(UNIX_EPOCH).unwrap())
}
}
impl<Resolution, Epoch> AnyTimestamp for LimitedResolutionTimestamp<Resolution, Epoch>
where
Resolution: TimeResolution,
Epoch: TimeEpoch,
{
fn duration_since_unix_epoch(&self) -> Result<Duration, TimeError> {
let relative_offset = Resolution::repr_to_duration(self.0.representation)?;
match relative_offset {
SignedDuration::Positive(offset) => Epoch::epoch_offset()
.checked_add(offset)
.ok_or(TimeError::DeltaNotRepresentable),
SignedDuration::Negative(offset) => Epoch::epoch_offset()
.checked_sub(offset)
.ok_or(TimeError::DeltaNotRepresentable),
}
}
}
impl<Resolution, Epoch> Debug for LimitedResolutionTimestamp<Resolution, Epoch>
where
Resolution: TimeResolution,
Epoch: TimeEpoch,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "LimitedResolutionTimestamp({self})")
}
}
impl<Resolution, Epoch> Display for LimitedResolutionTimestamp<Resolution, Epoch>
where
Resolution: TimeResolution,
Epoch: TimeEpoch,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f).or_else(|_| Display::fmt(&self.0, f))
}
}
impl<Resolution, Epoch> FromStr for LimitedResolutionTimestamp<Resolution, Epoch>
where
Resolution: TimeResolution,
Epoch: TimeEpoch,
{
type Err = TimeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split('.');
let seconds = parts.next().ok_or(TimeError::InvalidValue)?;
let seconds = seconds
.parse::<u64>()
.map_err(|_| TimeError::InvalidValue)?;
let duration = if let Some(subseconds_str) = parts.next() {
if subseconds_str.len() > 9 || parts.next().is_some() {
return Err(TimeError::InvalidValue);
}
let subseconds = subseconds_str
.parse::<u32>()
.map_err(|_| TimeError::InvalidValue)?;
let nanos =
subseconds * 10_u32.pow(u32::try_from(9 - subseconds_str.len()).unwrap());
Duration::new(seconds, nanos)
} else {
Duration::from_secs(seconds)
};
let epoch = Epoch::epoch_offset();
let duration = if duration < epoch {
SignedDuration::Negative(epoch - duration)
} else {
SignedDuration::Positive(duration - epoch)
};
Ok(Self::from(
LimitedResolutionDuration::<Resolution>::try_from(duration)?,
))
}
}
#[test]
fn timestamp_parse_tests() {
fn test_roundtrip_parsing<Resolution: TimeResolution>() {
let original = LimitedResolutionTimestamp::<Resolution, BonsaiEpoch>::now();
let unix_timestamp = original.to_string();
let parsed = unix_timestamp.parse().unwrap();
assert_eq!(
original, parsed,
"{original} produced {unix_timestamp}, but parsed {parsed}"
);
}
test_roundtrip_parsing::<Weeks>();
test_roundtrip_parsing::<Days>();
test_roundtrip_parsing::<Minutes>();
test_roundtrip_parsing::<Seconds>();
test_roundtrip_parsing::<Milliseconds>();
test_roundtrip_parsing::<Microseconds>();
test_roundtrip_parsing::<Nanoseconds>();
}
impl<'k, Resolution, Epoch> Key<'k> for LimitedResolutionTimestamp<Resolution, Epoch>
where
Resolution: TimeResolution,
Epoch: TimeEpoch,
{
const CAN_OWN_BYTES: bool = false;
fn from_ord_bytes<'e>(bytes: ByteSource<'k, 'e>) -> Result<Self, Self::Error> {
let duration = LimitedResolutionDuration::<Resolution>::from_ord_bytes(bytes)?;
Ok(Self::from(duration))
}
}
impl<Resolution, Epoch> KeyEncoding<Self> for LimitedResolutionTimestamp<Resolution, Epoch>
where
Resolution: TimeResolution,
Epoch: TimeEpoch,
{
type Error = TimeError;
const LENGTH: Option<usize> = None;
fn describe<Visitor>(visitor: &mut Visitor)
where
Visitor: KeyVisitor,
{
visitor.visit_composite(
CompositeKind::Struct(Cow::Borrowed(
"bonsaidb::core::key::time::LimitedResolutionTimestamp",
)),
1,
);
visitor.visit_composite_attribute("epoch", Epoch::epoch_offset().as_nanos());
<Resolution::Representation as KeyEncoding>::describe(visitor);
}
fn as_ord_bytes(&self) -> Result<Cow<'_, [u8]>, Self::Error> {
self.0.as_ord_bytes()
}
}
impl<Resolution, Epoch> From<LimitedResolutionDuration<Resolution>>
for LimitedResolutionTimestamp<Resolution, Epoch>
where
Resolution: TimeResolution,
Epoch: TimeEpoch,
{
fn from(duration: LimitedResolutionDuration<Resolution>) -> Self {
Self(duration, PhantomData)
}
}
impl<Resolution, Epoch> From<LimitedResolutionTimestamp<Resolution, Epoch>>
for LimitedResolutionDuration<Resolution>
where
Resolution: TimeResolution,
Epoch: TimeEpoch,
{
fn from(time: LimitedResolutionTimestamp<Resolution, Epoch>) -> Self {
time.0
}
}
impl<Resolution, Epoch> TryFrom<SystemTime> for LimitedResolutionTimestamp<Resolution, Epoch>
where
Resolution: TimeResolution,
Epoch: TimeEpoch,
{
type Error = TimeError;
fn try_from(time: SystemTime) -> Result<Self, TimeError> {
let epoch = UNIX_EPOCH
.checked_add(Epoch::epoch_offset())
.ok_or(TimeError::DeltaNotRepresentable)?;
match time.duration_since(epoch) {
Ok(duration) => {
LimitedResolutionDuration::try_from(SignedDuration::Positive(duration))
.map(Self::from)
}
Err(_) => match epoch.duration_since(time) {
Ok(duration) => {
LimitedResolutionDuration::try_from(SignedDuration::Negative(duration))
.map(Self::from)
}
Err(_) => Err(TimeError::DeltaNotRepresentable),
},
}
}
}
impl<Resolution, Epoch> TryFrom<LimitedResolutionTimestamp<Resolution, Epoch>> for SystemTime
where
Resolution: TimeResolution,
Epoch: TimeEpoch,
{
type Error = TimeError;
fn try_from(
time: LimitedResolutionTimestamp<Resolution, Epoch>,
) -> Result<Self, TimeError> {
let since_epoch = SignedDuration::try_from(time.0)?;
let epoch = UNIX_EPOCH
.checked_add(Epoch::epoch_offset())
.ok_or(TimeError::DeltaNotRepresentable)?;
let time = match since_epoch {
SignedDuration::Positive(since_epoch) => epoch.checked_add(since_epoch),
SignedDuration::Negative(since_epoch) => epoch.checked_sub(since_epoch),
};
time.ok_or(TimeError::DeltaNotRepresentable)
}
}
pub struct UnixEpoch;
impl TimeEpoch for UnixEpoch {
fn name() -> &'static str {
"Unix"
}
fn epoch_offset() -> Duration {
Duration::ZERO
}
}
pub struct BonsaiEpoch;
impl BonsaiEpoch {
const EPOCH: Duration = Duration::new(1_931_747_507, 0);
}
impl TimeEpoch for BonsaiEpoch {
fn name() -> &'static str {
"BonsaiDb"
}
fn epoch_offset() -> Duration {
Self::EPOCH
}
}
#[test]
fn limited_resolution_timestamp_tests() {
fn test_resolution<Resolution: TimeResolution>(resolution: Duration) {
let now_in_seconds = LimitedResolutionTimestamp::<Resolution, UnixEpoch>::now();
let as_system = SystemTime::try_from(now_in_seconds).unwrap();
let as_limited =
LimitedResolutionTimestamp::<Resolution, UnixEpoch>::try_from(as_system).unwrap();
assert_eq!(as_limited, now_in_seconds);
let now_in_seconds = LimitedResolutionTimestamp::<Resolution, BonsaiEpoch>::now();
let as_system = SystemTime::try_from(now_in_seconds).unwrap();
let as_limited =
LimitedResolutionTimestamp::<Resolution, BonsaiEpoch>::try_from(as_system).unwrap();
assert_eq!(as_limited, now_in_seconds);
let slightly_before_epoch = UNIX_EPOCH + BonsaiEpoch::EPOCH
- Duration::from_nanos(u64::try_from(resolution.as_nanos() / 2).unwrap());
let unix_epoch_in_recent =
LimitedResolutionTimestamp::<Resolution, BonsaiEpoch>::try_from(
slightly_before_epoch,
)
.unwrap();
let as_system = SystemTime::try_from(unix_epoch_in_recent).unwrap();
let as_limited =
LimitedResolutionTimestamp::<Resolution, BonsaiEpoch>::try_from(as_system).unwrap();
assert!(
slightly_before_epoch
.duration_since(as_system)
.expect("timestamp should have been trunctated towards MIN")
< resolution
);
assert_eq!(as_limited, unix_epoch_in_recent);
let slightly_after_epoch = UNIX_EPOCH
+ BonsaiEpoch::EPOCH
+ Duration::from_nanos(u64::try_from(resolution.as_nanos() / 2).unwrap());
let unix_epoch_in_recent =
LimitedResolutionTimestamp::<Resolution, BonsaiEpoch>::try_from(
slightly_after_epoch,
)
.unwrap();
let as_system = SystemTime::try_from(unix_epoch_in_recent).unwrap();
println!("{slightly_after_epoch:?} converted to {unix_epoch_in_recent} and back as {as_system:?}");
let as_limited =
LimitedResolutionTimestamp::<Resolution, BonsaiEpoch>::try_from(as_system).unwrap();
assert!(
slightly_after_epoch
.duration_since(as_system)
.expect("timestamp should have been truncated towards 0")
< resolution
);
assert_eq!(as_limited, unix_epoch_in_recent);
}
test_resolution::<Weeks>(Duration::from_secs(7 * 24 * 60 * 60));
test_resolution::<Days>(Duration::from_secs(24 * 60 * 60));
test_resolution::<Hours>(Duration::from_secs(60 * 60));
test_resolution::<Minutes>(Duration::from_secs(60));
test_resolution::<Seconds>(Duration::from_secs(1));
test_resolution::<Milliseconds>(Duration::from_millis(1));
test_resolution::<Microseconds>(Duration::from_micros(1));
test_resolution::<Nanoseconds>(Duration::from_nanos(1));
}
#[test]
fn serialization_tests() {
fn test_serialization<Resolution: TimeResolution>() {
let original = LimitedResolutionTimestamp::<Resolution, BonsaiEpoch>::now();
let serialized = pot::to_vec(&original).unwrap();
let deserialized = pot::from_slice(&serialized).unwrap();
assert_eq!(original, deserialized);
}
test_serialization::<Weeks>();
test_serialization::<Days>();
test_serialization::<Hours>();
test_serialization::<Minutes>();
test_serialization::<Seconds>();
test_serialization::<Milliseconds>();
test_serialization::<Microseconds>();
test_serialization::<Nanoseconds>();
}
}
pub type Weeks = limited::LimitedResolutionDuration<limited::Weeks>;
pub type Days = limited::LimitedResolutionDuration<limited::Days>;
pub type Hours = limited::LimitedResolutionDuration<limited::Hours>;
pub type Minutes = limited::LimitedResolutionDuration<limited::Minutes>;
pub type Seconds = limited::LimitedResolutionDuration<limited::Seconds>;
pub type Milliseconds = limited::LimitedResolutionDuration<limited::Milliseconds>;
pub type Microseconds = limited::LimitedResolutionDuration<limited::Microseconds>;
pub type Nanoseconds = limited::LimitedResolutionDuration<limited::Nanoseconds>;
pub type WeeksSinceUnixEpoch = limited::LimitedResolutionTimestamp<limited::Weeks, UnixEpoch>;
pub type DaysSinceUnixEpoch = limited::LimitedResolutionTimestamp<limited::Days, UnixEpoch>;
pub type HoursSinceUnixEpoch = limited::LimitedResolutionTimestamp<limited::Hours, UnixEpoch>;
pub type MinutesSinceUnixEpoch = limited::LimitedResolutionTimestamp<limited::Minutes, UnixEpoch>;
pub type SecondsSinceUnixEpoch = limited::LimitedResolutionTimestamp<limited::Seconds, UnixEpoch>;
pub type MillisecondsSinceUnixEpoch =
limited::LimitedResolutionTimestamp<limited::Milliseconds, UnixEpoch>;
pub type MicrosecondsSinceUnixEpoch =
limited::LimitedResolutionTimestamp<limited::Microseconds, UnixEpoch>;
pub type NanosecondsSinceUnixEpoch =
limited::LimitedResolutionTimestamp<limited::Nanoseconds, UnixEpoch>;
pub type TimestampAsWeeks = limited::LimitedResolutionTimestamp<limited::Weeks, BonsaiEpoch>;
pub type TimestampAsDays = limited::LimitedResolutionTimestamp<limited::Days, BonsaiEpoch>;
pub type TimestampAsHours = limited::LimitedResolutionTimestamp<limited::Hours, BonsaiEpoch>;
pub type TimestampAsMinutes = limited::LimitedResolutionTimestamp<limited::Minutes, BonsaiEpoch>;
pub type TimestampAsSeconds = limited::LimitedResolutionTimestamp<limited::Seconds, BonsaiEpoch>;
pub type TimestampAsMilliseconds =
limited::LimitedResolutionTimestamp<limited::Milliseconds, BonsaiEpoch>;
pub type TimestampAsMicroseconds =
limited::LimitedResolutionTimestamp<limited::Microseconds, BonsaiEpoch>;
pub type TimestampAsNanoseconds =
limited::LimitedResolutionTimestamp<limited::Nanoseconds, BonsaiEpoch>;