datetime_string/rfc3339/
partial_time.rs

1//! RFC 3339 [`partial-time`] string types.
2//!
3//! [`partial-time`]: https://tools.ietf.org/html/rfc3339#section-5.6
4
5#[cfg(feature = "alloc")]
6mod owned;
7
8use core::{cmp::Ordering, convert::TryFrom, fmt, ops, str};
9
10use crate::{
11    common::Hms6ColonStr,
12    error::{Error, ErrorKind},
13    rfc3339::SecfracStr,
14};
15
16#[cfg(feature = "alloc")]
17pub use self::owned::PartialTimeString;
18
19/// Minimum length of `partial-time` string (i.e. length of `hh:mm:ss`).
20const PARTIAL_TIME_LEN_MIN: usize = 8;
21
22/// Validates the given string as an RFC 3339 [`partial-time`] string.
23///
24/// [`partial-time`]: https://tools.ietf.org/html/rfc3339#section-5.6
25fn validate_bytes(s: &[u8]) -> Result<(), Error> {
26    let (hms, dotfrac) = match s.len().cmp(&PARTIAL_TIME_LEN_MIN) {
27        Ordering::Greater => s.split_at(PARTIAL_TIME_LEN_MIN),
28        Ordering::Less => return Err(ErrorKind::TooShort.into()),
29        Ordering::Equal => return Hms6ColonStr::from_bytes(s).map(|_| ()),
30    };
31    debug_assert!(
32        !dotfrac.is_empty(),
33        "If `dotfrac` component is available, it should be non-empty string"
34    );
35
36    Hms6ColonStr::from_bytes(hms)?;
37    SecfracStr::from_bytes(dotfrac)?;
38
39    Ok(())
40}
41
42/// String slice for a time in RFC 3339 [`partial-time`] format, such as `12:34:56.7890`.
43///
44/// This is "partial", because it is not associated to a time offset.
45///
46/// [`partial-time`]: https://tools.ietf.org/html/rfc3339#section-5.6
47#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
48#[repr(transparent)]
49// Note that `derive(Serialize)` cannot used here, because it encodes this as
50// `[u8]` rather than as a string.
51//
52// Comparisons implemented for the type are consistent (at least it is intended to be so).
53// See <https://github.com/rust-lang/rust-clippy/issues/2025>.
54// Note that `clippy::derive_ord_xor_partial_ord` would be introduced since Rust 1.47.0.
55#[allow(clippy::derive_hash_xor_eq)]
56#[allow(unknown_lints, clippy::derive_ord_xor_partial_ord)]
57pub struct PartialTimeStr([u8]);
58
59impl PartialTimeStr {
60    /// Creates a `&PartialTimeStr` from the given byte slice.
61    ///
62    /// This performs assertion in debug build, but not in release build.
63    ///
64    /// # Safety
65    ///
66    /// `validate_bytes(s)` should return `Ok(())`.
67    #[inline]
68    #[must_use]
69    pub(crate) unsafe fn from_bytes_maybe_unchecked(s: &[u8]) -> &Self {
70        debug_assert_ok!(validate_bytes(s));
71        &*(s as *const [u8] as *const Self)
72    }
73
74    /// Creates a `&mut PartialTimeStr` from the given mutable byte slice.
75    ///
76    /// This performs assertion in debug build, but not in release build.
77    ///
78    /// # Safety
79    ///
80    /// `validate_bytes(s)` should return `Ok(())`.
81    #[inline]
82    #[must_use]
83    pub(crate) unsafe fn from_bytes_maybe_unchecked_mut(s: &mut [u8]) -> &mut Self {
84        debug_assert_ok!(validate_bytes(s));
85        &mut *(s as *mut [u8] as *mut Self)
86    }
87
88    /// Creates a `&mut PartialTimeStr` from the given mutable string slice.
89    ///
90    /// This performs assertion in debug build, but not in release build.
91    ///
92    /// # Safety
93    ///
94    /// `validate_bytes(s.as_bytes())` should return `Ok(())`.
95    #[inline]
96    #[must_use]
97    unsafe fn from_str_maybe_unchecked_mut(s: &mut str) -> &mut Self {
98        // This is safe because `PartialTimeStr` ensures that the underlying
99        // bytes are ASCII string after modification.
100        Self::from_bytes_maybe_unchecked_mut(s.as_bytes_mut())
101    }
102
103    /// Creates a new `&PartialTimeStr` from a string slice.
104    ///
105    /// # Examples
106    ///
107    /// ```
108    /// # use datetime_string::rfc3339::PartialTimeStr;
109    /// let time = PartialTimeStr::from_str("12:34:56.7890")?;
110    /// assert_eq!(time.as_str(), "12:34:56.7890");
111    ///
112    /// assert!(PartialTimeStr::from_str("12:34:56").is_ok());
113    /// assert!(PartialTimeStr::from_str("12:34:56.0").is_ok());
114    /// assert!(PartialTimeStr::from_str("12:34:56.01234567890").is_ok());
115    ///
116    /// assert!(PartialTimeStr::from_str("12:34:56.").is_err());
117    /// assert!(PartialTimeStr::from_str(".").is_err());
118    /// assert!(PartialTimeStr::from_str("12:34.56").is_err());
119    /// # Ok::<_, datetime_string::Error>(())
120    /// ```
121    #[inline]
122    // `FromStr` trait cannot be implemented for a slice.
123    #[allow(clippy::should_implement_trait)]
124    pub fn from_str(s: &str) -> Result<&Self, Error> {
125        TryFrom::try_from(s)
126    }
127
128    /// Creates a new `&mut PartialTimeStr` from a mutable string slice.
129    ///
130    /// # Examples
131    ///
132    /// ```
133    /// # use datetime_string::rfc3339::PartialTimeStr;
134    /// let mut buf = "12:34:56.7890".to_owned();
135    /// let time = PartialTimeStr::from_mut_str(&mut buf)?;
136    /// assert_eq!(time.as_str(), "12:34:56.7890");
137    ///
138    /// time.hms_mut().set_time(23, 12, 01);
139    /// time.secfrac_mut().unwrap().digits_mut().fill_with_zero();
140    /// assert_eq!(time.as_str(), "23:12:01.0000");
141    ///
142    /// assert_eq!(buf, "23:12:01.0000");
143    /// # Ok::<_, datetime_string::Error>(())
144    /// ```
145    #[inline]
146    pub fn from_mut_str(s: &mut str) -> Result<&mut Self, Error> {
147        TryFrom::try_from(s)
148    }
149
150    /// Creates a new `&PartialTimeStr` from a byte slice.
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// # use datetime_string::rfc3339::PartialTimeStr;
156    /// let time = PartialTimeStr::from_bytes(b"12:34:56.7890")?;
157    /// assert_eq!(time.as_str(), "12:34:56.7890");
158    ///
159    /// assert!(PartialTimeStr::from_bytes(b"12:34:56").is_ok());
160    /// assert!(PartialTimeStr::from_bytes(b"12:34:56.0").is_ok());
161    /// assert!(PartialTimeStr::from_bytes(b"12:34:56.01234567890").is_ok());
162    ///
163    /// assert!(PartialTimeStr::from_bytes(b"12:34:56.").is_err());
164    /// assert!(PartialTimeStr::from_bytes(b".").is_err());
165    /// assert!(PartialTimeStr::from_bytes(b"12:34.56").is_err());
166    /// # Ok::<_, datetime_string::Error>(())
167    /// ```
168    #[inline]
169    pub fn from_bytes(s: &[u8]) -> Result<&Self, Error> {
170        TryFrom::try_from(s)
171    }
172
173    /// Creates a new `&mut PartialTimeStr` from a mutable byte slice.
174    ///
175    /// # Examples
176    ///
177    /// ```
178    /// # use datetime_string::rfc3339::PartialTimeStr;
179    /// let mut buf: [u8; 13] = *b"12:34:56.7890";
180    /// let time = PartialTimeStr::from_bytes_mut(&mut buf)?;
181    /// assert_eq!(time.as_str(), "12:34:56.7890");
182    ///
183    /// time.hms_mut().set_time(23, 12, 01);
184    /// time.secfrac_mut().unwrap().digits_mut().fill_with_zero();
185    /// assert_eq!(time.as_str(), "23:12:01.0000");
186    ///
187    /// assert_eq!(&buf[..], b"23:12:01.0000");
188    /// # Ok::<_, datetime_string::Error>(())
189    /// ```
190    #[inline]
191    pub fn from_bytes_mut(s: &mut [u8]) -> Result<&mut Self, Error> {
192        TryFrom::try_from(s)
193    }
194
195    /// Returns a string slice.
196    ///
197    /// # Examples
198    ///
199    /// ```
200    /// # use datetime_string::rfc3339::PartialTimeStr;
201    /// let time = PartialTimeStr::from_str("12:34:56.7890")?;
202    ///
203    /// assert_eq!(time.as_str(), "12:34:56.7890");
204    /// # Ok::<_, datetime_string::Error>(())
205    /// ```
206    #[inline]
207    #[must_use]
208    pub fn as_str(&self) -> &str {
209        unsafe {
210            // This is safe because the `PartialTimeStr` ensures that the
211            // underlying bytes are ASCII string.
212            debug_assert_safe_version_ok!(str::from_utf8(&self.0));
213            str::from_utf8_unchecked(&self.0)
214        }
215    }
216
217    /// Returns a byte slice.
218    ///
219    /// If you want to use indexed access, prefer [`as_bytes_fixed_len`].
220    ///
221    /// # Examples
222    ///
223    /// ```
224    /// # use datetime_string::rfc3339::PartialTimeStr;
225    /// let time = PartialTimeStr::from_str("12:34:56.7890")?;
226    ///
227    /// assert_eq!(time.as_str(), "12:34:56.7890");
228    /// # Ok::<_, datetime_string::Error>(())
229    /// ```
230    ///
231    /// [`as_bytes_fixed_len`]: #method.as_bytes_fixed_len
232    #[inline]
233    #[must_use]
234    pub fn as_bytes(&self) -> &[u8] {
235        &self.0
236    }
237
238    /// Returns a `hh:mm:ss` substring.
239    ///
240    /// # Examples
241    ///
242    /// ```
243    /// # use datetime_string::rfc3339::PartialTimeStr;
244    /// let time = PartialTimeStr::from_str("12:34:56.7890")?;
245    ///
246    /// assert_eq!(time.hms(), "12:34:56");
247    /// # Ok::<_, datetime_string::Error>(())
248    /// ```
249    #[inline]
250    #[must_use]
251    pub fn hms(&self) -> &Hms6ColonStr {
252        unsafe {
253            // This is safe because a valid partial-time string has `hh:mm:ss` as a prefix.
254            debug_assert_safe_version_ok!(Hms6ColonStr::from_bytes(
255                &self.0[..PARTIAL_TIME_LEN_MIN]
256            ));
257            Hms6ColonStr::from_bytes_maybe_unchecked(self.0.get_unchecked(..PARTIAL_TIME_LEN_MIN))
258        }
259    }
260
261    /// Returns a mutable `hh:mm:ss` substring.
262    ///
263    /// # Examples
264    ///
265    /// ```
266    /// # use datetime_string::rfc3339::PartialTimeStr;
267    /// let mut buf = "12:34:56.7890".to_owned();
268    /// let time = PartialTimeStr::from_mut_str(&mut buf)?;
269    /// assert_eq!(time.as_str(), "12:34:56.7890");
270    ///
271    /// time.hms_mut().set_time(23, 12, 1);
272    /// assert_eq!(time.as_str(), "23:12:01.7890");
273    /// # Ok::<_, datetime_string::Error>(())
274    /// ```
275    #[inline]
276    #[must_use]
277    pub fn hms_mut(&mut self) -> &mut Hms6ColonStr {
278        unsafe {
279            // This is safe because a valid partial-time string has `hh:mm:ss`
280            // as a prefix, and `Hms6ColonStr` ensures that the underlying bytes
281            // are ASCII string after modification.
282            debug_assert_ok!(Hms6ColonStr::from_bytes(&self.0[..PARTIAL_TIME_LEN_MIN]));
283            Hms6ColonStr::from_bytes_maybe_unchecked_mut(
284                self.0.get_unchecked_mut(..PARTIAL_TIME_LEN_MIN),
285            )
286        }
287    }
288
289    /// Returns [`time-secfrac`] substring.
290    ///
291    /// # Examples
292    ///
293    /// ```
294    /// # use datetime_string::rfc3339::PartialTimeStr;
295    /// let time = PartialTimeStr::from_str("12:34:56.7890")?;
296    ///
297    /// assert_eq!(time.secfrac().unwrap(), ".7890");
298    /// # Ok::<_, datetime_string::Error>(())
299    /// ```
300    ///
301    /// [`time-secfrac`]: https://tools.ietf.org/html/rfc3339#section-5.6
302    #[inline]
303    #[must_use]
304    pub fn secfrac(&self) -> Option<&SecfracStr> {
305        self.0.get(PARTIAL_TIME_LEN_MIN..).map(|v| {
306            unsafe {
307                // This is safe because a valid partial-time string which is longer than
308                // PARTIAL_TIME_LEN_MIN (== "hh:mm:ss".len()) has time-secfrac as a prefix.
309                debug_assert_safe_version_ok!(SecfracStr::from_bytes(v));
310                SecfracStr::from_bytes_maybe_unchecked(v)
311            }
312        })
313    }
314
315    /// Returns a mutable [`time-secfrac`] substring.
316    ///
317    /// # Examples
318    ///
319    /// ```
320    /// # use datetime_string::rfc3339::PartialTimeStr;
321    /// let mut buf = "12:34:56.7890".to_owned();
322    /// let time = PartialTimeStr::from_mut_str(&mut buf)?;
323    /// assert_eq!(time.as_str(), "12:34:56.7890");
324    ///
325    /// time.hms_mut().set_time(23, 12, 1);
326    /// assert_eq!(time.as_str(), "23:12:01.7890");
327    /// # Ok::<_, datetime_string::Error>(())
328    /// ```
329    ///
330    /// [`time-secfrac`]: https://tools.ietf.org/html/rfc3339#section-5.6
331    #[inline]
332    #[must_use]
333    pub fn secfrac_mut(&mut self) -> Option<&mut SecfracStr> {
334        unsafe {
335            // This is safe because a valid partial-time string which is longer than
336            // PARTIAL_TIME_LEN_MIN (== "hh:mm:ss".len()) has time-secfrac as a prefix,
337            // and `SecfracStr` ensures that the underlying bytes are ASCII string
338            // after modification.
339            debug_assert_safe_version_ok!(self
340                .0
341                .get_mut(PARTIAL_TIME_LEN_MIN..)
342                .map(|v| SecfracStr::from_bytes(v))
343                .transpose());
344            self.0
345                .get_mut(PARTIAL_TIME_LEN_MIN..)
346                .map(|v| SecfracStr::from_bytes_maybe_unchecked_mut(v))
347        }
348    }
349
350    /// Converts the time to [`chrono::NaiveTime`][`chrono04::NaiveTime`] of chrono v0.4.
351    ///
352    /// Note that this truncates subnanosecond secfrac.
353    ///
354    /// Enabled by `chrono04` feature.
355    ///
356    /// # Examples
357    ///
358    /// ```
359    /// # use datetime_string::rfc3339::PartialTimeStr;
360    /// use chrono04::NaiveTime;
361    ///
362    /// let time = PartialTimeStr::from_str("12:34:56.01234567899999")?;
363    /// assert_eq!(time.to_chrono_naive_time(), NaiveTime::from_hms_nano(12, 34, 56, 12_345_678));
364    ///
365    /// let leap = PartialTimeStr::from_str("23:59:60.876543210999")?;
366    /// assert_eq!(leap.to_chrono_naive_time(), NaiveTime::from_hms_nano(23, 59, 59, 1_876_543_210));
367    /// # Ok::<_, datetime_string::Error>(())
368    /// ```
369    #[cfg(feature = "chrono04")]
370    #[cfg_attr(docsrs, doc(cfg(feature = "chrono04")))]
371    #[deprecated(since = "0.2.2", note = "renamed to `to_chrono04_naive_time`")]
372    #[inline]
373    #[must_use]
374    pub fn to_chrono_naive_time(&self) -> chrono04::NaiveTime {
375        self.to_chrono04_naive_time()
376    }
377
378    /// Converts the time to [`chrono::NaiveTime`][`chrono04::NaiveTime`] of chrono v0.4.
379    ///
380    /// Note that this truncates subnanosecond secfrac.
381    ///
382    /// Enabled by `chrono04` feature.
383    ///
384    /// # Examples
385    ///
386    /// ```
387    /// # use datetime_string::rfc3339::PartialTimeStr;
388    /// use chrono04::NaiveTime;
389    ///
390    /// let time = PartialTimeStr::from_str("12:34:56.01234567899999")?;
391    /// assert_eq!(time.to_chrono04_naive_time(), NaiveTime::from_hms_nano(12, 34, 56, 12_345_678));
392    ///
393    /// let leap = PartialTimeStr::from_str("23:59:60.876543210999")?;
394    /// assert_eq!(leap.to_chrono04_naive_time(), NaiveTime::from_hms_nano(23, 59, 59, 1_876_543_210));
395    /// # Ok::<_, datetime_string::Error>(())
396    /// ```
397    #[cfg(feature = "chrono04")]
398    #[cfg_attr(docsrs, doc(cfg(feature = "chrono04")))]
399    pub fn to_chrono04_naive_time(&self) -> chrono04::NaiveTime {
400        use chrono04::Timelike;
401
402        let hms: chrono04::NaiveTime = self.hms().into();
403        debug_assert!(hms.nanosecond() <= 1_000_000_000);
404        let secfrac: u32 = self
405            .secfrac()
406            .map_or(0, |secfrac| secfrac.digits().nanoseconds());
407        debug_assert!(secfrac < 1_000_000_000);
408        hms.with_nanosecond(hms.nanosecond() + secfrac)
409            .expect("Should never fail: `hms.nanoseconds() + secfrac` is less than 2 seconds")
410    }
411
412    /// Converts the time to [`time::Time`][`time03::Time`] of `time` crate v0.3.
413    ///
414    /// Note that this truncates subnanosecond secfrac.
415    ///
416    /// Leap seconds are ignored and the previous second is used, since `time`
417    /// v0.3 does not support leap seconds. Subseconds part is preserved even
418    /// in such cases.
419    ///
420    /// Enabled by `time03` feature.
421    ///
422    /// # Examples
423    ///
424    /// ```
425    /// # use datetime_string::rfc3339::PartialTimeStr;
426    /// use time03::Time;
427    ///
428    /// let time = PartialTimeStr::from_str("12:34:56.01234567899999")?;
429    /// assert_eq!(
430    ///     time.to_time03_time(),
431    ///     Time::from_hms_nano(12, 34, 56, 12_345_678)
432    ///         .expect("valid time")
433    /// );
434    ///
435    /// let leap = PartialTimeStr::from_str("23:59:60.876543210999")?;
436    /// // Leap second is ignored and the previous second is used.
437    /// // Subseconds `.876543210` is still preserved in this case.
438    /// assert_eq!(
439    ///     leap.to_time03_time(),
440    ///     Time::from_hms_nano(23, 59, 59, 876_543_210)
441    ///         .expect("valid time")
442    /// );
443    /// # Ok::<_, datetime_string::Error>(())
444    /// ```
445    #[cfg(feature = "time03")]
446    #[cfg_attr(docsrs, doc(cfg(feature = "time03")))]
447    pub fn to_time03_time(&self) -> time03::Time {
448        let hms = self.hms();
449        let hour = hms.hour();
450        let minute = hms.minute();
451        let second = hms.second().min(59);
452        let nanosecond = self
453            .secfrac()
454            .map_or(0, |secfrac| secfrac.digits().nanoseconds());
455        debug_assert!(nanosecond < 1_000_000_000);
456        time03::Time::from_hms_nano(hour, minute, second, nanosecond)
457            .expect("[validity] valid time and no leap seconds")
458    }
459}
460
461impl AsRef<[u8]> for PartialTimeStr {
462    #[inline]
463    fn as_ref(&self) -> &[u8] {
464        &self.0
465    }
466}
467
468impl AsRef<str> for PartialTimeStr {
469    #[inline]
470    fn as_ref(&self) -> &str {
471        self.as_str()
472    }
473}
474
475impl AsRef<PartialTimeStr> for PartialTimeStr {
476    #[inline]
477    fn as_ref(&self) -> &PartialTimeStr {
478        self
479    }
480}
481
482impl AsMut<PartialTimeStr> for PartialTimeStr {
483    #[inline]
484    fn as_mut(&mut self) -> &mut PartialTimeStr {
485        self
486    }
487}
488
489impl<'a> From<&'a PartialTimeStr> for &'a str {
490    #[inline]
491    fn from(v: &'a PartialTimeStr) -> Self {
492        v.as_str()
493    }
494}
495
496impl<'a> TryFrom<&'a [u8]> for &'a PartialTimeStr {
497    type Error = Error;
498
499    #[inline]
500    fn try_from(v: &'a [u8]) -> Result<Self, Self::Error> {
501        validate_bytes(v)?;
502        Ok(unsafe {
503            // This is safe because a valid `partial-time` string is also an ASCII string.
504            PartialTimeStr::from_bytes_maybe_unchecked(v)
505        })
506    }
507}
508
509impl<'a> TryFrom<&'a mut [u8]> for &'a mut PartialTimeStr {
510    type Error = Error;
511
512    #[inline]
513    fn try_from(v: &'a mut [u8]) -> Result<Self, Self::Error> {
514        validate_bytes(v)?;
515        Ok(unsafe {
516            // This is safe because a valid `partial-time` string is also an ASCII string.
517            PartialTimeStr::from_bytes_maybe_unchecked_mut(v)
518        })
519    }
520}
521
522impl<'a> TryFrom<&'a str> for &'a PartialTimeStr {
523    type Error = Error;
524
525    #[inline]
526    fn try_from(v: &'a str) -> Result<Self, Self::Error> {
527        Self::try_from(v.as_bytes())
528    }
529}
530
531impl<'a> TryFrom<&'a mut str> for &'a mut PartialTimeStr {
532    type Error = Error;
533
534    #[inline]
535    fn try_from(v: &'a mut str) -> Result<Self, Self::Error> {
536        validate_bytes(v.as_bytes())?;
537        Ok(unsafe {
538            // This is safe because the string is already validated and
539            // `PartialTimeStr` ensures that the underlying bytes are ASCII
540            // string after modification.
541            PartialTimeStr::from_str_maybe_unchecked_mut(v)
542        })
543    }
544}
545
546impl fmt::Display for PartialTimeStr {
547    #[inline]
548    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
549        self.as_str().fmt(f)
550    }
551}
552
553impl ops::Deref for PartialTimeStr {
554    type Target = str;
555
556    #[inline]
557    fn deref(&self) -> &Self::Target {
558        self.as_str()
559    }
560}
561
562impl_cmp_symmetric!(str, PartialTimeStr, &PartialTimeStr);
563impl_cmp_symmetric!([u8], PartialTimeStr, [u8]);
564impl_cmp_symmetric!([u8], PartialTimeStr, &[u8]);
565impl_cmp_symmetric!([u8], &PartialTimeStr, [u8]);
566impl_cmp_symmetric!(str, PartialTimeStr, str);
567impl_cmp_symmetric!(str, PartialTimeStr, &str);
568impl_cmp_symmetric!(str, &PartialTimeStr, str);
569
570#[cfg(feature = "serde")]
571#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
572impl serde::Serialize for PartialTimeStr {
573    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
574    where
575        S: serde::Serializer,
576    {
577        serializer.serialize_str(self.as_str())
578    }
579}
580
581/// Items for serde support.
582#[cfg(feature = "serde")]
583mod serde_ {
584    use super::*;
585
586    use serde::de::{Deserialize, Deserializer, Visitor};
587
588    /// Visitor for `&PartialTimeStr`.
589    struct StrVisitor;
590
591    impl<'de> Visitor<'de> for StrVisitor {
592        type Value = &'de PartialTimeStr;
593
594        #[inline]
595        fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596            f.write_str("RFC 3339 partial-time string")
597        }
598
599        #[inline]
600        fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
601        where
602            E: serde::de::Error,
603        {
604            Self::Value::try_from(v).map_err(E::custom)
605        }
606
607        #[inline]
608        fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
609        where
610            E: serde::de::Error,
611        {
612            Self::Value::try_from(v).map_err(E::custom)
613        }
614    }
615
616    impl<'de> Deserialize<'de> for &'de PartialTimeStr {
617        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
618        where
619            D: Deserializer<'de>,
620        {
621            deserializer.deserialize_any(StrVisitor)
622        }
623    }
624}
625
626#[cfg(test)]
627mod tests {
628    #[cfg(feature = "serde")]
629    use super::*;
630
631    use super::validate_bytes as s_validate;
632
633    #[cfg(feature = "serde")]
634    use serde_test::{assert_de_tokens, assert_tokens, Token};
635
636    #[test]
637    fn validate_bytes() {
638        assert!(s_validate(b"00:00:00").is_ok());
639        assert!(s_validate(b"00:00:00.0000").is_ok());
640        assert!(s_validate(b"00:00:00.00000000000").is_ok());
641        assert!(s_validate(b"12:34:56.7890").is_ok());
642        assert!(s_validate(b"23:59:60").is_ok());
643        assert!(s_validate(b"23:59:60.9999").is_ok());
644        assert!(s_validate(b"23:59:60.01234567890").is_ok());
645        assert!(s_validate(b"23:59:60.99999999999").is_ok());
646    }
647
648    #[cfg(feature = "serde")]
649    #[test]
650    fn ser_de_str() {
651        let raw: &'static str = "12:34:56.7890";
652        assert_tokens(
653            &PartialTimeStr::from_str(raw).unwrap(),
654            &[Token::BorrowedStr(raw)],
655        );
656    }
657
658    #[cfg(feature = "serde")]
659    #[test]
660    fn de_bytes_slice() {
661        let raw: &'static [u8; 13] = b"12:34:56.7890";
662        assert_de_tokens(
663            &PartialTimeStr::from_bytes(raw).unwrap(),
664            &[Token::BorrowedBytes(raw)],
665        );
666    }
667}