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}