use alloc::borrow::Cow;
use alloc::string::{String, ToString};
use core::marker::PhantomData;
use crate::State;
use crate::Text;
use crate::de::{Deserialize, Slot, default_atom};
use crate::error::{Error, ErrorKind};
use crate::event::Atom;
use crate::ext::known::{Bridge, WellKnown, out_of_range};
use crate::ext::{Duration, ExtValue, Number, Timestamp};
use crate::ser::{Emit, Serialize};
const NANOS_PER_SECOND: i128 = 1_000_000_000;
mod sealed {
pub trait Sealed {}
}
#[cfg_attr(feature = "std", doc = "[`SystemTime`]: std::time::SystemTime")]
#[cfg_attr(
not(feature = "std"),
doc = "[`SystemTime`]: https://doc.rust-lang.org/std/time/struct.SystemTime.html"
)]
pub trait TimeValue: Sized + Send + Sync + sealed::Sealed {
#[doc(hidden)]
fn __to_nanos(&self) -> Result<i128, Error>;
#[doc(hidden)]
fn __from_nanos(nanos: i128) -> Result<Self, Error>;
#[doc(hidden)]
fn __from_ext(ext: &ExtValue) -> Result<Option<Self>, Error>;
#[doc(hidden)]
fn __is_instant() -> bool;
}
pub(crate) trait KnownTime: WellKnown + Copy {
const IS_INSTANT: bool;
fn to_nanos(&self) -> i128;
fn from_nanos(nanos: i128) -> Result<Self, Error>;
}
impl KnownTime for Duration {
const IS_INSTANT: bool = false;
fn to_nanos(&self) -> i128 {
i128::from(self.seconds) * NANOS_PER_SECOND + i128::from(self.nanosecond)
}
fn from_nanos(nanos: i128) -> Result<Duration, Error> {
Ok(Duration {
seconds: i64::try_from(nanos / NANOS_PER_SECOND)
.map_err(|_| out_of_range("duration out of range"))?,
nanosecond: (nanos % NANOS_PER_SECOND) as i32,
})
}
}
impl KnownTime for Timestamp {
const IS_INSTANT: bool = true;
fn to_nanos(&self) -> i128 {
i128::from(self.seconds) * NANOS_PER_SECOND + i128::from(self.nanosecond)
}
fn from_nanos(nanos: i128) -> Result<Timestamp, Error> {
Ok(Timestamp {
seconds: i64::try_from(nanos.div_euclid(NANOS_PER_SECOND))
.map_err(|_| out_of_range("timestamp out of range"))?,
nanosecond: nanos.rem_euclid(NANOS_PER_SECOND) as u32,
})
}
}
macro_rules! known_time_value {
($($ty:ty),*) => {
$(
impl sealed::Sealed for $ty {}
impl TimeValue for $ty {
fn __to_nanos(&self) -> Result<i128, Error> {
Ok(self.to_nanos())
}
fn __from_nanos(nanos: i128) -> Result<Self, Error> {
<$ty as KnownTime>::from_nanos(nanos)
}
fn __from_ext(ext: &ExtValue) -> Result<Option<Self>, Error> {
Ok(ext.downcast_ref::<$ty>().copied())
}
fn __is_instant() -> bool {
<$ty as KnownTime>::IS_INSTANT
}
}
)*
};
}
known_time_value!(Duration, Timestamp);
macro_rules! bridged_time_value {
($($ty:ty),* $(,)?) => {
$(
impl sealed::Sealed for $ty {}
impl TimeValue for $ty {
fn __to_nanos(&self) -> Result<i128, Error> {
Ok(Bridge::to_known(self)?.to_nanos())
}
fn __from_nanos(nanos: i128) -> Result<Self, Error> {
<$ty as Bridge>::from_known(KnownTime::from_nanos(nanos)?)
}
fn __from_ext(ext: &ExtValue) -> Result<Option<Self>, Error> {
match ext.downcast_ref::<<$ty as Bridge>::Known>() {
Some(known) => <$ty as Bridge>::from_known(*known).map(Some),
None => Ok(None),
}
}
fn __is_instant() -> bool {
<<$ty as Bridge>::Known as KnownTime>::IS_INSTANT
}
}
)*
};
}
bridged_time_value!(core::time::Duration);
#[cfg(feature = "std")]
bridged_time_value!(std::time::SystemTime);
#[cfg(feature = "chrono")]
bridged_time_value!(::chrono::TimeDelta, ::chrono::DateTime<::chrono::Utc>);
#[cfg(feature = "jiff")]
bridged_time_value!(::jiff::SignedDuration, ::jiff::Timestamp);
#[cfg(feature = "time")]
bridged_time_value!(::time::Duration, ::time::UtcDateTime);
pub trait UnitFormat: unit_format::Sealed + 'static {
#[doc(hidden)]
fn __atom(nanos: i128, unit: i128, instant: bool) -> Atom<'static>;
}
mod unit_format {
pub trait Sealed {}
impl Sealed for i64 {}
impl Sealed for f64 {}
impl Sealed for alloc::string::String {}
}
fn whole_units(nanos: i128, unit: i128, instant: bool) -> i128 {
if instant {
nanos.div_euclid(unit)
} else {
nanos / unit
}
}
fn integer_atom(value: i128) -> Atom<'static> {
match i64::try_from(value) {
Ok(value) => Atom::I64(value),
Err(_) => Atom::Ext(ExtValue::owned(value)),
}
}
impl UnitFormat for i64 {
fn __atom(nanos: i128, unit: i128, instant: bool) -> Atom<'static> {
integer_atom(whole_units(nanos, unit, instant))
}
}
impl UnitFormat for f64 {
fn __atom(nanos: i128, unit: i128, instant: bool) -> Atom<'static> {
let whole = whole_units(nanos, unit, instant);
let fraction = nanos - whole * unit;
Atom::F64(whole as f64 + fraction as f64 / unit as f64)
}
}
impl UnitFormat for String {
fn __atom(nanos: i128, unit: i128, instant: bool) -> Atom<'static> {
Atom::Str(Text::owned(whole_units(nanos, unit, instant).to_string()))
}
}
#[cold]
fn time_out_of_range() -> Error {
out_of_range("time out of range")
}
fn int_nanos(value: i128, unit_exp: u32) -> Result<i128, Error> {
value
.checked_mul(10i128.pow(unit_exp))
.ok_or_else(time_out_of_range)
}
fn decimal_nanos(text: &str, unit_exp: u32) -> Option<Result<i128, Error>> {
let text = text.as_bytes();
let (negative, text) = match text.first() {
Some(b'-') => (true, &text[1..]),
Some(b'+') => (false, &text[1..]),
_ => (false, text),
};
let (mantissa, exp) = match text.iter().position(|&x| x == b'e' || x == b'E') {
Some(index) => {
let exp = core::str::from_utf8(&text[index + 1..]).ok()?;
(&text[..index], exp.parse::<i32>().ok()?)
}
None => (text, 0),
};
let (int_part, frac_part) = match mantissa.iter().position(|&x| x == b'.') {
Some(index) => (&mantissa[..index], &mantissa[index + 1..]),
None => (mantissa, &[][..]),
};
if (int_part.is_empty() && frac_part.is_empty())
|| !int_part.iter().chain(frac_part).all(u8::is_ascii_digit)
{
return None;
}
let point = int_part.len() as i64 + i64::from(exp) + i64::from(unit_exp);
let mut value = 0i128;
let mut round_up = false;
let mut index = 0i64;
for &digit in int_part.iter().chain(frac_part) {
if index < point {
value = match value
.checked_mul(10)
.and_then(|x| x.checked_add(i128::from(digit - b'0')))
{
Some(value) => value,
None => return Some(Err(time_out_of_range())),
};
} else if index == point {
round_up = digit >= b'5';
break;
}
index += 1;
}
if value != 0 && point > index {
let zeros = u32::try_from(point - index).unwrap_or(u32::MAX);
value = match 10i128.checked_pow(zeros).and_then(|x| value.checked_mul(x)) {
Some(value) => value,
None => return Some(Err(time_out_of_range())),
};
}
if round_up {
value += 1;
}
Some(Ok(if negative { -value } else { value }))
}
struct FloatText {
buf: [u8; 48],
len: usize,
}
impl core::fmt::Write for FloatText {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let end = self.len + s.len();
self.buf
.get_mut(self.len..end)
.ok_or(core::fmt::Error)?
.copy_from_slice(s.as_bytes());
self.len = end;
Ok(())
}
}
fn float_nanos(value: f64, unit_exp: u32) -> Result<i128, Error> {
if !value.is_finite() {
return Err(time_out_of_range());
}
let mut text = FloatText {
buf: [0; 48],
len: 0,
};
core::fmt::write(&mut text, format_args!("{:e}", value)).map_err(|_| time_out_of_range())?;
let text = core::str::from_utf8(&text.buf[..text.len]).map_err(|_| time_out_of_range())?;
decimal_nanos(text, unit_exp).unwrap_or_else(|| Err(time_out_of_range()))
}
fn text_nanos(text: &str, unit_exp: u32, name: &str) -> Result<i128, Error> {
decimal_nanos(text, unit_exp).unwrap_or_else(|| {
Err(Error::new(
ErrorKind::InvalidValue,
alloc::format!("invalid number of {}", name),
))
})
}
fn unit_atom<'de, T, A>(
slot: &mut Slot<T, A>,
atom: Atom,
unit_exp: u32,
name: &str,
state: &mut State,
) -> Result<(), Error>
where
T: TimeValue,
A: Deserialize<'de, T>,
{
let nanos = match atom {
Atom::U64(value) => int_nanos(i128::from(value), unit_exp)?,
Atom::I64(value) => int_nanos(i128::from(value), unit_exp)?,
Atom::F64(value) => float_nanos(value, unit_exp)?,
Atom::Str(ref text) | Atom::Lexical(ref text) => text_nanos(text, unit_exp, name)?,
Atom::Ext(ref ext) => {
if let Some(&value) = ext.downcast_ref::<i128>() {
int_nanos(value, unit_exp)?
} else if let Some(&value) = ext.downcast_ref::<u128>() {
let value = i128::try_from(value).map_err(|_| time_out_of_range())?;
int_nanos(value, unit_exp)?
} else if let Some(number) = ext.downcast_value_ref::<Number>() {
text_nanos(number.as_str(), unit_exp, name)?
} else if let Some(value) = T::__from_ext(ext)? {
slot.set(value);
return Ok(());
} else {
return default_atom(slot, atom, state);
}
}
other => return default_atom(slot, other, state),
};
slot.set(T::__from_nanos(nanos)?);
Ok(())
}
macro_rules! time_unit {
($(#[$meta:meta])* $name:ident, $unit_exp:expr, $expecting:literal;) => {
$(#[$meta])*
pub struct $name<F = i64>(PhantomData<fn() -> F>);
impl<'de, T: TimeValue, F: UnitFormat> Deserialize<'de, T> for $name<F> {
fn deserialize_atom(
slot: &mut Slot<T, Self>,
atom: Atom,
state: &mut State,
) -> Result<(), Error> {
unit_atom(slot, atom, $unit_exp, $expecting, state)
}
fn expecting() -> Cow<'static, str> {
Cow::Borrowed($expecting)
}
slot_atom_into!(T);
}
impl<T: TimeValue, F: UnitFormat> Serialize<T> for $name<F> {
fn serialize<'a>(value: &'a T, _state: &mut State) -> Result<Emit<'a>, Error> {
Ok(Emit::Atom(F::__atom(
value.__to_nanos()?,
10i128.pow($unit_exp),
T::__is_instant(),
)))
}
begin_without_finish!(T);
}
};
}
time_unit! {
Seconds, 9, "seconds";
}
time_unit! {
Millis, 6, "milliseconds";
}
time_unit! {
Micros, 3, "microseconds";
}
time_unit! {
Nanos, 0, "nanoseconds";
}