use core::num::NonZeroUsize;
use time_0_3::{Date, Duration, Month, PrimitiveDateTime, Time, UtcDateTime};
use crate::{
ConstDecodeError, ConstEncodeError, DecodeError, EncodeError, NON_ZERO_USIZE_ONE, Varint,
time_utils::{self, DurationBuffer},
};
pub use time_utils::{DateBuffer, DateTimeBuffer, TimeBuffer};
macro_rules! impl_varint_for_time {
($($ty:ident($max:expr, $min:expr).$fn:ident), +$(,)?) => {
paste::paste! {
$(
impl Varint for $ty {
const MIN_ENCODED_LEN: NonZeroUsize = {
assert!($min.get() <= $max.get(), concat!("`", stringify!($ty), "::MIN_ENCODED_LEN` must be less than or equal to `", stringify!($ty), "MAX_ENCODED_LEN`"));
$min
};
const MAX_ENCODED_LEN: NonZeroUsize = {
assert!($max.get() >= $min.get(), concat!("`", stringify!($ty), "::MAX_ENCODED_LEN` must be greater than or equal to `", stringify!($ty), "MIN_ENCODED_LEN`"));
$max
};
fn encoded_len(&self) -> NonZeroUsize {
[< encoded_ $fn _len >](self)
}
fn encode(&self, buf: &mut [u8]) -> Result<NonZeroUsize, EncodeError> {
[< encode_ $fn _to >](self, buf).map_err(Into::into)
}
fn decode(buf: &[u8]) -> Result<(NonZeroUsize, Self), DecodeError>
where
Self: Sized,
{
[< decode_ $fn >](buf).map_err(Into::into)
}
}
)*
}
};
}
#[inline]
pub const fn encoded_duration_len(duration: &Duration) -> NonZeroUsize {
time_utils::encoded_secs_and_subsec_nanos_len(
duration.whole_seconds(),
duration.subsec_nanoseconds(),
)
}
#[inline]
pub const fn encode_duration(duration: &Duration) -> DurationBuffer {
time_utils::encode_secs_and_subsec_nanos(duration.whole_seconds(), duration.subsec_nanoseconds())
}
#[inline]
pub const fn encode_duration_to(
duration: &Duration,
buf: &mut [u8],
) -> Result<NonZeroUsize, ConstEncodeError> {
time_utils::encode_secs_and_subsec_nanos_to(
duration.whole_seconds(),
duration.subsec_nanoseconds(),
buf,
)
}
#[inline]
pub const fn decode_duration(buf: &[u8]) -> Result<(NonZeroUsize, Duration), ConstDecodeError> {
match time_utils::decode_secs_and_subsec_nanos(buf) {
Ok((bytes_read, secs, nanos)) => Ok((bytes_read, Duration::new(secs, nanos))),
Err(e) => Err(e),
}
}
#[inline]
pub const fn encoded_date_len(date: &Date) -> NonZeroUsize {
time_utils::encoded_date_len(date.year(), date.month() as u8, date.day())
}
#[inline]
pub const fn encode_date(date: &Date) -> DateBuffer {
time_utils::encode_date(date.year(), date.month() as u8, date.day())
}
#[inline]
pub const fn encode_date_to(date: &Date, buf: &mut [u8]) -> Result<NonZeroUsize, ConstEncodeError> {
time_utils::encode_date_to(date.year(), date.month() as u8, date.day(), buf)
}
#[inline]
pub const fn decode_date(buf: &[u8]) -> Result<(NonZeroUsize, Date), ConstDecodeError> {
match time_utils::decode_date(buf) {
Ok((bytes_read, year, month, day)) => {
let month = match u8_to_month(month) {
Ok(month) => month,
Err(e) => return Err(e),
};
match Date::from_calendar_date(year, month, day) {
Ok(date) => Ok((bytes_read, date)),
Err(_) => Err(ConstDecodeError::other("invalid date value")),
}
}
Err(e) => Err(e),
}
}
#[inline]
pub const fn encoded_utc_len(dt: &UtcDateTime) -> NonZeroUsize {
encoded_datetime_len(&PrimitiveDateTime::new(dt.date(), dt.time()))
}
#[inline]
pub const fn encode_utc(dt: &UtcDateTime) -> DateTimeBuffer {
encode_datetime(&PrimitiveDateTime::new(dt.date(), dt.time()))
}
#[inline]
pub const fn encode_utc_to(
dt: &UtcDateTime,
buf: &mut [u8],
) -> Result<NonZeroUsize, ConstEncodeError> {
encode_datetime_to(&PrimitiveDateTime::new(dt.date(), dt.time()), buf)
}
#[inline]
pub const fn decode_utc(buf: &[u8]) -> Result<(NonZeroUsize, UtcDateTime), ConstDecodeError> {
match decode_datetime(buf) {
Ok((bytes_read, dt)) => Ok((bytes_read, UtcDateTime::new(dt.date(), dt.time()))),
Err(e) => Err(e),
}
}
#[inline]
pub const fn encoded_datetime_len(dt: &PrimitiveDateTime) -> NonZeroUsize {
time_utils::encoded_datetime_len(
dt.year(),
dt.month() as u8,
dt.day(),
dt.hour(),
dt.minute(),
dt.second(),
dt.nanosecond(),
)
}
#[inline]
pub const fn encode_datetime(dt: &PrimitiveDateTime) -> DateTimeBuffer {
time_utils::encode_datetime(
dt.year(),
dt.month() as u8,
dt.day(),
dt.hour(),
dt.minute(),
dt.second(),
dt.nanosecond(),
)
}
#[inline]
pub const fn encode_datetime_to(
dt: &PrimitiveDateTime,
buf: &mut [u8],
) -> Result<NonZeroUsize, ConstEncodeError> {
time_utils::encode_datetime_to(
dt.year(),
dt.month() as u8,
dt.day(),
dt.hour(),
dt.minute(),
dt.second(),
dt.nanosecond(),
buf,
)
}
#[inline]
pub const fn decode_datetime(
buf: &[u8],
) -> Result<(NonZeroUsize, PrimitiveDateTime), ConstDecodeError> {
match time_utils::decode_datetime(buf) {
Ok((bytes_read, year, month, day, hour, minute, second, nano)) => {
let month = match u8_to_month(month) {
Ok(month) => month,
Err(e) => return Err(e),
};
let date = match Date::from_calendar_date(year, month, day) {
Ok(date) => date,
Err(_) => return Err(ConstDecodeError::other("invalid date value")),
};
let time = match Time::from_hms_nano(hour, minute, second, nano) {
Ok(time) => time,
Err(_) => return Err(ConstDecodeError::other("invalid time value")),
};
Ok((bytes_read, PrimitiveDateTime::new(date, time)))
}
Err(e) => Err(e),
}
}
#[inline]
pub const fn encoded_time_len(time: &Time) -> NonZeroUsize {
time_utils::encoded_time_len(time.nanosecond(), time.second(), time.minute(), time.hour())
}
#[inline]
pub const fn encode_time(time: &Time) -> TimeBuffer {
time_utils::encode_time(time.nanosecond(), time.second(), time.minute(), time.hour())
}
#[inline]
pub const fn encode_time_to(time: &Time, buf: &mut [u8]) -> Result<NonZeroUsize, ConstEncodeError> {
time_utils::encode_time_to(
time.nanosecond(),
time.second(),
time.minute(),
time.hour(),
buf,
)
}
#[inline]
pub const fn decode_time(buf: &[u8]) -> Result<(NonZeroUsize, Time), ConstDecodeError> {
match time_utils::decode_time(buf) {
Ok((bytes_read, nano, second, minute, hour)) => {
match Time::from_hms_nano(hour, minute, second, nano) {
Ok(time) => Ok((bytes_read, time)),
Err(_) => Err(ConstDecodeError::other("invalid time value")),
}
}
Err(e) => Err(e),
}
}
impl_varint_for_time!(
Duration(i128::MAX_ENCODED_LEN, i128::MIN_ENCODED_LEN).duration,
Time(TimeBuffer::CAPACITY, u64::MIN_ENCODED_LEN).time,
PrimitiveDateTime(DateTimeBuffer::CAPACITY, NON_ZERO_USIZE_ONE).datetime,
UtcDateTime(i128::MAX_ENCODED_LEN, NON_ZERO_USIZE_ONE).utc,
Date(DateBuffer::CAPACITY, NON_ZERO_USIZE_ONE).date
);
const fn u8_to_month(val: u8) -> Result<Month, ConstDecodeError> {
Ok(match val {
1 => Month::January,
2 => Month::February,
3 => Month::March,
4 => Month::April,
5 => Month::May,
6 => Month::June,
7 => Month::July,
8 => Month::August,
9 => Month::September,
10 => Month::October,
11 => Month::November,
12 => Month::December,
_ => return Err(ConstDecodeError::other("invalid month value")),
})
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
type Utc = UtcDateTime;
type Datetime = PrimitiveDateTime;
#[derive(Debug, Clone, Copy)]
struct DurationWrapper(Duration);
impl quickcheck::Arbitrary for DurationWrapper {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let d: core::time::Duration = quickcheck::Arbitrary::arbitrary(g);
Self(Duration::new(d.as_secs() as i64, d.subsec_nanos() as i32))
}
}
#[quickcheck_macros::quickcheck]
fn fuzzy_duration(value: DurationWrapper) -> bool {
let value = value.0;
let encoded = encode_duration(&value);
if encoded.len() != encoded_duration_len(&value).get()
|| (encoded.len() > <Duration>::MAX_ENCODED_LEN.get())
{
return false;
}
let Some(consumed) = crate::consume_varint_checked(&encoded) else {
return false;
};
if consumed.get() != encoded.len() {
return false;
}
if let Ok((bytes_read, decoded)) = decode_duration(&encoded) {
value == decoded && encoded.len() == bytes_read.get()
} else {
false
}
}
#[quickcheck_macros::quickcheck]
fn fuzzy_duration_varint(value: DurationWrapper) -> bool {
let value = value.0;
let mut buf = [0; <Duration>::MAX_ENCODED_LEN.get()];
let Ok(encoded_len) = value.encode(&mut buf) else {
return false;
};
if encoded_len != value.encoded_len() || (value.encoded_len() > <Duration>::MAX_ENCODED_LEN) {
return false;
}
let Some(consumed) = crate::consume_varint_checked(&buf) else {
return false;
};
if consumed != encoded_len {
return false;
}
if let Ok((bytes_read, decoded)) = <Duration>::decode(&buf) {
value == decoded && encoded_len == bytes_read
} else {
false
}
}
fuzzy!(@varing_ref(Date, Datetime, Time, Utc));
fuzzy!(@varint(Date, PrimitiveDateTime, Time, UtcDateTime));
}