iso8601_timestamp/
ts_str.rs

1use core::marker::PhantomData;
2use core::ops::{Add, Mul};
3use generic_array::{typenum as t, ArrayLength, GenericArray};
4
5mod sealed {
6    pub trait Sealed {}
7}
8
9// convert boolean/bit to integer
10type I<BOOL> = t::UInt<t::UTerm, BOOL>;
11
12// 4 bytes for full-formatting (--::)
13type F4<F> = t::Prod<I<F>, t::U4>;
14// 5 bytes for offset (00:00)
15type O5<O> = t::Prod<I<O>, t::U5>;
16// '+' + 4Y + 2M + 2D + T + 2H + 2m + 2s + Z
17type P17<P> = t::Sum<P, t::U17>;
18type P18<P> = t::Sum<P17<P>, I<t::Gr<P, t::U0>>>; // accounts for . that's only present when P>0
19type F4O5<F, O> = t::Sum<F4<F>, O5<O>>;
20
21type StrLen<F, O, P> = t::Sum<P18<P>, F4O5<F, O>>;
22
23#[doc(hidden)]
24pub struct FormatString<F, O, P>(PhantomData<(F, O, P)>);
25
26impl<F, O, P> sealed::Sealed for FormatString<F, O, P> {}
27
28#[doc(hidden)]
29pub trait IsValidFormat: sealed::Sealed {
30    type Length: ArrayLength;
31    type Storage: AsRef<[u8]> + AsMut<[u8]> + Clone + Copy + Default;
32}
33
34impl<F, O, P> IsValidFormat for FormatString<F, O, P>
35where
36    F: t::Bit,
37    I<F>: Mul<t::U4>,
38    O: t::Bit,
39    I<O>: Mul<t::U5>,
40    P: t::Unsigned + Add<t::U17> + t::IsLessOrEqual<t::U9, Output = t::True> + t::IsGreater<t::U0>,
41    F4<F>: Add<O5<O>>,
42    P17<P>: Add<I<t::Gr<P, t::U0>>>,
43    P18<P>: Add<F4O5<F, O>>,
44    StrLen<F, O, P>: ArrayLength,
45
46    <StrLen<F, O, P> as ArrayLength>::ArrayType<u8>: Copy,
47{
48    type Length = StrLen<F, O, P>;
49    type Storage = GenericArray<u8, Self::Length>;
50}
51
52#[allow(unused_assignments)]
53#[inline(always)]
54#[rustfmt::skip]
55pub fn template<F: t::Bit, O: t::Bit, P: t::Unsigned>() -> <FormatString<F, O, P> as IsValidFormat>::Storage
56where
57    FormatString<F, O, P>: IsValidFormat,
58{
59    let mut value: <FormatString<F, O, P> as IsValidFormat>::Storage = Default::default();
60
61    macro_rules! w {
62        ($x:literal) => {value.as_mut().copy_from_slice($x)};
63    }
64
65    match (F::BOOL, O::BOOL, P::USIZE) {
66        (true,  true,  0) => w!(b"+0000-00-00T00:00:00+00:00"),
67        (true,  true,  1) => w!(b"+0000-00-00T00:00:00.0+00:00"),
68        (true,  true,  2) => w!(b"+0000-00-00T00:00:00.00+00:00"),
69        (true,  true,  3) => w!(b"+0000-00-00T00:00:00.000+00:00"),
70        (true,  true,  4) => w!(b"+0000-00-00T00:00:00.0000+00:00"),
71        (true,  true,  5) => w!(b"+0000-00-00T00:00:00.00000+00:00"),
72        (true,  true,  6) => w!(b"+0000-00-00T00:00:00.000000+00:00"),
73        (true,  true,  7) => w!(b"+0000-00-00T00:00:00.0000000+00:00"),
74        (true,  true,  8) => w!(b"+0000-00-00T00:00:00.00000000+00:00"),
75        (true,  true,  9) => w!(b"+0000-00-00T00:00:00.000000000+00:00"),
76        (true,  false, 0) => w!(b"+0000-00-00T00:00:00Z"),
77        (true,  false, 1) => w!(b"+0000-00-00T00:00:00.0Z"),
78        (true,  false, 2) => w!(b"+0000-00-00T00:00:00.00Z"),
79        (true,  false, 3) => w!(b"+0000-00-00T00:00:00.000Z"),
80        (true,  false, 4) => w!(b"+0000-00-00T00:00:00.0000Z"),
81        (true,  false, 5) => w!(b"+0000-00-00T00:00:00.00000Z"),
82        (true,  false, 6) => w!(b"+0000-00-00T00:00:00.000000Z"),
83        (true,  false, 7) => w!(b"+0000-00-00T00:00:00.0000000Z"),
84        (true,  false, 8) => w!(b"+0000-00-00T00:00:00.00000000Z"),
85        (true,  false, 9) => w!(b"+0000-00-00T00:00:00.000000000Z"),
86        (false, true,  0) => w!(b"+00000000T000000+00:00"),
87        (false, true,  1) => w!(b"+00000000T000000.0+00:00"),
88        (false, true,  2) => w!(b"+00000000T000000.00+00:00"),
89        (false, true,  3) => w!(b"+00000000T000000.000+00:00"),
90        (false, true,  4) => w!(b"+00000000T000000.0000+00:00"),
91        (false, true,  5) => w!(b"+00000000T000000.00000+00:00"),
92        (false, true,  6) => w!(b"+00000000T000000.000000+00:00"),
93        (false, true,  7) => w!(b"+00000000T000000.0000000+00:00"),
94        (false, true,  8) => w!(b"+00000000T000000.00000000+00:00"),
95        (false, true,  9) => w!(b"+00000000T000000.000000000+00:00"),
96        (false, false, 0) => w!(b"+00000000T000000Z"),
97        (false, false, 1) => w!(b"+00000000T000000.0Z"),
98        (false, false, 2) => w!(b"+00000000T000000.00Z"),
99        (false, false, 3) => w!(b"+00000000T000000.000Z"),
100        (false, false, 4) => w!(b"+00000000T000000.0000Z"),
101        (false, false, 5) => w!(b"+00000000T000000.00000Z"),
102        (false, false, 6) => w!(b"+00000000T000000.000000Z"),
103        (false, false, 7) => w!(b"+00000000T000000.0000000Z"),
104        (false, false, 8) => w!(b"+00000000T000000.00000000Z"),
105        (false, false, 9) => w!(b"+00000000T000000.000000000Z"),
106
107        // SAFETY: Guaranteed by the IsValidFormat trait
108        _ => unsafe { core::hint::unreachable_unchecked() },
109    }
110
111    value
112}
113
114/// Fixed-size inline string storage that exactly fits the formatted timestamp.
115#[derive(Clone, Copy)]
116#[repr(transparent)]
117pub struct TimestampStr<S: IsValidFormat>(pub(crate) S::Storage);
118
119impl<S: IsValidFormat> TimestampStr<S> {
120    /// The maximum length this timestamp string can be in bytes (and characters).
121    ///
122    /// If the timestamp is positive, that is after the year 0000, the formatted
123    /// string will be `MAX_LEN - 1` as it will not print the `+` sign in front.
124    ///
125    /// This value is equal to `core::mem::size_of::<TimestampStr<S>>()`,
126    /// as the internal representation of `TimestampStr` is just `[u8; MAX_LEN]`.
127    pub const MAX_LEN: usize = <S::Length as t::Unsigned>::USIZE;
128}
129
130#[cfg(test)]
131mod ts_str_tests {
132    use crate::{formats as f, IsValidFormat, TimestampStr};
133    use core::mem::size_of;
134
135    #[test]
136    fn test_ts_str_size_of() {
137        fn assert_size_of<S: IsValidFormat>() {
138            assert_eq!(size_of::<TimestampStr<S>>(), <TimestampStr<S>>::MAX_LEN);
139        }
140
141        assert_size_of::<f::FullMicroseconds>();
142        assert_size_of::<f::FullMilliseconds>();
143        assert_size_of::<f::FullMillisecondsOffset>();
144        assert_size_of::<f::FullNanoseconds>();
145        assert_size_of::<f::ShortMilliseconds>();
146    }
147}
148
149impl<S: IsValidFormat> AsRef<str> for TimestampStr<S> {
150    #[inline]
151    fn as_ref(&self) -> &str {
152        // SAFETY: String is guaranteed to be valid UTF-8,
153        // just need to offset it to avoid the + sign if positive.
154        unsafe {
155            // skip + sign if positive
156            let bytes = self.0.as_ref();
157            let is_positive = *bytes.get_unchecked(0) == b'+';
158            core::str::from_utf8_unchecked(bytes.get_unchecked(is_positive as usize..))
159        }
160    }
161}
162
163use core::borrow::Borrow;
164
165impl<S: IsValidFormat> Borrow<str> for TimestampStr<S> {
166    #[inline]
167    fn borrow(&self) -> &str {
168        self.as_ref()
169    }
170}
171
172use core::ops::Deref;
173
174impl<S: IsValidFormat> Deref for TimestampStr<S> {
175    type Target = str;
176
177    #[inline(always)]
178    fn deref(&self) -> &Self::Target {
179        self.as_ref()
180    }
181}
182
183impl<S: IsValidFormat> PartialEq for TimestampStr<S> {
184    #[inline]
185    fn eq(&self, other: &Self) -> bool {
186        self.0.as_ref() == other.0.as_ref()
187    }
188}
189
190impl<S: IsValidFormat> PartialEq<str> for TimestampStr<S> {
191    #[inline]
192    fn eq(&self, other: &str) -> bool {
193        self.as_ref() == other
194    }
195}
196
197impl<S: IsValidFormat> PartialEq<&str> for TimestampStr<S> {
198    #[inline]
199    fn eq(&self, other: &&str) -> bool {
200        self.as_ref() == *other
201    }
202}
203
204impl<S: IsValidFormat> PartialEq<TimestampStr<S>> for str {
205    #[inline]
206    fn eq(&self, other: &TimestampStr<S>) -> bool {
207        self == other.as_ref()
208    }
209}
210
211impl<S: IsValidFormat> PartialEq<TimestampStr<S>> for &str {
212    #[inline]
213    fn eq(&self, other: &TimestampStr<S>) -> bool {
214        *self == other.as_ref()
215    }
216}
217
218use core::fmt;
219
220impl<S: IsValidFormat> fmt::Debug for TimestampStr<S> {
221    #[inline(always)]
222    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223        fmt::Debug::fmt(self.as_ref(), f)
224    }
225}
226
227impl<S: IsValidFormat> fmt::Display for TimestampStr<S> {
228    #[inline(always)]
229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230        fmt::Display::fmt(self.as_ref(), f)
231    }
232}
233
234#[cfg(feature = "serde")]
235mod serde_impl {
236    use serde_core::ser::{Serialize, Serializer};
237
238    use super::{IsValidFormat, TimestampStr};
239
240    impl<STORAGE: IsValidFormat> Serialize for TimestampStr<STORAGE> {
241        #[inline]
242        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
243        where
244            S: Serializer,
245        {
246            serializer.serialize_str(self)
247        }
248    }
249}