1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3#![deny(
4 missing_docs,
5 clippy::missing_safety_doc,
6 clippy::undocumented_unsafe_blocks,
7 clippy::must_use_candidate,
8 clippy::perf,
9 clippy::complexity,
10 clippy::suspicious
11)]
12
13use core::ops::{AddAssign, Deref, DerefMut, SubAssign};
14
15#[cfg(feature = "std")]
16use std::time::SystemTime;
17
18pub extern crate time;
19
20pub use time::{Duration, UtcOffset};
21use time::{OffsetDateTime, PrimitiveDateTime};
22
23pub use generic_array::typenum;
24use typenum as t;
25
26#[macro_use]
27mod macros;
28
29mod format;
30mod impls;
31mod parse;
32mod ts_str;
33
34use ts_str::IsValidFormat;
35pub use ts_str::{FormatString, TimestampStr};
36
37#[cfg_attr(feature = "diesel", derive(diesel::AsExpression, diesel::FromSqlRow))]
41#[cfg_attr(feature = "diesel", diesel(sql_type = diesel::sql_types::Timestamp))]
42#[cfg_attr(feature = "diesel-pg", diesel(sql_type = diesel::sql_types::Timestamptz))]
43#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
44#[repr(transparent)]
45pub struct Timestamp(PrimitiveDateTime);
46
47use core::fmt;
48
49impl fmt::Debug for Timestamp {
50 #[inline]
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 f.debug_tuple("Timestamp")
53 .field(&self.format_nanoseconds())
54 .finish()
55 }
56}
57
58impl fmt::Display for Timestamp {
59 #[inline]
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 f.write_str(&self.format())
62 }
63}
64
65#[cfg(feature = "std")]
66impl From<SystemTime> for Timestamp {
67 fn from(ts: SystemTime) -> Self {
68 Timestamp(match ts.duration_since(SystemTime::UNIX_EPOCH) {
69 Ok(dur) => *Self::UNIX_EPOCH + dur,
70 Err(err) => *Self::UNIX_EPOCH - err.duration(),
71 })
72 }
73}
74
75#[cfg(feature = "std")]
76impl From<Timestamp> for SystemTime {
77 fn from(ts: Timestamp) -> Self {
78 SystemTime::UNIX_EPOCH + ts.duration_since(Timestamp::UNIX_EPOCH)
79 }
80}
81
82impl From<OffsetDateTime> for Timestamp {
83 fn from(ts: OffsetDateTime) -> Self {
84 let utc_datetime = ts.to_offset(UtcOffset::UTC);
85 let date = utc_datetime.date();
86 let time = utc_datetime.time();
87 Timestamp(PrimitiveDateTime::new(date, time))
88 }
89}
90
91impl From<PrimitiveDateTime> for Timestamp {
92 #[inline]
93 fn from(ts: PrimitiveDateTime) -> Self {
94 Timestamp(ts)
95 }
96}
97
98#[cfg(all(feature = "std", not(any(target_arch = "wasm64", target_arch = "wasm32"))))]
100impl Timestamp {
101 #[inline]
103 #[must_use]
104 pub fn now_utc() -> Self {
105 Timestamp::from(SystemTime::now())
106 }
107}
108
109#[cfg(all(feature = "worker", target_arch = "wasm32", not(feature = "js")))]
110impl From<worker::Date> for Timestamp {
111 fn from(d: worker::Date) -> Self {
112 match Timestamp::UNIX_EPOCH.checked_add(Duration::milliseconds(d.as_millis() as i64)) {
113 Some(ts) => ts,
114 None => panic!("Invalid Date value"),
115 }
116 }
117}
118
119#[cfg(all(feature = "worker", target_arch = "wasm32", not(feature = "js")))]
120impl Timestamp {
121 #[inline]
127 #[must_use]
128 pub fn now_utc() -> Self {
129 worker::Date::now().into()
130 }
131}
132
133#[cfg(all(
134 feature = "js",
135 any(target_arch = "wasm32", target_arch = "wasm64"),
136 not(feature = "worker")
137))]
138impl Timestamp {
139 #[must_use]
145 pub fn now_utc() -> Self {
146 match Timestamp::UNIX_EPOCH.checked_add(Duration::milliseconds(js_sys::Date::now() as i64)) {
147 Some(ts) => ts,
148 None => panic!("Invalid Date::now() value"),
149 }
150 }
151}
152
153pub mod formats {
155 use super::*;
156
157 pub type FullMilliseconds = FormatString<t::True, t::False, t::U3>;
159 pub type FullMicroseconds = FormatString<t::True, t::False, t::U6>;
161 pub type FullNanoseconds = FormatString<t::True, t::False, t::U9>;
163
164 pub type FullMillisecondsOffset = FormatString<t::True, t::True, t::U3>;
166
167 pub type ShortMilliseconds = FormatString<t::False, t::False, t::U3>;
169
170 #[test]
171 #[allow(clippy::assertions_on_constants)]
172 fn test_short_ms_length() {
173 assert_eq!(
175 <<ShortMilliseconds as crate::ts_str::IsValidFormat>::Length as super::t::Unsigned>::USIZE,
176 "+20230324T070559.005Z".len()
177 );
178
179 assert!("+20230324T070559.005Z".len() <= 23);
180 }
181}
182
183#[macro_export]
191macro_rules! datetime {
192 ($($tt:tt)*) => {
193 $crate::Timestamp::from_primitive_datetime(time::macros::datetime!($($tt)*))
194 };
195}
196
197impl Timestamp {
198 pub const UNIX_EPOCH: Self = datetime!(1970 - 01 - 01 00:00);
200
201 #[inline(always)]
203 #[must_use]
204 pub const fn from_primitive_datetime(dt: PrimitiveDateTime) -> Self {
205 Timestamp(dt)
206 }
207
208 #[inline]
210 #[must_use]
211 pub fn duration_since(self, earlier: Self) -> Duration {
212 self.0 - earlier.0
213 }
214
215 #[must_use]
217 pub fn format_raw<F: t::Bit, O: t::Bit, P: t::Unsigned>(
218 &self,
219 offset: UtcOffset,
220 ) -> TimestampStr<FormatString<F, O, P>>
221 where
222 FormatString<F, O, P>: IsValidFormat,
223 {
224 format::do_format(self.0, offset)
225 }
226
227 #[inline(always)]
229 #[must_use]
230 pub fn format_with_precision<P: t::Unsigned>(&self) -> TimestampStr<FormatString<t::True, t::False, P>>
231 where
232 FormatString<t::True, t::False, P>: IsValidFormat,
233 {
234 self.format_raw(UtcOffset::UTC)
235 }
236
237 #[inline(always)]
239 #[must_use]
240 pub fn format(&self) -> TimestampStr<formats::FullMilliseconds> {
241 self.format_with_precision()
242 }
243
244 #[inline(always)]
246 #[must_use]
247 pub fn format_nanoseconds(&self) -> TimestampStr<formats::FullNanoseconds> {
248 self.format_with_precision()
249 }
250
251 #[inline(always)]
253 #[must_use]
254 pub fn format_microseconds(&self) -> TimestampStr<formats::FullMicroseconds> {
255 self.format_with_precision()
256 }
257
258 #[inline(always)]
260 #[must_use]
261 pub fn format_short(&self) -> TimestampStr<formats::ShortMilliseconds> {
262 self.format_raw(UtcOffset::UTC)
263 }
264
265 #[inline(always)]
268 #[must_use]
269 pub fn format_with_offset(&self, offset: UtcOffset) -> TimestampStr<formats::FullMillisecondsOffset> {
270 self.format_raw(offset)
271 }
272
273 #[inline(always)]
275 #[must_use]
276 pub fn format_with_offset_and_precision<P: t::Unsigned>(
277 &self,
278 offset: UtcOffset,
279 ) -> TimestampStr<FormatString<t::True, t::True, P>>
280 where
281 FormatString<t::True, t::True, P>: IsValidFormat,
282 {
283 self.format_raw(offset)
284 }
285
286 #[inline(never)]
288 #[must_use] pub fn parse(ts: &str) -> Option<Self> {
290 parse::parse_iso8601(ts.as_bytes()).map(Timestamp)
291 }
292
293 #[inline(always)]
295 #[must_use]
296 pub const fn assume_offset(self, offset: UtcOffset) -> time::OffsetDateTime {
297 self.0.assume_offset(offset)
298 }
299
300 #[inline]
304 #[must_use]
305 pub const fn checked_add(self, duration: Duration) -> Option<Self> {
306 match self.0.checked_add(duration) {
307 Some(ts) => Some(Timestamp(ts)),
308 None => None,
309 }
310 }
311
312 #[inline]
316 #[must_use]
317 pub const fn checked_sub(self, duration: Duration) -> Option<Self> {
318 match self.0.checked_sub(duration) {
319 Some(ts) => Some(Timestamp(ts)),
320 None => None,
321 }
322 }
323
324 #[inline]
328 #[must_use]
329 pub const fn saturating_add(self, duration: Duration) -> Self {
330 Timestamp(self.0.saturating_add(duration))
331 }
332
333 #[inline]
337 #[must_use]
338 pub const fn saturating_sub(self, duration: Duration) -> Self {
339 Timestamp(self.0.saturating_sub(duration))
340 }
341}
342
343impl Deref for Timestamp {
344 type Target = PrimitiveDateTime;
345
346 #[inline(always)]
347 fn deref(&self) -> &Self::Target {
348 &self.0
349 }
350}
351
352impl DerefMut for Timestamp {
353 #[inline(always)]
354 fn deref_mut(&mut self) -> &mut Self::Target {
355 &mut self.0
356 }
357}
358
359use core::ops::{Add, Sub};
360
361impl<T> Add<T> for Timestamp
362where
363 PrimitiveDateTime: Add<T, Output = PrimitiveDateTime>,
364{
365 type Output = Self;
366
367 #[inline]
368 fn add(self, rhs: T) -> Self::Output {
369 Timestamp(self.0 + rhs)
370 }
371}
372
373impl<T> Sub<T> for Timestamp
374where
375 PrimitiveDateTime: Sub<T, Output = PrimitiveDateTime>,
376{
377 type Output = Self;
378
379 #[inline]
380 fn sub(self, rhs: T) -> Self::Output {
381 Timestamp(self.0 - rhs)
382 }
383}
384
385impl<T> AddAssign<T> for Timestamp
386where
387 PrimitiveDateTime: AddAssign<T>,
388{
389 #[inline]
390 fn add_assign(&mut self, rhs: T) {
391 self.0 += rhs;
392 }
393}
394
395impl<T> SubAssign<T> for Timestamp
396where
397 PrimitiveDateTime: SubAssign<T>,
398{
399 #[inline]
400 fn sub_assign(&mut self, rhs: T) {
401 self.0 -= rhs;
402 }
403}
404
405#[cfg(feature = "serde")]
406mod serde_impl {
407 use serde_core::de::{Deserialize, Deserializer, Error, Visitor};
408 use serde_core::ser::{Serialize, Serializer};
409
410 #[cfg(feature = "bson")]
411 use serde_core::de::MapAccess;
412
413 use super::Timestamp;
414
415 impl Serialize for Timestamp {
416 #[inline]
417 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
418 where
419 S: Serializer,
420 {
421 if serializer.is_human_readable() {
422 self.format().serialize(serializer)
423 } else {
424 (self.duration_since(Timestamp::UNIX_EPOCH).whole_milliseconds() as i64).serialize(serializer)
425 }
426 }
427 }
428
429 const OUT_OF_RANGE: &str = "Milliseconds out of range";
430
431 impl<'de> Deserialize<'de> for Timestamp {
432 #[inline]
433 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
434 where
435 D: Deserializer<'de>,
436 {
437 use core::fmt;
438
439 struct TsVisitor;
440
441 #[allow(clippy::needless_lifetimes)] impl<'de> Visitor<'de> for TsVisitor {
443 type Value = Timestamp;
444
445 #[inline]
446 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
447 formatter.write_str("an ISO8601 Timestamp")
448 }
449
450 #[inline]
451 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
452 where
453 E: Error,
454 {
455 match Timestamp::parse(v) {
456 Some(ts) => Ok(ts),
457 None => Err(E::custom("Invalid Format")),
458 }
459 }
460
461 #[cfg(feature = "bson")]
462 fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
463 where
464 M: MapAccess<'de>,
465 {
466 let Some(key) = access.next_key::<&str>()? else {
474 return Err(M::Error::custom("Map Is Empty"));
475 };
476
477 match key {
478 "$date" => access.next_value::<Timestamp>(), "$numberLong" => match access.next_value::<&str>()?.parse() {
483 Ok(ms) => self.visit_i64(ms),
484 Err(_) => Err(M::Error::custom("Invalid Number in `$numberLong` field")),
485 },
486
487 #[cfg(not(feature = "std"))]
488 _ => Err(M::Error::custom("Unexpected key in map")),
489
490 #[cfg(feature = "std")]
491 _ => Err(M::Error::custom(format_args!("Unexpected key in map: {key}"))),
492 }
493 }
494
495 #[inline]
496 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
497 where
498 E: Error,
499 {
500 Timestamp::UNIX_EPOCH
501 .checked_add(time::Duration::milliseconds(v))
502 .ok_or_else(|| E::custom(OUT_OF_RANGE))
503 }
504
505 #[inline]
506 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
507 where
508 E: Error,
509 {
510 let seconds = v / 1000;
511 let nanoseconds = (v % 1_000) * 1_000_000;
512
513 Timestamp::UNIX_EPOCH
514 .checked_add(time::Duration::new(seconds as i64, nanoseconds as i32))
515 .ok_or_else(|| E::custom(OUT_OF_RANGE))
516 }
517 }
518
519 deserializer.deserialize_any(TsVisitor)
520 }
521 }
522}
523
524#[cfg(feature = "diesel")]
525mod diesel_impl {
526 #[cfg(feature = "diesel-pg")]
527 use diesel::sql_types::Timestamptz as DbTimestamptz;
528 use diesel::{
529 backend::Backend,
530 deserialize::{self, FromSql},
531 serialize::{self, ToSql},
532 sql_types::Timestamp as DbTimestamp,
533 };
534 use time::PrimitiveDateTime;
535
536 use super::Timestamp;
537
538 impl<DB> FromSql<DbTimestamp, DB> for Timestamp
539 where
540 DB: Backend,
541 PrimitiveDateTime: FromSql<DbTimestamp, DB>,
542 {
543 fn from_sql(bytes: <DB as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
544 <PrimitiveDateTime as FromSql<DbTimestamp, DB>>::from_sql(bytes).map(Timestamp::from)
545 }
546 }
547
548 #[cfg(feature = "diesel-pg")]
549 impl<DB> FromSql<DbTimestamptz, DB> for Timestamp
550 where
551 DB: Backend,
552 PrimitiveDateTime: FromSql<DbTimestamptz, DB>,
553 {
554 fn from_sql(bytes: <DB as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
555 <PrimitiveDateTime as FromSql<DbTimestamptz, DB>>::from_sql(bytes).map(Timestamp::from)
556 }
557 }
558
559 impl<DB> ToSql<DbTimestamp, DB> for Timestamp
560 where
561 DB: Backend,
562 PrimitiveDateTime: ToSql<DbTimestamp, DB>,
563 {
564 fn to_sql<'b>(&'b self, out: &mut serialize::Output<'b, '_, DB>) -> serialize::Result {
565 <PrimitiveDateTime as ToSql<DbTimestamp, DB>>::to_sql(self, out)
566 }
567 }
568
569 #[cfg(feature = "diesel-pg")]
570 impl<DB> ToSql<DbTimestamptz, DB> for Timestamp
571 where
572 DB: Backend,
573 PrimitiveDateTime: ToSql<DbTimestamptz, DB>,
574 {
575 fn to_sql<'b>(&'b self, out: &mut serialize::Output<'b, '_, DB>) -> serialize::Result {
576 <PrimitiveDateTime as ToSql<DbTimestamptz, DB>>::to_sql(self, out)
577 }
578 }
579
580 #[cfg(feature = "rkyv_08")]
581 const _: () = {
582 use diesel::query_builder::bind_collector::RawBytesBindCollector;
583
584 use super::ArchivedTimestamp;
585
586 impl<DB> ToSql<DbTimestamp, DB> for ArchivedTimestamp
587 where
588 for<'c> DB: Backend<BindCollector<'c> = RawBytesBindCollector<DB>>,
589 Timestamp: ToSql<DbTimestamp, DB>,
590 {
591 fn to_sql<'b>(&'b self, out: &mut serialize::Output<'b, '_, DB>) -> serialize::Result {
592 <Timestamp as ToSql<DbTimestamp, DB>>::to_sql(&Timestamp::from(*self), &mut out.reborrow())
593 }
594 }
595
596 #[cfg(feature = "diesel-pg")]
597 impl<DB> ToSql<DbTimestamptz, DB> for ArchivedTimestamp
598 where
599 for<'c> DB: Backend<BindCollector<'c> = RawBytesBindCollector<DB>>,
600 Timestamp: ToSql<DbTimestamptz, DB>,
601 {
602 fn to_sql<'b>(&'b self, out: &mut serialize::Output<'b, '_, DB>) -> serialize::Result {
603 <Timestamp as ToSql<DbTimestamptz, DB>>::to_sql(&Timestamp::from(*self), &mut out.reborrow())
604 }
605 }
606 };
607}
608
609#[cfg(feature = "pg")]
610mod pg_impl {
611 use postgres_types::{accepts, to_sql_checked, FromSql, IsNull, ToSql, Type};
612 use time::PrimitiveDateTime;
613
614 use super::Timestamp;
615
616 impl ToSql for Timestamp {
617 #[inline]
618 fn to_sql(
619 &self,
620 ty: &Type,
621 out: &mut bytes::BytesMut,
622 ) -> Result<IsNull, Box<dyn core::error::Error + Sync + Send>>
623 where
624 Self: Sized,
625 {
626 self.0.to_sql(ty, out)
627 }
628
629 accepts!(TIMESTAMP, TIMESTAMPTZ);
630 to_sql_checked!();
631 }
632
633 impl<'a> FromSql<'a> for Timestamp {
634 #[inline]
635 fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn core::error::Error + Sync + Send>> {
636 PrimitiveDateTime::from_sql(ty, raw).map(Timestamp)
637 }
638
639 accepts!(TIMESTAMP, TIMESTAMPTZ);
640 }
641
642 #[cfg(feature = "rkyv_08")]
643 const _: () = {
644 impl ToSql for super::ArchivedTimestamp {
645 fn to_sql(
646 &self,
647 _ty: &Type,
648 out: &mut bytes::BytesMut,
649 ) -> Result<IsNull, Box<dyn core::error::Error + Sync + Send>> {
650 const EPOCH_OFFSET: i64 = 946684800000000; let Some(ts) = self.0.to_native().checked_mul(1000) else {
654 return Err("Timestamp out of range".into());
655 };
656
657 let Some(pts) = ts.checked_sub(EPOCH_OFFSET) else {
659 return Err("Timestamp out of range".into());
660 };
661
662 postgres_protocol::types::time_to_sql(pts, out);
663
664 Ok(IsNull::No)
665 }
666
667 accepts!(TIMESTAMP, TIMESTAMPTZ);
668 to_sql_checked!();
669 }
670 };
671}
672
673#[cfg(feature = "rusqlite")]
674mod rusqlite_impl {
675 use super::{Duration, Timestamp};
676
677 use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, Value, ValueRef};
678 use rusqlite::Error;
679
680 #[derive(Debug)]
681 struct InvalidTimestamp;
682
683 use core::{error, fmt, str};
684
685 extern crate alloc;
686
687 use alloc::borrow::ToOwned;
688
689 impl error::Error for InvalidTimestamp {}
690 impl fmt::Display for InvalidTimestamp {
691 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
692 f.write_str("Invalid ISO8601 Timestamp")
693 }
694 }
695
696 impl FromSql for Timestamp {
697 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
698 match value {
700 ValueRef::Text(bytes) => match str::from_utf8(bytes) {
701 Err(e) => Err(FromSqlError::Other(Error::Utf8Error(e).into())),
702 Ok(ts) => match Timestamp::parse(ts) {
703 Some(ts) => Ok(ts),
704 None => Err(FromSqlError::Other(InvalidTimestamp.into())),
705 },
706 },
707
708 ValueRef::Integer(ts) => Timestamp::UNIX_EPOCH
710 .checked_add(Duration::seconds(ts))
711 .ok_or_else(|| FromSqlError::OutOfRange(ts)),
712
713 ValueRef::Real(ts) => {
717 let ts = Duration::seconds_f64((ts - 2440587.5) * 86_400.0);
718
719 Timestamp::UNIX_EPOCH
720 .checked_add(ts)
721 .ok_or_else(|| FromSqlError::OutOfRange(ts.whole_seconds()))
722 }
723
724 _ => Err(FromSqlError::InvalidType),
725 }
726 }
727 }
728
729 impl ToSql for Timestamp {
730 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
731 Ok(ToSqlOutput::Owned(Value::Text(self.format().to_owned())))
732 }
733 }
734
735 #[cfg(feature = "rkyv_08")]
736 const _: () = {
737 impl ToSql for super::ArchivedTimestamp {
738 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
739 Ok(ToSqlOutput::Owned(Value::Text(
740 Timestamp::from(*self).format().to_owned(),
741 )))
742 }
743 }
744 };
745}
746
747#[cfg(feature = "schema")]
748mod schema_impl {
749 use schemars::{json_schema, JsonSchema, Schema, SchemaGenerator};
750
751 extern crate alloc;
752
753 use alloc::borrow::Cow;
754
755 use super::Timestamp;
756
757 impl JsonSchema for Timestamp {
758 fn schema_name() -> Cow<'static, str> {
759 Cow::Borrowed("ISO8601 Timestamp")
760 }
761
762 fn schema_id() -> Cow<'static, str> {
763 Cow::Borrowed("iso8601_timestamp::Timestamp")
764 }
765
766 fn json_schema(_: &mut SchemaGenerator) -> Schema {
767 json_schema!({
768 "type": "string",
769 "format": "date-time",
770 "description": "ISO8601 formatted timestamp",
771 "examples": ["1970-01-01T00:00:00Z"],
772 })
773 }
774 }
775}
776
777#[cfg(feature = "rand")]
778mod rand_impl {
779 use rand::distr::{Distribution, StandardUniform};
780 use rand::Rng;
781
782 use super::Timestamp;
783
784 impl Distribution<Timestamp> for StandardUniform {
785 #[inline]
786 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Timestamp {
787 Timestamp(rng.random())
788 }
789 }
790}
791
792#[cfg(feature = "quickcheck")]
793mod quickcheck_impl {
794 extern crate alloc;
795
796 use alloc::boxed::Box;
797 use quickcheck::{Arbitrary, Gen};
798
799 use super::Timestamp;
800
801 impl Arbitrary for Timestamp {
802 #[inline(always)]
803 fn arbitrary(g: &mut Gen) -> Self {
804 Timestamp(Arbitrary::arbitrary(g))
805 }
806
807 fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
808 Box::new(
809 (self.date(), self.time())
810 .shrink()
811 .map(|(d, t)| Timestamp(time::PrimitiveDateTime::new(d, t))),
812 )
813 }
814 }
815}
816
817#[cfg(feature = "ramhorns")]
818mod ramhorns_impl {
819 use super::{formats::FullMilliseconds, ts_str::IsValidFormat, Timestamp};
820
821 use generic_array::typenum::Unsigned;
822 use ramhorns::{encoding::Encoder, Content};
823
824 impl Content for Timestamp {
825 fn capacity_hint(&self, _tpl: &ramhorns::Template) -> usize {
826 <FullMilliseconds as IsValidFormat>::Length::USIZE
827 }
828
829 fn render_escaped<E: Encoder>(&self, encoder: &mut E) -> Result<(), E::Error> {
830 encoder.write_unescaped(&self.format())
831 }
832 }
833
834 #[cfg(feature = "rkyv_08")]
835 const _: () = {
836 impl Content for super::ArchivedTimestamp {
837 fn capacity_hint(&self, _tpl: &ramhorns::Template) -> usize {
838 <FullMilliseconds as IsValidFormat>::Length::USIZE
839 }
840
841 fn render_escaped<E: Encoder>(&self, encoder: &mut E) -> Result<(), E::Error> {
842 encoder.write_unescaped(&Timestamp::from(*self).format())
843 }
844 }
845 };
846}
847
848#[cfg(feature = "rkyv_08")]
849pub use rkyv_08_impl::ArchivedTimestamp;
850
851#[cfg(feature = "rkyv_08")]
852mod rkyv_08_impl {
853 use super::*;
854
855 use rkyv_08::{
856 bytecheck::CheckBytes,
857 place::Place,
858 rancor::{Fallible, Source},
859 traits::NoUndef,
860 Archive, Archived, Deserialize, Serialize,
861 };
862
863 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, rkyv_08::Portable)]
865 #[rkyv(crate = rkyv_08)]
866 #[repr(transparent)]
867 pub struct ArchivedTimestamp(pub Archived<i64>);
868
869 unsafe impl NoUndef for ArchivedTimestamp {}
871
872 impl ArchivedTimestamp {
873 #[inline(always)]
875 #[must_use]
876 pub const fn get(self) -> i64 {
877 self.0.to_native()
878 }
879 }
880
881 impl From<ArchivedTimestamp> for Timestamp {
882 fn from(value: ArchivedTimestamp) -> Self {
883 Timestamp::UNIX_EPOCH
884 .checked_add(Duration::milliseconds(value.get()))
885 .unwrap_or(Timestamp::UNIX_EPOCH) }
887 }
888
889 impl Archive for Timestamp {
890 type Archived = ArchivedTimestamp;
891 type Resolver = ();
892
893 fn resolve(&self, _resolver: Self::Resolver, out: Place<Self::Archived>) {
894 out.write(ArchivedTimestamp(<Archived<i64>>::from_native(
895 self.duration_since(Timestamp::UNIX_EPOCH).whole_milliseconds() as i64,
896 )));
897 }
898 }
899
900 impl<S: Fallible + ?Sized> Serialize<S> for Timestamp {
901 #[inline(always)]
902 fn serialize(&self, _serializer: &mut S) -> Result<Self::Resolver, S::Error> {
903 Ok(())
904 }
905 }
906
907 impl<D: Fallible + ?Sized> Deserialize<Timestamp, D> for ArchivedTimestamp {
908 #[inline]
909 fn deserialize(&self, _deserializer: &mut D) -> Result<Timestamp, <D as Fallible>::Error> {
910 Ok(Timestamp::from(*self))
911 }
912 }
913
914 unsafe impl<C> CheckBytes<C> for ArchivedTimestamp
916 where
917 C: Fallible + ?Sized,
918 <C as Fallible>::Error: Source,
919 {
920 #[inline(always)]
921 unsafe fn check_bytes<'a>(value: *const Self, context: &mut C) -> Result<(), C::Error> {
922 CheckBytes::<C>::check_bytes(value as *const Archived<i64>, context)
923 }
924 }
925}
926
927#[cfg(feature = "fred")]
928mod fred_impl {
929 use fred::{
930 error::{Error, ErrorKind},
931 types::{Expiration, FromKey, FromValue, Key, Value},
932 };
933
934 use super::{Duration, Timestamp};
935
936 impl From<Timestamp> for Value {
937 fn from(ts: Timestamp) -> Self {
938 Value::Integer(ts.duration_since(Timestamp::UNIX_EPOCH).whole_milliseconds() as i64)
939 }
940 }
941
942 impl From<Timestamp> for Key {
943 fn from(ts: Timestamp) -> Self {
944 Key::from(&*ts.format())
945 }
946 }
947
948 impl FromValue for Timestamp {
949 fn from_value(value: Value) -> Result<Self, Error> {
950 match value {
951 Value::String(ts) => Timestamp::parse(&ts)
952 .ok_or_else(|| Error::new(ErrorKind::Parse, "Invalid Timestamp format")),
953 Value::Bytes(ts) => match core::str::from_utf8(&ts) {
954 Ok(ts) => Timestamp::parse(ts)
955 .ok_or_else(|| Error::new(ErrorKind::Parse, "Invalid Timestamp format")),
956 Err(_) => Err(Error::new(ErrorKind::Parse, "Invalid UTF-8 Timestamp")),
957 },
958 Value::Integer(ts) => Timestamp::UNIX_EPOCH
959 .checked_add(Duration::seconds(ts))
960 .ok_or_else(|| Error::new(ErrorKind::Parse, "Timestamp out of range")),
961 _ => Err(Error::new(ErrorKind::Parse, "Invalid Timestamp type")),
962 }
963 }
964 }
965
966 impl FromKey for Timestamp {
967 fn from_key(value: Key) -> Result<Self, Error> {
968 let Ok(value) = core::str::from_utf8(value.as_bytes()) else {
969 return Err(Error::new(ErrorKind::Parse, "Invalid UTF-8 Key"));
970 };
971
972 Timestamp::parse(value).ok_or_else(|| Error::new(ErrorKind::Parse, "Invalid Timestamp format"))
973 }
974 }
975
976 impl From<Timestamp> for Expiration {
977 fn from(ts: Timestamp) -> Self {
978 Expiration::PXAT(ts.duration_since(Timestamp::UNIX_EPOCH).whole_milliseconds() as i64)
979 }
980 }
981
982 #[cfg(feature = "rkyv_08")]
983 const _: () = {
984 use super::ArchivedTimestamp;
985
986 impl From<ArchivedTimestamp> for Value {
987 fn from(ts: ArchivedTimestamp) -> Self {
988 Value::Integer(ts.get())
989 }
990 }
991
992 impl From<ArchivedTimestamp> for Key {
993 fn from(value: ArchivedTimestamp) -> Self {
994 Key::from(&*Timestamp::from(value).format())
995 }
996 }
997
998 impl From<ArchivedTimestamp> for Expiration {
999 fn from(ts: ArchivedTimestamp) -> Self {
1000 Expiration::PXAT(ts.get())
1001 }
1002 }
1003 };
1004}
1005
1006#[cfg(feature = "borsh")]
1007mod borsh_impl {
1008 use super::{Duration, Timestamp};
1009
1010 use borsh::{io, BorshDeserialize, BorshSerialize};
1011
1012 impl BorshSerialize for Timestamp {
1013 fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
1014 let ts = self.duration_since(Timestamp::UNIX_EPOCH).whole_milliseconds() as i64;
1015
1016 ts.serialize(writer)
1017 }
1018 }
1019
1020 impl BorshDeserialize for Timestamp {
1021 fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
1022 let ts = i64::deserialize_reader(reader)?;
1023
1024 Timestamp::UNIX_EPOCH
1025 .checked_add(Duration::milliseconds(ts))
1026 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Timestamp out of range"))
1027 }
1028 }
1029}