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}