datetime_string/rfc3339/
full_time.rs

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