1use alloc::borrow::Cow;
4use alloc::string::{String, ToString};
5use core::marker::PhantomData;
6
7use crate::State;
8use crate::Text;
9use crate::de::{Deserialize, Slot, default_atom};
10use crate::error::{Error, ErrorKind};
11use crate::event::Atom;
12use crate::ext::known::{Bridge, WellKnown, out_of_range};
13use crate::ext::{Duration, ExtValue, Number, Timestamp};
14use crate::ser::{Emit, Serialize};
15
16const NANOS_PER_SECOND: i128 = 1_000_000_000;
17
18mod sealed {
19 pub trait Sealed {}
20}
21
22#[cfg_attr(feature = "std", doc = "[`SystemTime`]: std::time::SystemTime")]
36#[cfg_attr(
37 not(feature = "std"),
38 doc = "[`SystemTime`]: https://doc.rust-lang.org/std/time/struct.SystemTime.html"
39)]
40pub trait TimeValue: Sized + Send + Sync + sealed::Sealed {
41 #[doc(hidden)]
43 fn __to_nanos(&self) -> Result<i128, Error>;
44
45 #[doc(hidden)]
47 fn __from_nanos(nanos: i128) -> Result<Self, Error>;
48
49 #[doc(hidden)]
51 fn __from_ext(ext: &ExtValue) -> Result<Option<Self>, Error>;
52
53 #[doc(hidden)]
57 fn __is_instant() -> bool;
58}
59
60pub(crate) trait KnownTime: WellKnown + Copy {
62 const IS_INSTANT: bool;
63 fn to_nanos(&self) -> i128;
64 fn from_nanos(nanos: i128) -> Result<Self, Error>;
65}
66
67impl KnownTime for Duration {
68 const IS_INSTANT: bool = false;
69
70 fn to_nanos(&self) -> i128 {
71 i128::from(self.seconds) * NANOS_PER_SECOND + i128::from(self.nanosecond)
72 }
73
74 fn from_nanos(nanos: i128) -> Result<Duration, Error> {
75 Ok(Duration {
77 seconds: i64::try_from(nanos / NANOS_PER_SECOND)
78 .map_err(|_| out_of_range("duration out of range"))?,
79 nanosecond: (nanos % NANOS_PER_SECOND) as i32,
80 })
81 }
82}
83
84impl KnownTime for Timestamp {
85 const IS_INSTANT: bool = true;
86
87 fn to_nanos(&self) -> i128 {
88 i128::from(self.seconds) * NANOS_PER_SECOND + i128::from(self.nanosecond)
89 }
90
91 fn from_nanos(nanos: i128) -> Result<Timestamp, Error> {
92 Ok(Timestamp {
94 seconds: i64::try_from(nanos.div_euclid(NANOS_PER_SECOND))
95 .map_err(|_| out_of_range("timestamp out of range"))?,
96 nanosecond: nanos.rem_euclid(NANOS_PER_SECOND) as u32,
97 })
98 }
99}
100
101macro_rules! known_time_value {
103 ($($ty:ty),*) => {
104 $(
105 impl sealed::Sealed for $ty {}
106
107 impl TimeValue for $ty {
108 fn __to_nanos(&self) -> Result<i128, Error> {
109 Ok(self.to_nanos())
110 }
111
112 fn __from_nanos(nanos: i128) -> Result<Self, Error> {
113 <$ty as KnownTime>::from_nanos(nanos)
114 }
115
116 fn __from_ext(ext: &ExtValue) -> Result<Option<Self>, Error> {
117 Ok(ext.downcast_ref::<$ty>().copied())
118 }
119
120 fn __is_instant() -> bool {
121 <$ty as KnownTime>::IS_INSTANT
122 }
123 }
124 )*
125 };
126}
127
128known_time_value!(Duration, Timestamp);
129
130macro_rules! bridged_time_value {
133 ($($ty:ty),* $(,)?) => {
134 $(
135 impl sealed::Sealed for $ty {}
136
137 impl TimeValue for $ty {
138 fn __to_nanos(&self) -> Result<i128, Error> {
139 Ok(Bridge::to_known(self)?.to_nanos())
140 }
141
142 fn __from_nanos(nanos: i128) -> Result<Self, Error> {
143 <$ty as Bridge>::from_known(KnownTime::from_nanos(nanos)?)
144 }
145
146 fn __from_ext(ext: &ExtValue) -> Result<Option<Self>, Error> {
147 match ext.downcast_ref::<<$ty as Bridge>::Known>() {
148 Some(known) => <$ty as Bridge>::from_known(*known).map(Some),
149 None => Ok(None),
150 }
151 }
152
153 fn __is_instant() -> bool {
154 <<$ty as Bridge>::Known as KnownTime>::IS_INSTANT
155 }
156 }
157 )*
158 };
159}
160
161bridged_time_value!(core::time::Duration);
162
163#[cfg(feature = "std")]
164bridged_time_value!(std::time::SystemTime);
165
166#[cfg(feature = "chrono")]
167bridged_time_value!(::chrono::TimeDelta, ::chrono::DateTime<::chrono::Utc>);
168
169#[cfg(feature = "jiff")]
170bridged_time_value!(::jiff::SignedDuration, ::jiff::Timestamp);
171
172#[cfg(feature = "time")]
173bridged_time_value!(::time::Duration, ::time::UtcDateTime);
174
175pub trait UnitFormat: unit_format::Sealed + 'static {
179 #[doc(hidden)]
181 fn __atom(nanos: i128, unit: i128, instant: bool) -> Atom<'static>;
182}
183
184mod unit_format {
185 pub trait Sealed {}
186 impl Sealed for i64 {}
187 impl Sealed for f64 {}
188 impl Sealed for alloc::string::String {}
189}
190
191fn whole_units(nanos: i128, unit: i128, instant: bool) -> i128 {
195 if instant {
196 nanos.div_euclid(unit)
197 } else {
198 nanos / unit
199 }
200}
201
202fn integer_atom(value: i128) -> Atom<'static> {
204 match i64::try_from(value) {
205 Ok(value) => Atom::I64(value),
206 Err(_) => Atom::Ext(ExtValue::owned(value)),
207 }
208}
209
210impl UnitFormat for i64 {
213 fn __atom(nanos: i128, unit: i128, instant: bool) -> Atom<'static> {
214 integer_atom(whole_units(nanos, unit, instant))
215 }
216}
217
218impl UnitFormat for f64 {
220 fn __atom(nanos: i128, unit: i128, instant: bool) -> Atom<'static> {
221 let whole = whole_units(nanos, unit, instant);
222 let fraction = nanos - whole * unit;
223 Atom::F64(whole as f64 + fraction as f64 / unit as f64)
224 }
225}
226
227impl UnitFormat for String {
230 fn __atom(nanos: i128, unit: i128, instant: bool) -> Atom<'static> {
231 Atom::Str(Text::owned(whole_units(nanos, unit, instant).to_string()))
232 }
233}
234
235#[cold]
237fn time_out_of_range() -> Error {
238 out_of_range("time out of range")
239}
240
241fn int_nanos(value: i128, unit_exp: u32) -> Result<i128, Error> {
243 value
244 .checked_mul(10i128.pow(unit_exp))
245 .ok_or_else(time_out_of_range)
246}
247
248fn decimal_nanos(text: &str, unit_exp: u32) -> Option<Result<i128, Error>> {
254 let text = text.as_bytes();
255 let (negative, text) = match text.first() {
256 Some(b'-') => (true, &text[1..]),
257 Some(b'+') => (false, &text[1..]),
258 _ => (false, text),
259 };
260 let (mantissa, exp) = match text.iter().position(|&x| x == b'e' || x == b'E') {
261 Some(index) => {
262 let exp = core::str::from_utf8(&text[index + 1..]).ok()?;
263 (&text[..index], exp.parse::<i32>().ok()?)
264 }
265 None => (text, 0),
266 };
267 let (int_part, frac_part) = match mantissa.iter().position(|&x| x == b'.') {
268 Some(index) => (&mantissa[..index], &mantissa[index + 1..]),
269 None => (mantissa, &[][..]),
270 };
271 if (int_part.is_empty() && frac_part.is_empty())
272 || !int_part.iter().chain(frac_part).all(u8::is_ascii_digit)
273 {
274 return None;
275 }
276
277 let point = int_part.len() as i64 + i64::from(exp) + i64::from(unit_exp);
279 let mut value = 0i128;
280 let mut round_up = false;
281 let mut index = 0i64;
282 for &digit in int_part.iter().chain(frac_part) {
283 if index < point {
284 value = match value
285 .checked_mul(10)
286 .and_then(|x| x.checked_add(i128::from(digit - b'0')))
287 {
288 Some(value) => value,
289 None => return Some(Err(time_out_of_range())),
290 };
291 } else if index == point {
292 round_up = digit >= b'5';
294 break;
295 }
296 index += 1;
297 }
298 if value != 0 && point > index {
300 let zeros = u32::try_from(point - index).unwrap_or(u32::MAX);
301 value = match 10i128.checked_pow(zeros).and_then(|x| value.checked_mul(x)) {
302 Some(value) => value,
303 None => return Some(Err(time_out_of_range())),
304 };
305 }
306 if round_up {
307 value += 1;
308 }
309 Some(Ok(if negative { -value } else { value }))
310}
311
312struct FloatText {
314 buf: [u8; 48],
315 len: usize,
316}
317
318impl core::fmt::Write for FloatText {
319 fn write_str(&mut self, s: &str) -> core::fmt::Result {
320 let end = self.len + s.len();
321 self.buf
322 .get_mut(self.len..end)
323 .ok_or(core::fmt::Error)?
324 .copy_from_slice(s.as_bytes());
325 self.len = end;
326 Ok(())
327 }
328}
329
330fn float_nanos(value: f64, unit_exp: u32) -> Result<i128, Error> {
336 if !value.is_finite() {
337 return Err(time_out_of_range());
338 }
339 let mut text = FloatText {
340 buf: [0; 48],
341 len: 0,
342 };
343 core::fmt::write(&mut text, format_args!("{:e}", value)).map_err(|_| time_out_of_range())?;
345 let text = core::str::from_utf8(&text.buf[..text.len]).map_err(|_| time_out_of_range())?;
346 decimal_nanos(text, unit_exp).unwrap_or_else(|| Err(time_out_of_range()))
347}
348
349fn text_nanos(text: &str, unit_exp: u32, name: &str) -> Result<i128, Error> {
351 decimal_nanos(text, unit_exp).unwrap_or_else(|| {
352 Err(Error::new(
353 ErrorKind::InvalidValue,
354 alloc::format!("invalid number of {}", name),
355 ))
356 })
357}
358
359fn unit_atom<'de, T, A>(
361 slot: &mut Slot<T, A>,
362 atom: Atom,
363 unit_exp: u32,
364 name: &str,
365 state: &mut State,
366) -> Result<(), Error>
367where
368 T: TimeValue,
369 A: Deserialize<'de, T>,
370{
371 let nanos = match atom {
372 Atom::U64(value) => int_nanos(i128::from(value), unit_exp)?,
373 Atom::I64(value) => int_nanos(i128::from(value), unit_exp)?,
374 Atom::F64(value) => float_nanos(value, unit_exp)?,
375 Atom::Str(ref text) | Atom::Lexical(ref text) => text_nanos(text, unit_exp, name)?,
376 Atom::Ext(ref ext) => {
377 if let Some(&value) = ext.downcast_ref::<i128>() {
378 int_nanos(value, unit_exp)?
379 } else if let Some(&value) = ext.downcast_ref::<u128>() {
380 let value = i128::try_from(value).map_err(|_| time_out_of_range())?;
381 int_nanos(value, unit_exp)?
382 } else if let Some(number) = ext.downcast_value_ref::<Number>() {
383 text_nanos(number.as_str(), unit_exp, name)?
385 } else if let Some(value) = T::__from_ext(ext)? {
386 slot.set(value);
388 return Ok(());
389 } else {
390 return default_atom(slot, atom, state);
391 }
392 }
393 other => return default_atom(slot, other, state),
394 };
395 slot.set(T::__from_nanos(nanos)?);
396 Ok(())
397}
398
399macro_rules! time_unit {
401 ($(#[$meta:meta])* $name:ident, $unit_exp:expr, $expecting:literal;) => {
402 $(#[$meta])*
403 pub struct $name<F = i64>(PhantomData<fn() -> F>);
404
405 impl<'de, T: TimeValue, F: UnitFormat> Deserialize<'de, T> for $name<F> {
406 fn deserialize_atom(
407 slot: &mut Slot<T, Self>,
408 atom: Atom,
409 state: &mut State,
410 ) -> Result<(), Error> {
411 unit_atom(slot, atom, $unit_exp, $expecting, state)
412 }
413
414 fn expecting() -> Cow<'static, str> {
415 Cow::Borrowed($expecting)
416 }
417
418 slot_atom_into!(T);
419 }
420
421 impl<T: TimeValue, F: UnitFormat> Serialize<T> for $name<F> {
422 fn serialize<'a>(value: &'a T, _state: &mut State) -> Result<Emit<'a>, Error> {
423 Ok(Emit::Atom(F::__atom(
424 value.__to_nanos()?,
425 10i128.pow($unit_exp),
426 T::__is_instant(),
427 )))
428 }
429
430 begin_without_finish!(T);
431 }
432 };
433}
434
435time_unit! {
436 Seconds, 9, "seconds";
482}
483
484time_unit! {
485 Millis, 6, "milliseconds";
501}
502
503time_unit! {
504 Micros, 3, "microseconds";
508}
509
510time_unit! {
511 Nanos, 0, "nanoseconds";
517}