datetime_string/rfc3339/
date_time.rs

1//! RFC 3339 [`date-time`] string types.
2//!
3//! [`date-time`]: https://tools.ietf.org/html/rfc3339#section-5.6
4
5#[cfg(feature = "alloc")]
6mod owned;
7
8use core::{
9    convert::TryFrom,
10    fmt,
11    ops::{self, RangeFrom, RangeTo},
12    str,
13};
14
15#[cfg(feature = "serde")]
16use serde::Serialize;
17
18use crate::error::{Error, ErrorKind};
19
20use super::{FullDateStr, FullTimeStr};
21
22#[cfg(feature = "alloc")]
23pub use self::owned::DateTimeString;
24
25/// Minimum length of the `date-time` string.
26///
27/// This is a length of `YYYY-MM-DDThh:mm:ssZ`.
28const DATETIME_LEN_MIN: usize = 20;
29/// Position of separator "T".
30const T_POS: usize = 10;
31/// Range of the date in a string.
32///
33/// This is always valid range for `full-date` string.
34const DATE_RANGE: RangeTo<usize> = ..T_POS;
35/// Range of the time in a string.
36///
37/// This is always valid range for `full-date` string.
38const TIME_RANGE: RangeFrom<usize> = (T_POS + 1)..;
39
40/// Validates the given string as an RFC 3339 [`date-time`].
41///
42/// [`date-time`]: https://tools.ietf.org/html/rfc3339#section-5.6
43fn validate_bytes(s: &[u8]) -> Result<(), Error> {
44    if s.len() < DATETIME_LEN_MIN {
45        return Err(ErrorKind::TooShort.into());
46    }
47
48    if s[T_POS] != b'T' {
49        return Err(ErrorKind::InvalidSeparator.into());
50    }
51
52    FullDateStr::from_bytes(&s[DATE_RANGE])?;
53    FullTimeStr::from_bytes(&s[TIME_RANGE])?;
54
55    Ok(())
56}
57
58/// String slice for a datetime in RFC 3339 [`date-time`] format, such as
59/// `2001-06-17T12:34:56.7890-23:12`.
60///
61/// [`date-time`]: https://tools.ietf.org/html/rfc3339#section-5.6
62#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
63#[repr(transparent)]
64// Note that `derive(Serialize)` cannot used here, because it encodes this as
65// `[u8]` rather than as a string.
66//
67// Comparisons implemented for the type are consistent (at least it is intended to be so).
68// See <https://github.com/rust-lang/rust-clippy/issues/2025>.
69// Note that `clippy::derive_ord_xor_partial_ord` would be introduced since Rust 1.47.0.
70#[allow(clippy::derive_hash_xor_eq)]
71#[allow(unknown_lints, clippy::derive_ord_xor_partial_ord)]
72pub struct DateTimeStr([u8]);
73
74impl DateTimeStr {
75    /// Creates a `&DateTimeStr` from the given byte slice.
76    ///
77    /// This performs assertion in debug build, but not in release build.
78    ///
79    /// # Safety
80    ///
81    /// `validate_bytes(s)` should return `Ok(())`.
82    #[inline]
83    #[must_use]
84    unsafe fn from_bytes_maybe_unchecked(s: &[u8]) -> &Self {
85        debug_assert_ok!(validate_bytes(s));
86        &*(s as *const [u8] as *const Self)
87    }
88
89    /// Creates a `&mut DateTimeStr` from the given mutable byte slice.
90    ///
91    /// This performs assertion in debug build, but not in release build.
92    ///
93    /// # Safety
94    ///
95    /// `validate_bytes(s)` should return `Ok(())`.
96    #[inline]
97    #[must_use]
98    unsafe fn from_bytes_maybe_unchecked_mut(s: &mut [u8]) -> &mut Self {
99        debug_assert_ok!(validate_bytes(s));
100        &mut *(s as *mut [u8] as *mut Self)
101    }
102
103    /// Creates a `&mut DateTimeStr` from the given mutable string slice.
104    ///
105    /// This performs assertion in debug build, but not in release build.
106    ///
107    /// # Safety
108    ///
109    /// `validate_bytes(s.as_bytes())` should return `Ok(())`.
110    #[inline]
111    #[must_use]
112    unsafe fn from_str_maybe_unchecked_mut(s: &mut str) -> &mut Self {
113        // This is safe because `DateTimeStr` ensures that the underlying
114        // bytes are ASCII string after modification.
115        Self::from_bytes_maybe_unchecked_mut(s.as_bytes_mut())
116    }
117
118    /// Creates a new `&DateTimeStr` from a string slice.
119    ///
120    /// # Examples
121    ///
122    /// ```
123    /// # use datetime_string::rfc3339::DateTimeStr;
124    /// let datetime = DateTimeStr::from_str("2001-06-17T12:34:56.7890-23:12")?;
125    /// assert_eq!(datetime.as_str(), "2001-06-17T12:34:56.7890-23:12");
126    ///
127    /// assert!(DateTimeStr::from_str("2000-02-29T12:34:56Z").is_ok());
128    /// assert!(DateTimeStr::from_str("9999-12-31T23:59:59+23:59").is_ok());
129    /// assert!(DateTimeStr::from_str("0000-01-01T00:00:00.0-00:00").is_ok());
130    /// # Ok::<_, datetime_string::Error>(())
131    /// ```
132    #[inline]
133    // `FromStr` trait cannot be implemented for a slice.
134    #[allow(clippy::should_implement_trait)]
135    pub fn from_str(s: &str) -> Result<&Self, Error> {
136        TryFrom::try_from(s)
137    }
138
139    /// Creates a new `&mut DateTimeStr` from a mutable string slice.
140    ///
141    /// # Examples
142    ///
143    /// ```
144    /// # use datetime_string::rfc3339::DateTimeStr;
145    /// let mut buf = "2001-06-17T12:34:56.7890-23:12".to_owned();
146    /// let datetime = DateTimeStr::from_mut_str(&mut buf)?;
147    /// assert_eq!(datetime.as_str(), "2001-06-17T12:34:56.7890-23:12");
148    ///
149    /// datetime.date_mut().set_year(1999);
150    /// assert_eq!(datetime.as_str(), "1999-06-17T12:34:56.7890-23:12");
151    /// # Ok::<_, datetime_string::Error>(())
152    /// ```
153    #[inline]
154    pub fn from_mut_str(s: &mut str) -> Result<&mut Self, Error> {
155        TryFrom::try_from(s)
156    }
157
158    /// Creates a new `&DateTimeStr` from a byte slice.
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// # use datetime_string::rfc3339::DateTimeStr;
164    /// let datetime = DateTimeStr::from_bytes(b"2001-06-17T12:34:56.7890-23:12")?;
165    /// assert_eq!(datetime.as_str(), "2001-06-17T12:34:56.7890-23:12");
166    ///
167    /// assert!(DateTimeStr::from_bytes(b"2001-06-17T12:34:56Z").is_ok());
168    /// assert!(DateTimeStr::from_bytes(b"9999-12-31T23:59:59+23:59").is_ok());
169    /// assert!(DateTimeStr::from_bytes(b"0000-01-01T00:00:00.0-00:00").is_ok());
170    /// # Ok::<_, datetime_string::Error>(())
171    /// ```
172    #[inline]
173    pub fn from_bytes(s: &[u8]) -> Result<&Self, Error> {
174        TryFrom::try_from(s)
175    }
176
177    /// Creates a new `&mut DateTimeStr` from a mutable byte slice.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// # use datetime_string::rfc3339::DateTimeStr;
183    /// let mut buf = b"2001-06-17T12:34:56.7890-23:12".to_owned();
184    /// let datetime = DateTimeStr::from_bytes_mut(&mut buf[..])?;
185    /// assert_eq!(datetime.as_str(), "2001-06-17T12:34:56.7890-23:12");
186    ///
187    /// datetime.date_mut().set_year(1999);
188    /// assert_eq!(datetime.as_str(), "1999-06-17T12:34:56.7890-23:12");
189    /// # Ok::<_, datetime_string::Error>(())
190    /// ```
191    #[inline]
192    pub fn from_bytes_mut(s: &mut [u8]) -> Result<&mut Self, Error> {
193        TryFrom::try_from(s)
194    }
195
196    /// Returns a string slice.
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// # use datetime_string::rfc3339::DateTimeStr;
202    /// let datetime = DateTimeStr::from_str("2001-06-17T12:34:56.7890-23:12")?;
203    ///
204    /// assert_eq!(datetime.as_str(), "2001-06-17T12:34:56.7890-23:12");
205    /// # Ok::<_, datetime_string::Error>(())
206    /// ```
207    #[inline]
208    #[must_use]
209    pub fn as_str(&self) -> &str {
210        unsafe {
211            // This is safe because the `SecfracDigitsStr` ensures that the
212            // underlying bytes are ASCII string.
213            debug_assert_safe_version_ok!(str::from_utf8(&self.0));
214            str::from_utf8_unchecked(&self.0)
215        }
216    }
217
218    /// Returns a byte slice.
219    ///
220    /// If you want to use indexed access, prefer [`as_bytes_fixed_len`].
221    ///
222    /// # Examples
223    ///
224    /// ```
225    /// # use datetime_string::rfc3339::DateTimeStr;
226    /// let datetime = DateTimeStr::from_str("2001-06-17T12:34:56.7890-23:12")?;
227    ///
228    /// assert_eq!(datetime.as_bytes(), b"2001-06-17T12:34:56.7890-23:12");
229    /// # Ok::<_, datetime_string::Error>(())
230    /// ```
231    ///
232    /// [`as_bytes_fixed_len`]: #method.as_bytes_fixed_len
233    #[inline]
234    #[must_use]
235    pub fn as_bytes(&self) -> &[u8] {
236        &self.0
237    }
238
239    /// Decomposes the string into `&FullDateStr` and `&FullTimeStr`.
240    ///
241    /// # Examples
242    ///
243    /// ```
244    /// # use datetime_string::rfc3339::DateTimeStr;
245    /// let datetime = DateTimeStr::from_str("2001-06-17T12:34:56.7890-23:12")?;
246    /// assert_eq!(datetime.as_str(), "2001-06-17T12:34:56.7890-23:12");
247    ///
248    /// let (date, time) = datetime.decompose();
249    /// assert_eq!(date.as_str(), "2001-06-17");
250    /// assert_eq!(time.as_str(), "12:34:56.7890-23:12");
251    /// # Ok::<_, datetime_string::Error>(())
252    /// ```
253    #[inline]
254    #[must_use]
255    pub fn decompose(&self) -> (&FullDateStr, &FullTimeStr) {
256        let date = unsafe {
257            // This is safe because `DATE_RANGE` fits inside the string, and a
258            // `date-time` string has a `full-date` followed by 'T' and `full-time`.
259            debug_assert_safe_version_ok!(<FullDateStr>::from_bytes(&self.0[DATE_RANGE]));
260            FullDateStr::from_bytes_maybe_unchecked(self.0.get_unchecked(DATE_RANGE))
261        };
262        let time = unsafe {
263            // This is safe because `TIME_RANGE` fits inside the string, and a
264            // `date-time` string has a `full-time` suffix following `full-date`
265            // and 'T'.
266            debug_assert_safe_version_ok!(<FullTimeStr>::from_bytes(&self.0[TIME_RANGE]));
267            FullTimeStr::from_bytes_maybe_unchecked(self.0.get_unchecked(TIME_RANGE))
268        };
269
270        (date, time)
271    }
272
273    /// Decomposes the string into `&mut FullDateStr` and `&mut FullTimeStr`.
274    ///
275    /// # Examples
276    ///
277    /// ```
278    /// # use datetime_string::rfc3339::DateTimeStr;
279    /// use datetime_string::common::TimeOffsetSign;
280    ///
281    /// let mut buf = "2001-06-17T12:34:56.7890-23:12".to_owned();
282    /// let datetime = DateTimeStr::from_mut_str(&mut buf)?;
283    /// assert_eq!(datetime.as_str(), "2001-06-17T12:34:56.7890-23:12");
284    ///
285    /// let (date, time) = datetime.decompose_mut();
286    /// assert_eq!(date.as_str(), "2001-06-17");
287    /// assert_eq!(time.as_str(), "12:34:56.7890-23:12");
288    ///
289    /// date.set_year(1999)?;
290    /// time.partial_time_mut().secfrac_mut().unwrap().digits_mut().fill_with_zero();
291    /// assert_eq!(datetime.as_str(), "1999-06-17T12:34:56.0000-23:12");
292    /// # Ok::<_, datetime_string::Error>(())
293    /// ```
294    #[inline]
295    #[must_use]
296    pub fn decompose_mut(&mut self) -> (&mut FullDateStr, &mut FullTimeStr) {
297        debug_assert_ok!(<FullDateStr>::from_bytes(&self.0[..T_POS]));
298        debug_assert_ok!(<FullTimeStr>::from_bytes(&self.0[(T_POS + 1)..]));
299
300        unsafe {
301            let (date, t_time) = self.0.split_at_mut(T_POS);
302            // Note that `t_time` contains the separator "T" as a prefix.
303            let time = t_time.get_unchecked_mut(1..);
304
305            // This is safe because a `date-time` string has a `full-date`
306            // followed by 'T' and `full-time`, and `FullDateStr` ensures that
307            // the underlying bytes are ASCII string after modification.
308            let date = FullDateStr::from_bytes_maybe_unchecked_mut(date);
309            // This is safe because a `date-time` string has a `full-time`
310            // suffix following `full-date` and 'T', and `FullTimeStr` ensures
311            // that the underlying bytes are ASCII string after modification.
312            let time = FullTimeStr::from_bytes_maybe_unchecked_mut(time);
313
314            (date, time)
315        }
316    }
317
318    /// Returns a `&FullDateStr`.
319    ///
320    /// # Examples
321    ///
322    /// ```
323    /// # use datetime_string::rfc3339::DateTimeStr;
324    /// let datetime = DateTimeStr::from_str("2001-06-17T12:34:56.7890-23:12")?;
325    /// assert_eq!(datetime.as_str(), "2001-06-17T12:34:56.7890-23:12");
326    ///
327    /// let date = datetime.date();
328    /// assert_eq!(date.as_str(), "2001-06-17");
329    /// # Ok::<_, datetime_string::Error>(())
330    /// ```
331    #[inline]
332    #[must_use]
333    pub fn date(&self) -> &FullDateStr {
334        unsafe {
335            debug_assert_safe_version_ok!(FullDateStr::from_bytes(&self.0[DATE_RANGE]));
336            // This is safe because the range is valid for the shortest possible string.
337            let s = self.0.get_unchecked(DATE_RANGE);
338            // This is safe because a `date-time` string has a `full-date` before "T".
339            FullDateStr::from_bytes_maybe_unchecked(s)
340        }
341    }
342
343    /// Returns a `&mut FullDateStr`.
344    ///
345    /// # Examples
346    ///
347    /// ```
348    /// # use datetime_string::rfc3339::DateTimeStr;
349    /// let mut buf = "2001-06-17T12:34:56.7890-23:12".to_owned();
350    /// let datetime = DateTimeStr::from_mut_str(&mut buf)?;
351    /// assert_eq!(datetime.as_str(), "2001-06-17T12:34:56.7890-23:12");
352    ///
353    /// let date = datetime.date_mut();
354    /// assert_eq!(date.as_str(), "2001-06-17");
355    ///
356    /// date.set_year(1999)?;
357    /// assert_eq!(datetime.as_str(), "1999-06-17T12:34:56.7890-23:12");
358    /// # Ok::<_, datetime_string::Error>(())
359    /// ```
360    #[inline]
361    #[must_use]
362    pub fn date_mut(&mut self) -> &mut FullDateStr {
363        unsafe {
364            debug_assert_ok!(FullDateStr::from_bytes(&self.0[DATE_RANGE]));
365            // This is safe because the range is valid for the shortest possible
366            // string, and `FullDateStr` ensures that the underlying bytes are
367            // ASCII string after modification.
368            let s = self.0.get_unchecked_mut(DATE_RANGE);
369            // This is safe because a `date-time` string has a `partial-time` before "T".
370            FullDateStr::from_bytes_maybe_unchecked_mut(s)
371        }
372    }
373
374    /// Returns a `&FullTimeStr`.
375    ///
376    /// # Examples
377    ///
378    /// ```
379    /// # use datetime_string::rfc3339::DateTimeStr;
380    /// let datetime = DateTimeStr::from_str("2001-06-17T12:34:56.7890-23:12")?;
381    /// assert_eq!(datetime.as_str(), "2001-06-17T12:34:56.7890-23:12");
382    ///
383    /// let time = datetime.time();
384    /// assert_eq!(time.as_str(), "12:34:56.7890-23:12");
385    /// # Ok::<_, datetime_string::Error>(())
386    /// ```
387    #[inline]
388    #[must_use]
389    pub fn time(&self) -> &FullTimeStr {
390        unsafe {
391            debug_assert_safe_version_ok!(FullTimeStr::from_bytes(&self.0[TIME_RANGE]));
392            // This is safe because the range is valid for the shortest possible string.
393            let s = self.0.get_unchecked(TIME_RANGE);
394            // This is safe because a `date-time` string has a `time-offset` right after "T".
395            FullTimeStr::from_bytes_maybe_unchecked(s)
396        }
397    }
398
399    /// Returns a `&mut FullTimeStr`.
400    ///
401    /// # Examples
402    ///
403    /// ```
404    /// # use datetime_string::rfc3339::DateTimeStr;
405    /// use datetime_string::common::TimeOffsetSign;
406    ///
407    /// let mut buf = "2001-06-17T12:34:56.7890-23:12".to_owned();
408    /// let datetime = DateTimeStr::from_mut_str(&mut buf)?;
409    /// assert_eq!(datetime.as_str(), "2001-06-17T12:34:56.7890-23:12");
410    ///
411    /// let time = datetime.time_mut();
412    /// assert_eq!(time.as_str(), "12:34:56.7890-23:12");
413    ///
414    /// time.partial_time_mut().secfrac_mut().unwrap().digits_mut().fill_with_zero();
415    /// assert_eq!(datetime.as_str(), "2001-06-17T12:34:56.0000-23:12");
416    /// # Ok::<_, datetime_string::Error>(())
417    /// ```
418    #[inline]
419    #[must_use]
420    pub fn time_mut(&mut self) -> &mut FullTimeStr {
421        unsafe {
422            debug_assert_ok!(FullTimeStr::from_bytes(&self.0[TIME_RANGE]));
423            // This is safe because the range is valid for the shortest possible
424            // string, and `FullTimeStr` ensures that the underlying bytes are
425            // ASCII string after modification.
426            let s = self.0.get_unchecked_mut(TIME_RANGE);
427            // This is safe because a `date-time` string has a `time-offset` right after "T".
428            FullTimeStr::from_bytes_maybe_unchecked_mut(s)
429        }
430    }
431
432    /// Converts the time to [`chrono::DateTime<FixedOffset>`][`chrono04::DateTime`] of chrono v0.4.
433    ///
434    /// Note that this truncates subnanosecond secfrac.
435    ///
436    /// Enabled by `chrono04` feature.
437    ///
438    /// # Examples
439    ///
440    /// ```
441    /// # use datetime_string::rfc3339::DateTimeStr;
442    /// use chrono04::{FixedOffset, TimeZone};
443    ///
444    /// let datetime = DateTimeStr::from_str("1999-12-31T12:34:56.01234567899999+09:00")?;
445    /// assert_eq!(
446    ///     datetime.to_chrono_date_time(),
447    ///     FixedOffset::east(9 * 60 * 60).ymd(1999, 12, 31).and_hms_nano(12, 34, 56, 12_345_678)
448    /// );
449    ///
450    /// let leap = DateTimeStr::from_str("2001-12-31T23:59:60.876543210999-00:00")?;
451    /// assert_eq!(
452    ///     leap.to_chrono_date_time(),
453    ///     FixedOffset::east(0).ymd(2001, 12, 31).and_hms_nano(23, 59, 59, 1_876_543_210)
454    /// );
455    /// # Ok::<_, datetime_string::Error>(())
456    /// ```
457    #[cfg(feature = "chrono04")]
458    #[cfg_attr(docsrs, doc(cfg(feature = "chrono04")))]
459    #[deprecated(since = "0.2.2", note = "renamed to `to_chrono04_date_time`")]
460    #[inline]
461    #[must_use]
462    pub fn to_chrono_date_time(&self) -> chrono04::DateTime<chrono04::FixedOffset> {
463        self.to_chrono04_date_time()
464    }
465
466    /// Converts the time to [`chrono::DateTime<FixedOffset>`][`chrono04::DateTime`] of chrono v0.4.
467    ///
468    /// Note that this truncates subnanosecond secfrac.
469    ///
470    /// Enabled by `chrono04` feature.
471    ///
472    /// # Examples
473    ///
474    /// ```
475    /// # use datetime_string::rfc3339::DateTimeStr;
476    /// use chrono04::{FixedOffset, TimeZone};
477    ///
478    /// let datetime = DateTimeStr::from_str("1999-12-31T12:34:56.01234567899999+09:00")?;
479    /// assert_eq!(
480    ///     datetime.to_chrono04_date_time(),
481    ///     FixedOffset::east(9 * 60 * 60).ymd(1999, 12, 31).and_hms_nano(12, 34, 56, 12_345_678)
482    /// );
483    ///
484    /// let leap = DateTimeStr::from_str("2001-12-31T23:59:60.876543210999-00:00")?;
485    /// assert_eq!(
486    ///     leap.to_chrono04_date_time(),
487    ///     FixedOffset::east(0).ymd(2001, 12, 31).and_hms_nano(23, 59, 59, 1_876_543_210)
488    /// );
489    /// # Ok::<_, datetime_string::Error>(())
490    /// ```
491    #[cfg(feature = "chrono04")]
492    #[cfg_attr(docsrs, doc(cfg(feature = "chrono04")))]
493    pub fn to_chrono04_date_time(&self) -> chrono04::DateTime<chrono04::FixedOffset> {
494        use chrono04::TimeZone;
495
496        let (date_s, time_s) = self.decompose();
497        let date: chrono04::NaiveDate = date_s.into();
498        let time: chrono04::NaiveTime = time_s.partial_time().to_chrono04_naive_time();
499        let offset: chrono04::FixedOffset = time_s.offset().into();
500
501        offset
502            .from_local_datetime(&date.and_time(time))
503            .single()
504            .expect("Should never fail: fixed time offset is not affected by summer time")
505    }
506
507    /// Converts the time to [`time::OffsetDateTime`][`time03::OffsetDateTime`].
508    ///
509    /// Note that this truncates subnanosecond secfrac.
510    ///
511    /// Leap seconds are ignored and the previous second is used, since `time`
512    /// v0.3 does not support leap seconds. Subseconds part is preserved even
513    /// in such cases.
514    ///
515    /// Enabled by `time03` feature.
516    ///
517    /// # Examples
518    ///
519    /// ```
520    /// # use datetime_string::rfc3339::DateTimeStr;
521    /// use time03::{Date, Month, UtcOffset};
522    ///
523    /// let datetime = DateTimeStr::from_str("1999-12-31T12:34:56.01234567899999+09:00")?;
524    /// assert_eq!(
525    ///     datetime.to_time03_date_time(),
526    ///     Date::from_calendar_date(1999, Month::December, 31)
527    ///         .expect("valid date")
528    ///         .with_hms_nano(12, 34, 56, 012_345_678)
529    ///         .expect("valid time")
530    ///         .assume_offset(UtcOffset::from_hms(9, 0, 0).expect("valid offset"))
531    /// );
532    ///
533    /// let leap = DateTimeStr::from_str("2001-12-31T23:59:60.876543210999-00:00")?;
534    /// assert_eq!(
535    ///     leap.to_time03_date_time(),
536    ///     Date::from_calendar_date(2001, Month::December, 31)
537    ///         .expect("valid date")
538    ///         .with_hms_nano(23, 59, 59, 876_543_210)
539    ///         .expect("valid time")
540    ///         .assume_utc()
541    /// );
542    /// # Ok::<_, datetime_string::Error>(())
543    /// ```
544    #[cfg(feature = "time03")]
545    #[cfg_attr(docsrs, doc(cfg(feature = "time03")))]
546    pub fn to_time03_date_time(&self) -> time03::OffsetDateTime {
547        let (date_s, time_s) = self.decompose();
548        let date: time03::Date = date_s.into();
549        let time: time03::Time = time_s.partial_time().to_time03_time();
550        let offset: time03::UtcOffset = time_s.offset().into();
551
552        date.with_time(time).assume_offset(offset)
553    }
554}
555
556impl AsRef<[u8]> for DateTimeStr {
557    #[inline]
558    fn as_ref(&self) -> &[u8] {
559        &self.0
560    }
561}
562
563impl AsRef<str> for DateTimeStr {
564    #[inline]
565    fn as_ref(&self) -> &str {
566        self.as_str()
567    }
568}
569
570impl AsRef<DateTimeStr> for DateTimeStr {
571    #[inline]
572    fn as_ref(&self) -> &DateTimeStr {
573        self
574    }
575}
576
577impl AsMut<DateTimeStr> for DateTimeStr {
578    #[inline]
579    fn as_mut(&mut self) -> &mut DateTimeStr {
580        self
581    }
582}
583
584impl<'a> From<&'a DateTimeStr> for &'a str {
585    #[inline]
586    fn from(v: &'a DateTimeStr) -> Self {
587        v.as_str()
588    }
589}
590
591impl<'a> TryFrom<&'a [u8]> for &'a DateTimeStr {
592    type Error = Error;
593
594    #[inline]
595    fn try_from(v: &'a [u8]) -> Result<Self, Self::Error> {
596        validate_bytes(v)?;
597        Ok(unsafe {
598            // This is safe because a valid `date-time` string is also an ASCII string.
599            DateTimeStr::from_bytes_maybe_unchecked(v)
600        })
601    }
602}
603
604impl<'a> TryFrom<&'a mut [u8]> for &'a mut DateTimeStr {
605    type Error = Error;
606
607    #[inline]
608    fn try_from(v: &'a mut [u8]) -> Result<Self, Self::Error> {
609        validate_bytes(v)?;
610        Ok(unsafe {
611            // This is safe because a valid `date-time` string is also an ASCII string.
612            DateTimeStr::from_bytes_maybe_unchecked_mut(v)
613        })
614    }
615}
616
617impl<'a> TryFrom<&'a str> for &'a DateTimeStr {
618    type Error = Error;
619
620    #[inline]
621    fn try_from(v: &'a str) -> Result<Self, Self::Error> {
622        Self::try_from(v.as_bytes())
623    }
624}
625
626impl<'a> TryFrom<&'a mut str> for &'a mut DateTimeStr {
627    type Error = Error;
628
629    #[inline]
630    fn try_from(v: &'a mut str) -> Result<Self, Self::Error> {
631        validate_bytes(v.as_bytes())?;
632        Ok(unsafe {
633            // This is safe because the string is already validated and
634            // `DateTimeStr` ensures that the underlying bytes are ASCII
635            // string after modification.
636            DateTimeStr::from_str_maybe_unchecked_mut(v)
637        })
638    }
639}
640
641impl fmt::Display for DateTimeStr {
642    #[inline]
643    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
644        self.as_str().fmt(f)
645    }
646}
647
648impl ops::Deref for DateTimeStr {
649    type Target = str;
650
651    #[inline]
652    fn deref(&self) -> &Self::Target {
653        self.as_str()
654    }
655}
656
657impl_cmp_symmetric!(str, DateTimeStr, &DateTimeStr);
658impl_cmp_symmetric!([u8], DateTimeStr, [u8]);
659impl_cmp_symmetric!([u8], DateTimeStr, &[u8]);
660impl_cmp_symmetric!([u8], &DateTimeStr, [u8]);
661impl_cmp_symmetric!(str, DateTimeStr, str);
662impl_cmp_symmetric!(str, DateTimeStr, &str);
663impl_cmp_symmetric!(str, &DateTimeStr, str);
664
665#[cfg(feature = "serde")]
666#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
667impl Serialize for DateTimeStr {
668    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
669    where
670        S: serde::Serializer,
671    {
672        serializer.serialize_str(self.as_str())
673    }
674}
675
676/// Items for serde support.
677#[cfg(feature = "serde")]
678mod serde_ {
679    use super::*;
680
681    use serde::de::{Deserialize, Deserializer, Visitor};
682
683    /// Visitor for `&DateTimeStr`.
684    struct StrVisitor;
685
686    impl<'de> Visitor<'de> for StrVisitor {
687        type Value = &'de DateTimeStr;
688
689        #[inline]
690        fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
691            f.write_str("RFC 3339 date-time string")
692        }
693
694        #[inline]
695        fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
696        where
697            E: serde::de::Error,
698        {
699            Self::Value::try_from(v).map_err(E::custom)
700        }
701
702        #[inline]
703        fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
704        where
705            E: serde::de::Error,
706        {
707            Self::Value::try_from(v).map_err(E::custom)
708        }
709    }
710
711    impl<'de> Deserialize<'de> for &'de DateTimeStr {
712        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
713        where
714            D: Deserializer<'de>,
715        {
716            deserializer.deserialize_any(StrVisitor)
717        }
718    }
719}
720
721#[cfg(test)]
722mod tests {
723    #[cfg(feature = "serde")]
724    use super::*;
725
726    use super::validate_bytes as s_validate;
727
728    #[cfg(feature = "serde")]
729    use serde_test::{assert_de_tokens, assert_tokens, Token};
730
731    #[test]
732    fn validate_bytes() {
733        assert!(s_validate(b"2001-06-17T12:34:56Z").is_ok());
734        assert!(s_validate(b"2001-06-17T12:34:56-00:00").is_ok());
735        assert!(s_validate(b"2001-06-17T12:34:56-12:30").is_ok());
736        assert!(s_validate(b"2001-06-17T12:34:56-23:59").is_ok());
737        assert!(s_validate(b"2001-06-17T12:34:56+00:00").is_ok());
738        assert!(s_validate(b"2001-06-17T12:34:56+12:30").is_ok());
739        assert!(s_validate(b"2001-06-17T12:34:56+23:59").is_ok());
740
741        assert!(s_validate(b"2001-06-17T00:00:00-00:00").is_ok());
742        assert!(s_validate(b"2001-06-17T23:59:59-00:00").is_ok());
743
744        assert!(s_validate(b"2001-06-17T12:34:56.7890Z").is_ok());
745        assert!(s_validate(b"2001-06-17T12:34:56.7890-00:00").is_ok());
746        assert!(s_validate(b"2001-06-17T12:34:56.7890+00:00").is_ok());
747    }
748
749    #[cfg(feature = "serde")]
750    #[test]
751    fn ser_de_str() {
752        let raw: &'static str = "2001-06-17T12:34:56.7890-23:12";
753        assert_tokens(
754            &DateTimeStr::from_str(raw).unwrap(),
755            &[Token::BorrowedStr(raw)],
756        );
757    }
758
759    #[cfg(feature = "serde")]
760    #[test]
761    fn de_bytes_slice() {
762        let raw: &'static [u8] = b"2001-06-17T12:34:56.7890-23:12";
763        assert_de_tokens(
764            &DateTimeStr::from_bytes(raw).unwrap(),
765            &[Token::BorrowedBytes(raw)],
766        );
767    }
768}