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}