datetime_string/common/hms6_colon.rs
1//! Time string in `%H:%M:%S` (`hh:mm:ss`) format.
2//!
3//! This is also an RFC 3339 [`partial-time`] string without `secfrac` part.
4//!
5//! [`partial-time`]: https://tools.ietf.org/html/rfc3339#section-5.6
6
7use core::{
8 convert::TryFrom,
9 fmt,
10 ops::{self, Range},
11 str,
12};
13
14use crate::{parse::parse_digits2, str::write_digit2};
15
16#[cfg(feature = "alloc")]
17use alloc::{string::String, vec::Vec};
18
19use crate::error::{ComponentKind, Error, ErrorKind};
20
21/// Length of `hh:mm:ss` string.
22const HMS_LEN: usize = 8;
23/// Range of the hour in the string.
24const HOUR_RANGE: Range<usize> = 0..2;
25/// Range of the minute in the string.
26const MINUTE_RANGE: Range<usize> = 3..5;
27/// Range of the second in the string.
28const SECOND_RANGE: Range<usize> = 6..8;
29/// Maximum value of the hour.
30const HOUR_MAX: u8 = 23;
31/// Maximum value of the minute.
32const MINUTE_MAX: u8 = 59;
33/// Maximum value of the second.
34///
35/// Note that a leap second is always allowed.
36const SECOND_MAX: u8 = 60;
37
38/// Validates the given `%H:%M:%S` string.
39///
40/// In other words, this string can contain an RFC 3339 [`partial-time`] string without `secfrac` part.
41///
42/// This type allows leap seconds unconditionally, because leap seconds are
43/// irregular and cannot predict, and date and timezone is also necessary to
44/// check if a leap second really happened or will happen.
45/// It is user's responsibility to validate a leap second really happened or
46/// will happen, if the "second" component is 60.
47///
48/// [`partial-time`]: https://tools.ietf.org/html/rfc3339#section-5.6
49fn validate_bytes(s: &[u8]) -> Result<(), Error> {
50 let s: &[u8; HMS_LEN] = TryFrom::try_from(s).map_err(|_| {
51 if s.len() < HMS_LEN {
52 ErrorKind::TooShort
53 } else {
54 ErrorKind::TooLong
55 }
56 })?;
57
58 if (s[2] != b':') || (s[5] != b':') {
59 return Err(ErrorKind::InvalidSeparator.into());
60 }
61
62 let hour_s: [u8; 2] = [s[0], s[1]];
63 let minute_s: [u8; 2] = [s[3], s[4]];
64 let second_s: [u8; 2] = [s[6], s[7]];
65
66 if !hour_s.iter().all(u8::is_ascii_digit) {
67 return Err(ErrorKind::InvalidComponentType(ComponentKind::Hour).into());
68 }
69 if !minute_s.iter().all(u8::is_ascii_digit) {
70 return Err(ErrorKind::InvalidComponentType(ComponentKind::Minute).into());
71 }
72 if !second_s.iter().all(u8::is_ascii_digit) {
73 return Err(ErrorKind::InvalidComponentType(ComponentKind::Second).into());
74 }
75
76 let hour = parse_digits2(hour_s);
77 if hour > HOUR_MAX {
78 return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Hour).into());
79 }
80 let minute = parse_digits2(minute_s);
81 if minute > MINUTE_MAX {
82 return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Minute).into());
83 }
84 let second = parse_digits2(second_s);
85 // Leap second is always allowed for this type. See the documentation for the types.
86 if second > SECOND_MAX {
87 return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Minute).into());
88 }
89
90 Ok(())
91}
92
93/// String slice for a time in `%H:%M:%S` (`hh:mm:ss`) format, such as `01:23:45`.
94///
95/// This is also an RFC 3339 [`partial-time`] string without `secfrac` part.
96///
97/// [`partial-time`]: https://tools.ietf.org/html/rfc3339#section-5.6
98#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
99#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
100#[repr(transparent)]
101// Note that `derive(Serialize)` cannot used here, because it encodes this as
102// `[u8]` rather than as a string.
103//
104// Comparisons implemented for the type are consistent (at least it is intended to be so).
105// See <https://github.com/rust-lang/rust-clippy/issues/2025>.
106// Note that `clippy::derive_ord_xor_partial_ord` would be introduced since Rust 1.47.0.
107#[allow(clippy::derive_hash_xor_eq)]
108#[allow(unknown_lints, clippy::derive_ord_xor_partial_ord)]
109pub struct Hms6ColonStr([u8]);
110
111impl Hms6ColonStr {
112 /// Creates a `&Hms6ColonStr` from the given byte slice.
113 ///
114 /// This performs assertion in debug build, but not in release build.
115 ///
116 /// # Safety
117 ///
118 /// `validate_bytes(s)` should return `Ok(())`.
119 #[inline]
120 #[must_use]
121 pub(crate) unsafe fn from_bytes_maybe_unchecked(v: &[u8]) -> &Self {
122 debug_assert_ok!(validate_bytes(v));
123 &*(v as *const [u8] as *const Self)
124 }
125
126 /// Creates a `&mut Hms6ColonStr` from the given mutable byte slice.
127 ///
128 /// This performs assertion in debug build, but not in release build.
129 ///
130 /// # Safety
131 ///
132 /// `validate_bytes(s)` should return `Ok(())`.
133 #[inline]
134 #[must_use]
135 pub(crate) unsafe fn from_bytes_maybe_unchecked_mut(s: &mut [u8]) -> &mut Self {
136 debug_assert_ok!(validate_bytes(s));
137 &mut *(s as *mut [u8] as *mut Self)
138 }
139
140 /// Creates a `&mut Hms6ColonStr` from the given mutable string slice.
141 ///
142 /// This performs assertion in debug build, but not in release build.
143 ///
144 /// # Safety
145 ///
146 /// `validate_bytes(s.as_bytes())` should return `Ok(())`.
147 #[inline]
148 #[must_use]
149 unsafe fn from_str_maybe_unchecked_mut(s: &mut str) -> &mut Self {
150 // This is safe because `Hms6ColonStr` ensures that the underlying bytes
151 // are ASCII string after modification.
152 Self::from_bytes_maybe_unchecked_mut(s.as_bytes_mut())
153 }
154
155 /// Creates a new `&Hms6ColonStr` from a string slice.
156 ///
157 /// # Examples
158 ///
159 /// ```
160 /// # use datetime_string::common::Hms6ColonStr;
161 /// let time = Hms6ColonStr::from_str("12:34:56")?;
162 /// assert_eq!(time.as_str(), "12:34:56");
163 ///
164 /// assert!(Hms6ColonStr::from_str("00:00:00").is_ok());
165 /// assert!(Hms6ColonStr::from_str("23:59:59").is_ok());
166 /// assert!(Hms6ColonStr::from_str("23:59:60").is_ok(), "Leap second is always allowed");
167 ///
168 /// assert!(Hms6ColonStr::from_str("24:00:00").is_err(), "Invalid hour");
169 /// assert!(Hms6ColonStr::from_str("00:60:00").is_err(), "Invalid minute");
170 /// assert!(Hms6ColonStr::from_str("00:00:61").is_err(), "Invalid second");
171 /// # Ok::<_, datetime_string::Error>(())
172 /// ```
173 #[inline]
174 // `FromStr` trait cannot be implemented for a slice.
175 #[allow(clippy::should_implement_trait)]
176 pub fn from_str(s: &str) -> Result<&Self, Error> {
177 TryFrom::try_from(s)
178 }
179
180 /// Creates a new `&mut Hms6ColonStr` from a mutable string slice.
181 ///
182 /// # Examples
183 ///
184 /// ```
185 /// # use datetime_string::common::Hms6ColonStr;
186 /// let mut buf = "12:34:56".to_owned();
187 /// let time = Hms6ColonStr::from_mut_str(&mut buf)?;
188 /// assert_eq!(time.as_str(), "12:34:56");
189 ///
190 /// time.set_hour(0)?;
191 /// assert_eq!(time.as_str(), "00:34:56");
192 ///
193 /// assert_eq!(buf, "00:34:56");
194 /// # Ok::<_, datetime_string::Error>(())
195 /// ```
196 #[inline]
197 pub fn from_mut_str(s: &mut str) -> Result<&mut Self, Error> {
198 TryFrom::try_from(s)
199 }
200
201 /// Creates a new `&Hms6ColonStr` from a byte slice.
202 ///
203 /// # Examples
204 ///
205 /// ```
206 /// # use datetime_string::common::Hms6ColonStr;
207 /// let time = Hms6ColonStr::from_bytes(b"12:34:56")?;
208 /// assert_eq!(time.as_str(), "12:34:56");
209 ///
210 /// assert!(Hms6ColonStr::from_bytes(b"00:00:00").is_ok());
211 /// assert!(Hms6ColonStr::from_bytes(b"23:59:59").is_ok());
212 /// assert!(Hms6ColonStr::from_bytes(b"23:59:60").is_ok(), "Leap second is always allowed");
213 ///
214 /// assert!(Hms6ColonStr::from_bytes(b"24:00:00").is_err(), "Invalid hour");
215 /// assert!(Hms6ColonStr::from_bytes(b"00:60:00").is_err(), "Invalid minute");
216 /// assert!(Hms6ColonStr::from_bytes(b"00:00:61").is_err(), "Invalid second");
217 /// # Ok::<_, datetime_string::Error>(())
218 /// ```
219 #[inline]
220 pub fn from_bytes(s: &[u8]) -> Result<&Self, Error> {
221 TryFrom::try_from(s)
222 }
223
224 /// Creates a new `&mut Hms6ColonStr` from a mutable byte slice.
225 ///
226 /// # Examples
227 ///
228 /// ```
229 /// # use datetime_string::common::Hms6ColonStr;
230 /// let mut buf: [u8; 8] = *b"12:34:56";
231 /// let time = Hms6ColonStr::from_bytes_mut(&mut buf[..])?;
232 /// assert_eq!(time.as_str(), "12:34:56");
233 ///
234 /// time.set_hour(0)?;
235 /// assert_eq!(time.as_str(), "00:34:56");
236 ///
237 /// assert_eq!(&buf[..], b"00:34:56");
238 /// # Ok::<_, datetime_string::Error>(())
239 /// ```
240 #[inline]
241 pub fn from_bytes_mut(s: &mut [u8]) -> Result<&mut Self, Error> {
242 TryFrom::try_from(s)
243 }
244
245 /// Assigns the given value.
246 ///
247 /// # Examples
248 ///
249 /// ```
250 /// # use datetime_string::common::Hms6ColonStr;
251 /// let mut buf: [u8; 8] = *b"12:34:56";
252 /// let time = Hms6ColonStr::from_bytes_mut(&mut buf[..])?;
253 /// assert_eq!(time.as_str(), "12:34:56");
254 ///
255 /// let newtime = Hms6ColonStr::from_str("01:01:01")?;
256 ///
257 /// time.assign(newtime);
258 /// assert_eq!(time.as_str(), "01:01:01");
259 /// assert_eq!(buf, *b"01:01:01");
260 /// # Ok::<_, datetime_string::Error>(())
261 /// ```
262 #[inline]
263 pub fn assign(&mut self, v: &Self) {
264 debug_assert_eq!(self.0.len(), v.0.len());
265 self.0.copy_from_slice(&v.0);
266 }
267
268 /// Returns a string slice.
269 ///
270 /// # Examples
271 ///
272 /// ```
273 /// # use datetime_string::common::Hms6ColonStr;
274 /// let time = Hms6ColonStr::from_str("12:34:56")?;
275 ///
276 /// assert_eq!(time.as_str(), "12:34:56");
277 /// # Ok::<_, datetime_string::Error>(())
278 /// ```
279 #[inline]
280 #[must_use]
281 pub fn as_str(&self) -> &str {
282 unsafe {
283 // This is safe because the `Hms6ColonStr` ensures that the
284 // underlying bytes are ASCII string.
285 debug_assert_safe_version_ok!(str::from_utf8(&self.0));
286 str::from_utf8_unchecked(&self.0)
287 }
288 }
289
290 /// Returns a byte slice.
291 ///
292 /// If you want to use indexed access, prefer [`as_bytes_fixed_len`].
293 ///
294 /// # Examples
295 ///
296 /// ```
297 /// # use datetime_string::common::Hms6ColonStr;
298 /// let time = Hms6ColonStr::from_bytes(b"12:34:56")?;
299 ///
300 /// assert_eq!(time.as_bytes(), b"12:34:56");
301 /// # Ok::<_, datetime_string::Error>(())
302 /// ```
303 ///
304 /// [`as_bytes_fixed_len`]: #method.as_bytes_fixed_len
305 #[inline]
306 #[must_use]
307 pub fn as_bytes(&self) -> &[u8] {
308 &self.0
309 }
310
311 /// Returns a fixed length byte slice.
312 ///
313 /// # Examples
314 ///
315 /// ```
316 /// # use datetime_string::common::Hms6ColonStr;
317 /// let time = Hms6ColonStr::from_str("12:34:56")?;
318 ///
319 /// let fixed_len: &[u8; 8] = time.as_bytes_fixed_len();
320 /// assert_eq!(fixed_len, b"12:34:56");
321 /// # Ok::<_, datetime_string::Error>(())
322 /// ```
323 #[inline]
324 #[must_use]
325 pub fn as_bytes_fixed_len(&self) -> &[u8; 8] {
326 debug_assert_eq!(self.len(), HMS_LEN, "Hms6ColonStr must always be 8 bytes");
327
328 debug_assert_safe_version_ok!(<&[u8; 8]>::try_from(&self.0[..HMS_LEN]));
329 let ptr = self.0.as_ptr() as *const [u8; HMS_LEN];
330 // This must be always safe because the length is already checked.
331 unsafe { &*ptr }
332 }
333
334 /// Returns the hour as a string slice.
335 ///
336 /// # Examples
337 ///
338 /// ```
339 /// # use datetime_string::common::Hms6ColonStr;
340 /// let time = Hms6ColonStr::from_str("12:34:56")?;
341 ///
342 /// assert_eq!(time.hour_str(), "12");
343 /// # Ok::<_, datetime_string::Error>(())
344 /// ```
345 #[inline]
346 #[must_use]
347 pub fn hour_str(&self) -> &str {
348 unsafe {
349 // This is safe because the string is ASCII string and `HOUR_RANGE`
350 // is always inside the string.
351 debug_assert_safe_version_ok!(str::from_utf8(&self.0[HOUR_RANGE]));
352 str::from_utf8_unchecked(self.0.get_unchecked(HOUR_RANGE))
353 }
354 }
355
356 /// Returns the hour as a fixed length byte slice.
357 ///
358 /// # Examples
359 ///
360 /// ```
361 /// # use datetime_string::common::Hms6ColonStr;
362 /// let time = Hms6ColonStr::from_str("12:34:56")?;
363 ///
364 /// let hour_fixed_len: &[u8; 2] = time.hour_bytes_fixed_len();
365 /// assert_eq!(hour_fixed_len, b"12");
366 /// # Ok::<_, datetime_string::Error>(())
367 /// ```
368 #[inline]
369 #[must_use]
370 pub fn hour_bytes_fixed_len(&self) -> &[u8; 2] {
371 unsafe {
372 // This is safe because `HOUR_RANGE` fits inside the string.
373 debug_assert_safe_version_ok!(<&[u8; 2]>::try_from(&self.0[HOUR_RANGE]));
374 let ptr = self.0.as_ptr().add(HOUR_RANGE.start) as *const [u8; 2];
375 &*ptr
376 }
377 }
378
379 /// Returns the hour as a fixed length mutable byte slice.
380 ///
381 /// # Safety
382 ///
383 /// The returned slice should have only ASCII digits.
384 /// If non-ASCII digits are stored, it may lead to undefined behavior.
385 #[inline]
386 #[must_use]
387 unsafe fn hour_bytes_mut_fixed_len(&mut self) -> &mut [u8; 2] {
388 // This is safe because `HOUR_RANGE` fits inside the string.
389 debug_assert_safe_version_ok!(<&mut [u8; 2]>::try_from(&mut self.0[HOUR_RANGE]));
390 let ptr = self.0.as_mut_ptr().add(HOUR_RANGE.start) as *mut [u8; 2];
391 &mut *ptr
392 }
393
394 /// Returns the hour as an integer.
395 ///
396 /// # Examples
397 ///
398 /// ```
399 /// # use datetime_string::common::Hms6ColonStr;
400 /// let time = Hms6ColonStr::from_str("12:34:56")?;
401 ///
402 /// assert_eq!(time.hour(), 12);
403 /// # Ok::<_, datetime_string::Error>(())
404 /// ```
405 #[inline]
406 #[must_use]
407 pub fn hour(&self) -> u8 {
408 parse_digits2(*self.hour_bytes_fixed_len())
409 }
410
411 /// Returns the minute as a string slice.
412 ///
413 /// # Examples
414 ///
415 /// ```
416 /// # use datetime_string::common::Hms6ColonStr;
417 /// let time = Hms6ColonStr::from_str("12:34:56")?;
418 ///
419 /// assert_eq!(time.minute_str(), "34");
420 /// # Ok::<_, datetime_string::Error>(())
421 /// ```
422 #[inline]
423 #[must_use]
424 pub fn minute_str(&self) -> &str {
425 unsafe {
426 // This is safe because the string is ASCII string and `MINUTE_RANGE`
427 // is always inside the string.
428 debug_assert_safe_version_ok!(str::from_utf8(&self.0[MINUTE_RANGE]));
429 str::from_utf8_unchecked(self.0.get_unchecked(MINUTE_RANGE))
430 }
431 }
432
433 /// Returns the minute as a fixed length byte slice.
434 ///
435 /// # Examples
436 ///
437 /// ```
438 /// # use datetime_string::common::Hms6ColonStr;
439 /// let time = Hms6ColonStr::from_str("12:34:56")?;
440 ///
441 /// let minute_fixed_len: &[u8; 2] = time.minute_bytes_fixed_len();
442 /// assert_eq!(minute_fixed_len, b"34");
443 /// # Ok::<_, datetime_string::Error>(())
444 /// ```
445 #[inline]
446 #[must_use]
447 pub fn minute_bytes_fixed_len(&self) -> &[u8; 2] {
448 unsafe {
449 // This is safe because `MINUTE_RANGE` fits inside the string.
450 debug_assert_safe_version_ok!(<&[u8; 2]>::try_from(&self.0[MINUTE_RANGE]));
451 let ptr = self.0.as_ptr().add(MINUTE_RANGE.start) as *const [u8; 2];
452 &*ptr
453 }
454 }
455
456 /// Returns the minute as a fixed length mutable byte slice.
457 ///
458 /// # Safety
459 ///
460 /// The returned slice should have only ASCII digits.
461 /// If non-ASCII digits are stored, it may lead to undefined behavior.
462 #[inline]
463 #[must_use]
464 unsafe fn minute_bytes_mut_fixed_len(&mut self) -> &mut [u8; 2] {
465 // This is safe because `MINUTE_RANGE` fits inside the string.
466 debug_assert_safe_version_ok!(<&mut [u8; 2]>::try_from(&mut self.0[MINUTE_RANGE]));
467 let ptr = self.0.as_mut_ptr().add(MINUTE_RANGE.start) as *mut [u8; 2];
468 &mut *ptr
469 }
470
471 /// Returns the minute as an integer.
472 ///
473 /// # Examples
474 ///
475 /// ```
476 /// # use datetime_string::common::Hms6ColonStr;
477 /// let time = Hms6ColonStr::from_str("12:34:56")?;
478 ///
479 /// assert_eq!(time.minute(), 34);
480 /// # Ok::<_, datetime_string::Error>(())
481 /// ```
482 #[inline]
483 #[must_use]
484 pub fn minute(&self) -> u8 {
485 parse_digits2(*self.minute_bytes_fixed_len())
486 }
487
488 /// Returns the second as a string slice.
489 ///
490 /// # Examples
491 ///
492 /// ```
493 /// # use datetime_string::common::Hms6ColonStr;
494 /// let time = Hms6ColonStr::from_str("12:34:56")?;
495 ///
496 /// assert_eq!(time.second_str(), "56");
497 /// # Ok::<_, datetime_string::Error>(())
498 /// ```
499 #[inline]
500 #[must_use]
501 pub fn second_str(&self) -> &str {
502 unsafe {
503 // This is safe because the string is ASCII string and `SECOND_RANGE`
504 // is always inside the string.
505 debug_assert_safe_version_ok!(str::from_utf8(&self.0[SECOND_RANGE]));
506 str::from_utf8_unchecked(self.0.get_unchecked(SECOND_RANGE))
507 }
508 }
509
510 /// Returns the second as a fixed length byte slice.
511 ///
512 /// # Examples
513 ///
514 /// ```
515 /// # use datetime_string::common::Hms6ColonStr;
516 /// let time = Hms6ColonStr::from_str("12:34:56")?;
517 ///
518 /// let second_fixed_len: &[u8; 2] = time.second_bytes_fixed_len();
519 /// assert_eq!(second_fixed_len, b"56");
520 /// # Ok::<_, datetime_string::Error>(())
521 /// ```
522 #[inline]
523 #[must_use]
524 pub fn second_bytes_fixed_len(&self) -> &[u8; 2] {
525 unsafe {
526 // This is safe because `SECOND_RANGE` fits inside the string.
527 debug_assert_safe_version_ok!(<&[u8; 2]>::try_from(&self.0[SECOND_RANGE]));
528 let ptr = self.0.as_ptr().add(SECOND_RANGE.start) as *const [u8; 2];
529 &*ptr
530 }
531 }
532
533 /// Returns the second as a fixed length mutable byte slice.
534 ///
535 /// # Safety
536 ///
537 /// The returned slice should have only ASCII digits.
538 /// If non-ASCII digits are stored, it may lead to undefined behavior.
539 #[inline]
540 #[must_use]
541 unsafe fn second_bytes_mut_fixed_len(&mut self) -> &mut [u8; 2] {
542 // This is safe because `SECOND_RANGE` fits inside the string.
543 debug_assert_safe_version_ok!(<&mut [u8; 2]>::try_from(&mut self.0[SECOND_RANGE]));
544 let ptr = self.0.as_mut_ptr().add(SECOND_RANGE.start) as *mut [u8; 2];
545 &mut *ptr
546 }
547
548 /// Returns the second as an integer.
549 ///
550 /// # Examples
551 ///
552 /// ```
553 /// # use datetime_string::common::Hms6ColonStr;
554 /// let time = Hms6ColonStr::from_str("12:34:56")?;
555 ///
556 /// assert_eq!(time.second(), 56);
557 /// # Ok::<_, datetime_string::Error>(())
558 /// ```
559 #[inline]
560 #[must_use]
561 pub fn second(&self) -> u8 {
562 parse_digits2(*self.second_bytes_fixed_len())
563 }
564
565 /// Sets the given hour value to the string.
566 ///
567 /// # Failures
568 ///
569 /// * Fails if the time after modification is invalid.
570 ///
571 /// # Examples
572 ///
573 /// ```
574 /// # use datetime_string::common::Hms6ColonStr;
575 /// let mut buf: [u8; 8] = *b"12:34:56";
576 /// let time = Hms6ColonStr::from_bytes_mut(&mut buf[..])?;
577 /// assert_eq!(time.as_str(), "12:34:56");
578 ///
579 /// time.set_hour(0)?;
580 /// assert_eq!(time.as_str(), "00:34:56");
581 ///
582 /// assert!(time.set_hour(24).is_err(), "24:34:56 is invalid");
583 /// # Ok::<_, datetime_string::Error>(())
584 /// ```
585 pub fn set_hour(&mut self, hour: u8) -> Result<(), Error> {
586 if hour > HOUR_MAX {
587 return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Hour).into());
588 }
589 unsafe {
590 // This is safe because `write_digit2()` fills the slice with ASCII digits.
591 write_digit2(self.hour_bytes_mut_fixed_len(), hour);
592 }
593 debug_assert_ok!(validate_bytes(&self.0));
594
595 Ok(())
596 }
597
598 /// Sets the given minute value to the string.
599 ///
600 /// # Failures
601 ///
602 /// * Fails if the time after modification is invalid.
603 ///
604 /// # Examples
605 ///
606 /// ```
607 /// # use datetime_string::common::Hms6ColonStr;
608 /// let mut buf: [u8; 8] = *b"12:34:56";
609 /// let time = Hms6ColonStr::from_bytes_mut(&mut buf[..])?;
610 /// assert_eq!(time.as_str(), "12:34:56");
611 ///
612 /// time.set_minute(0)?;
613 /// assert_eq!(time.as_str(), "12:00:56");
614 ///
615 /// assert!(time.set_minute(60).is_err(), "24:60:56 is invalid");
616 /// # Ok::<_, datetime_string::Error>(())
617 /// ```
618 pub fn set_minute(&mut self, minute: u8) -> Result<(), Error> {
619 if minute > MINUTE_MAX {
620 return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Minute).into());
621 }
622 unsafe {
623 // This is safe because `write_digit2()` fills the slice with ASCII digits.
624 write_digit2(self.minute_bytes_mut_fixed_len(), minute);
625 }
626 debug_assert_ok!(validate_bytes(&self.0));
627
628 Ok(())
629 }
630
631 /// Sets the given second value to the string.
632 ///
633 /// # Failures
634 ///
635 /// * Fails if the time after modification is invalid.
636 ///
637 /// # Examples
638 ///
639 /// ```
640 /// # use datetime_string::common::Hms6ColonStr;
641 /// let mut buf: [u8; 8] = *b"12:34:56";
642 /// let time = Hms6ColonStr::from_bytes_mut(&mut buf[..])?;
643 /// assert_eq!(time.as_str(), "12:34:56");
644 ///
645 /// time.set_second(0)?;
646 /// assert_eq!(time.as_str(), "12:34:00");
647 ///
648 /// assert!(time.set_second(61).is_err(), "24:34:61 is invalid");
649 /// # Ok::<_, datetime_string::Error>(())
650 /// ```
651 pub fn set_second(&mut self, second: u8) -> Result<(), Error> {
652 if second > SECOND_MAX {
653 return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Second).into());
654 }
655 unsafe {
656 // This is safe because `write_digit2()` fills the slice with ASCII digits.
657 write_digit2(self.second_bytes_mut_fixed_len(), second);
658 }
659 debug_assert_ok!(validate_bytes(&self.0));
660
661 Ok(())
662 }
663
664 /// Sets the given hour and minute values to the string.
665 ///
666 /// # Failures
667 ///
668 /// * Fails if the time after modification is invalid.
669 ///
670 /// # Examples
671 ///
672 /// ```
673 /// # use datetime_string::common::Hms6ColonStr;
674 /// let mut buf: [u8; 8] = *b"12:34:56";
675 /// let time = Hms6ColonStr::from_bytes_mut(&mut buf[..])?;
676 /// assert_eq!(time.as_str(), "12:34:56");
677 ///
678 /// time.set_hour_minute(21, 10)?;
679 /// assert_eq!(time.as_str(), "21:10:56");
680 ///
681 /// assert!(time.set_hour_minute(23, 60).is_err(), "23:60:56 is invalid");
682 /// # Ok::<_, datetime_string::Error>(())
683 /// ```
684 pub fn set_hour_minute(&mut self, hour: u8, minute: u8) -> Result<(), Error> {
685 if hour > HOUR_MAX {
686 return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Hour).into());
687 }
688 if minute > MINUTE_MAX {
689 return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Minute).into());
690 }
691 unsafe {
692 // These are safe because `write_digit2()` fills the slice with ASCII digits.
693 write_digit2(self.hour_bytes_mut_fixed_len(), hour);
694 write_digit2(self.minute_bytes_mut_fixed_len(), minute);
695 }
696 debug_assert_ok!(validate_bytes(&self.0));
697
698 Ok(())
699 }
700
701 /// Sets the given minute and second values to the string.
702 ///
703 /// # Failures
704 ///
705 /// * Fails if the time after modification is invalid.
706 ///
707 /// # Examples
708 ///
709 /// ```
710 /// # use datetime_string::common::Hms6ColonStr;
711 /// let mut buf: [u8; 8] = *b"12:34:56";
712 /// let time = Hms6ColonStr::from_bytes_mut(&mut buf[..])?;
713 /// assert_eq!(time.as_str(), "12:34:56");
714 ///
715 /// time.set_minute_second(54, 32)?;
716 /// assert_eq!(time.as_str(), "12:54:32");
717 ///
718 /// assert!(time.set_minute_second(60, 59).is_err(), "12:60:59 is invalid");
719 /// # Ok::<_, datetime_string::Error>(())
720 /// ```
721 pub fn set_minute_second(&mut self, minute: u8, second: u8) -> Result<(), Error> {
722 if minute > MINUTE_MAX {
723 return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Minute).into());
724 }
725 if second > SECOND_MAX {
726 return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Second).into());
727 }
728 unsafe {
729 // These are safe because `write_digit2()` fills the slice with ASCII digits.
730 write_digit2(self.minute_bytes_mut_fixed_len(), minute);
731 write_digit2(self.second_bytes_mut_fixed_len(), second);
732 }
733 debug_assert_ok!(validate_bytes(&self.0));
734
735 Ok(())
736 }
737
738 /// Sets the given hour, minute, and second values to the string.
739 ///
740 /// # Failures
741 ///
742 /// * Fails if the time after modification is invalid.
743 ///
744 /// # Examples
745 ///
746 /// ```
747 /// # use datetime_string::common::Hms6ColonStr;
748 /// let mut buf: [u8; 8] = *b"12:34:56";
749 /// let time = Hms6ColonStr::from_bytes_mut(&mut buf[..])?;
750 /// assert_eq!(time.as_str(), "12:34:56");
751 ///
752 /// time.set_time(23, 12, 1)?;
753 /// assert_eq!(time.as_str(), "23:12:01");
754 ///
755 /// assert!(time.set_time(24, 0, 0).is_err(), "24:00:00 is invalid");
756 /// # Ok::<_, datetime_string::Error>(())
757 /// ```
758 pub fn set_time(&mut self, hour: u8, minute: u8, second: u8) -> Result<(), Error> {
759 if hour > HOUR_MAX {
760 return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Hour).into());
761 }
762 if minute > MINUTE_MAX {
763 return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Minute).into());
764 }
765 if second > SECOND_MAX {
766 return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Second).into());
767 }
768 unsafe {
769 // These are safe because `write_digit2()` fills the slice with ASCII digits.
770 write_digit2(self.hour_bytes_mut_fixed_len(), hour);
771 write_digit2(self.minute_bytes_mut_fixed_len(), minute);
772 write_digit2(self.second_bytes_mut_fixed_len(), second);
773 }
774 debug_assert_ok!(validate_bytes(&self.0));
775
776 Ok(())
777 }
778
779 /// Retruns the seconds from the start of the day.
780 ///
781 /// # Example
782 ///
783 /// ```
784 /// # use datetime_string::common::Hms6ColonStr;
785 /// let time = Hms6ColonStr::from_str("12:34:56")?;
786 /// assert_eq!(time.to_seconds(), 12 * 60 * 60 + 34 * 60 + 56);
787 ///
788 /// let zero = Hms6ColonStr::from_str("00:00:00")?;
789 /// assert_eq!(zero.to_seconds(), 0);
790 ///
791 /// let last = Hms6ColonStr::from_str("23:59:59")?;
792 /// assert_eq!(last.to_seconds(), 24 * 60 * 60 - 1);
793 ///
794 /// let last_leap = Hms6ColonStr::from_str("23:59:60")?;
795 /// assert_eq!(last_leap.to_seconds(), 24 * 60 * 60);
796 /// # Ok::<_, datetime_string::Error>(())
797 /// ```
798 #[inline]
799 pub fn to_seconds(&self) -> u32 {
800 u32::from(self.hour()) * 3600 + u32::from(self.minute()) * 60 + u32::from(self.second())
801 }
802
803 /// Returns `true` if the time points to the leap second.
804 ///
805 /// # Example
806 ///
807 /// ```
808 /// # use datetime_string::common::Hms6ColonStr;
809 /// let normal = Hms6ColonStr::from_str("12:34:59")?;
810 /// assert!(!normal.is_leap_second());
811 ///
812 /// let leap = Hms6ColonStr::from_str("12:34:60")?;
813 /// assert!(leap.is_leap_second());
814 /// # Ok::<_, datetime_string::Error>(())
815 /// ```
816 #[inline]
817 #[must_use]
818 pub fn is_leap_second(&self) -> bool {
819 self.second_bytes_fixed_len() == b"60"
820 }
821}
822
823#[cfg(feature = "alloc")]
824#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
825impl alloc::borrow::ToOwned for Hms6ColonStr {
826 type Owned = Hms6ColonString;
827
828 #[inline]
829 fn to_owned(&self) -> Self::Owned {
830 self.into()
831 }
832}
833
834impl AsRef<[u8]> for Hms6ColonStr {
835 #[inline]
836 fn as_ref(&self) -> &[u8] {
837 self.as_bytes()
838 }
839}
840
841impl AsRef<str> for Hms6ColonStr {
842 #[inline]
843 fn as_ref(&self) -> &str {
844 self.as_str()
845 }
846}
847
848impl AsRef<Hms6ColonStr> for Hms6ColonStr {
849 #[inline]
850 fn as_ref(&self) -> &Hms6ColonStr {
851 self
852 }
853}
854
855impl AsMut<Hms6ColonStr> for Hms6ColonStr {
856 #[inline]
857 fn as_mut(&mut self) -> &mut Hms6ColonStr {
858 self
859 }
860}
861
862impl<'a> From<&'a Hms6ColonStr> for &'a str {
863 #[inline]
864 fn from(v: &'a Hms6ColonStr) -> Self {
865 v.as_str()
866 }
867}
868
869#[cfg(feature = "chrono04")]
870#[cfg_attr(docsrs, doc(cfg(feature = "chrono04")))]
871impl From<&Hms6ColonStr> for chrono04::NaiveTime {
872 fn from(v: &Hms6ColonStr) -> Self {
873 let hour = u32::from(v.hour());
874 let minute = u32::from(v.minute());
875 let second = u32::from(v.second());
876
877 // Note that `chrono04::NaiveTime::from_hms()` does not allow leap second.
878 // See <https://docs.rs/chrono/0.4.19/chrono/naive/struct.NaiveTime.html#representing-leap-seconds>.
879 if second == 60 {
880 Self::from_hms_milli(hour, minute, 59, 1000)
881 } else {
882 Self::from_hms(hour, minute, second)
883 }
884 }
885}
886
887#[cfg(feature = "time03")]
888#[cfg_attr(docsrs, doc(cfg(feature = "time03")))]
889impl From<&Hms6ColonStr> for time03::Time {
890 fn from(v: &Hms6ColonStr) -> Self {
891 let hour = v.hour();
892 let minute = v.minute();
893 // Note that `time` v0.3 does not allow leap seconds.
894 let second = v.second().min(59);
895
896 Self::from_hms(hour, minute, second)
897 .expect("[validity] the time must be valid and leap second is already handled")
898 }
899}
900
901impl<'a> TryFrom<&'a [u8]> for &'a Hms6ColonStr {
902 type Error = Error;
903
904 #[inline]
905 fn try_from(v: &'a [u8]) -> Result<Self, Self::Error> {
906 validate_bytes(v)?;
907 Ok(unsafe {
908 // This is safe because the value is successfully validated.
909 Hms6ColonStr::from_bytes_maybe_unchecked(v)
910 })
911 }
912}
913
914impl<'a> TryFrom<&'a mut [u8]> for &'a mut Hms6ColonStr {
915 type Error = Error;
916
917 #[inline]
918 fn try_from(v: &'a mut [u8]) -> Result<Self, Self::Error> {
919 validate_bytes(v)?;
920 Ok(unsafe {
921 // This is safe because the value is successfully validated.
922 Hms6ColonStr::from_bytes_maybe_unchecked_mut(v)
923 })
924 }
925}
926
927impl<'a> TryFrom<&'a str> for &'a Hms6ColonStr {
928 type Error = Error;
929
930 #[inline]
931 fn try_from(v: &'a str) -> Result<Self, Self::Error> {
932 TryFrom::try_from(v.as_bytes())
933 }
934}
935
936impl<'a> TryFrom<&'a mut str> for &'a mut Hms6ColonStr {
937 type Error = Error;
938
939 #[inline]
940 fn try_from(v: &'a mut str) -> Result<Self, Self::Error> {
941 validate_bytes(v.as_bytes())?;
942 Ok(unsafe {
943 // This is safe because the value is successfully validated, and
944 // `Hms6ColonStr` ensures the value after modification is an ASCII string.
945 Hms6ColonStr::from_str_maybe_unchecked_mut(v)
946 })
947 }
948}
949
950impl fmt::Display for Hms6ColonStr {
951 #[inline]
952 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
953 self.as_str().fmt(f)
954 }
955}
956
957impl ops::Deref for Hms6ColonStr {
958 type Target = str;
959
960 #[inline]
961 fn deref(&self) -> &Self::Target {
962 self.as_str()
963 }
964}
965
966impl_cmp_symmetric!(str, Hms6ColonStr, &Hms6ColonStr);
967impl_cmp_symmetric!([u8], Hms6ColonStr, [u8]);
968impl_cmp_symmetric!([u8], Hms6ColonStr, &[u8]);
969impl_cmp_symmetric!([u8], &Hms6ColonStr, [u8]);
970impl_cmp_symmetric!(str, Hms6ColonStr, str);
971impl_cmp_symmetric!(str, Hms6ColonStr, &str);
972impl_cmp_symmetric!(str, &Hms6ColonStr, str);
973
974#[cfg(feature = "serde")]
975#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
976impl serde::Serialize for Hms6ColonStr {
977 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
978 where
979 S: serde::Serializer,
980 {
981 serializer.serialize_str(self.as_str())
982 }
983}
984
985/// Owned string for a time in `%H:%M:%S` (`hh:mm:ss`) format, such as `01:23:45`.
986///
987/// This is also an RFC 3339 [`partial-time`] string without `secfrac` part.
988///
989/// This is a fixed length string, and implements [`Copy`] trait.
990///
991/// To create a value of this type, use [`str::parse`] method or
992/// [`std::convert::TryFrom`] trait, or convert from `&Hms6ColonStr`.
993///
994/// # Examples
995///
996/// ```
997/// # use datetime_string::common::Hms6ColonString;
998/// use datetime_string::common::Hms6ColonStr;
999/// use std::convert::TryFrom;
1000///
1001/// let try_from = Hms6ColonString::try_from("12:34:56")?;
1002///
1003/// let parse = "12:34:56".parse::<Hms6ColonString>()?;
1004/// let parse2: Hms6ColonString = "12:34:56".parse()?;
1005///
1006/// let to_owned = Hms6ColonStr::from_str("12:34:56")?.to_owned();
1007/// let into: Hms6ColonString = Hms6ColonStr::from_str("12:34:56")?.into();
1008/// # Ok::<_, datetime_string::Error>(())
1009/// ```
1010///
1011/// [`partial-time`]: https://tools.ietf.org/html/rfc3339#section-5.6
1012#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1013#[repr(transparent)]
1014// Note that `derive(Serialize)` cannot used here, because it encodes this as
1015// `[u8; 8]` rather than as a string.
1016//
1017// Comparisons implemented for the type are consistent (at least it is intended to be so).
1018// See <https://github.com/rust-lang/rust-clippy/issues/2025>.
1019// Note that `clippy::derive_ord_xor_partial_ord` would be introduced since Rust 1.47.0.
1020#[allow(clippy::derive_hash_xor_eq)]
1021#[allow(unknown_lints, clippy::derive_ord_xor_partial_ord)]
1022pub struct Hms6ColonString([u8; HMS_LEN]);
1023
1024impl Hms6ColonString {
1025 /// Creates a `Hms6ColonString` from the given bytes.
1026 ///
1027 /// # Safety
1028 ///
1029 /// `validate_bytes(&s)` should return `Ok(())`.
1030 #[inline]
1031 #[must_use]
1032 unsafe fn new_maybe_unchecked(s: [u8; 8]) -> Self {
1033 debug_assert_ok!(validate_bytes(&s));
1034 Self(s)
1035 }
1036
1037 /// Returns `00:00:00`.
1038 #[inline]
1039 #[must_use]
1040 fn zero() -> Self {
1041 unsafe {
1042 // This is safe because `00:00:00` is valid.
1043 debug_assert_safe_version_ok!(Self::try_from(*b"00:00:00"));
1044 Self::new_maybe_unchecked(*b"00:00:00")
1045 }
1046 }
1047
1048 /// Creates a new `Hms6ColonString` from the given time.
1049 ///
1050 /// # Examples
1051 ///
1052 /// ```
1053 /// # use datetime_string::common::Hms6ColonString;
1054 /// let time = Hms6ColonString::from_hms(12, 34, 56)?;
1055 /// assert_eq!(time.as_str(), "12:34:56");
1056 ///
1057 /// assert!(Hms6ColonString::from_hms(0, 0, 61).is_err(), "00:00:61 is invaild time");
1058 /// # Ok::<_, datetime_string::Error>(())
1059 /// ```
1060 pub fn from_hms(hour: u8, minute: u8, second: u8) -> Result<Self, Error> {
1061 let mut v = Self::zero();
1062 v.set_time(hour, minute, second)?;
1063 Ok(v)
1064 }
1065
1066 /// Returns a `&Hms6ColonStr` for the string.
1067 ///
1068 /// # Examples
1069 ///
1070 /// ```
1071 /// # use datetime_string::common::Hms6ColonString;
1072 /// use datetime_string::common::Hms6ColonStr;
1073 /// let time = "12:34:56".parse::<Hms6ColonString>()?;
1074 ///
1075 /// // Usually you don't need to call `as_deref()` explicitly, because
1076 /// // `Deref<Target = Hms6ColonStr>` trait is implemented.
1077 /// let _: &Hms6ColonStr = time.as_deref();
1078 /// # Ok::<_, datetime_string::Error>(())
1079 /// ```
1080 #[inline]
1081 #[must_use]
1082 pub fn as_deref(&self) -> &Hms6ColonStr {
1083 unsafe {
1084 // This is safe because the string is already validated.
1085 debug_assert_safe_version_ok!(Hms6ColonStr::from_bytes(&self.0));
1086 Hms6ColonStr::from_bytes_maybe_unchecked(&self.0)
1087 }
1088 }
1089
1090 /// Returns a `&mut Hms6ColonStr` for the string.
1091 ///
1092 /// # Examples
1093 ///
1094 /// ```
1095 /// # use datetime_string::common::Hms6ColonString;
1096 /// use datetime_string::common::Hms6ColonStr;
1097 /// let mut time = "12:34:56".parse::<Hms6ColonString>()?;
1098 ///
1099 /// // Usually you don't need to call `as_deref_mut()` explicitly, because
1100 /// // `DerefMut` trait is implemented.
1101 /// let _: &mut Hms6ColonStr = time.as_deref_mut();
1102 /// # Ok::<_, datetime_string::Error>(())
1103 /// ```
1104 #[inline]
1105 #[must_use]
1106 pub fn as_deref_mut(&mut self) -> &mut Hms6ColonStr {
1107 unsafe {
1108 // This is safe because the string is already validated.
1109 debug_assert_ok!(Hms6ColonStr::from_bytes(&self.0));
1110 Hms6ColonStr::from_bytes_maybe_unchecked_mut(&mut self.0)
1111 }
1112 }
1113}
1114
1115impl core::borrow::Borrow<Hms6ColonStr> for Hms6ColonString {
1116 #[inline]
1117 fn borrow(&self) -> &Hms6ColonStr {
1118 self.as_deref()
1119 }
1120}
1121
1122impl core::borrow::BorrowMut<Hms6ColonStr> for Hms6ColonString {
1123 #[inline]
1124 fn borrow_mut(&mut self) -> &mut Hms6ColonStr {
1125 self.as_deref_mut()
1126 }
1127}
1128
1129impl AsRef<[u8]> for Hms6ColonString {
1130 #[inline]
1131 fn as_ref(&self) -> &[u8] {
1132 self.as_bytes()
1133 }
1134}
1135
1136impl AsRef<str> for Hms6ColonString {
1137 #[inline]
1138 fn as_ref(&self) -> &str {
1139 self.as_str()
1140 }
1141}
1142
1143impl AsRef<Hms6ColonStr> for Hms6ColonString {
1144 #[inline]
1145 fn as_ref(&self) -> &Hms6ColonStr {
1146 self
1147 }
1148}
1149
1150impl AsMut<Hms6ColonStr> for Hms6ColonString {
1151 #[inline]
1152 fn as_mut(&mut self) -> &mut Hms6ColonStr {
1153 self
1154 }
1155}
1156
1157#[cfg(feature = "alloc")]
1158#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
1159impl From<Hms6ColonString> for Vec<u8> {
1160 #[inline]
1161 fn from(v: Hms6ColonString) -> Vec<u8> {
1162 (*v.as_bytes_fixed_len()).into()
1163 }
1164}
1165
1166#[cfg(feature = "alloc")]
1167#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
1168impl From<Hms6ColonString> for String {
1169 #[inline]
1170 fn from(v: Hms6ColonString) -> String {
1171 let vec: Vec<u8> = (*v.as_bytes_fixed_len()).into();
1172 unsafe {
1173 // This is safe because a valid `hh:mm:ss` string is also an ASCII string.
1174 String::from_utf8_unchecked(vec)
1175 }
1176 }
1177}
1178
1179impl From<&Hms6ColonStr> for Hms6ColonString {
1180 fn from(v: &Hms6ColonStr) -> Self {
1181 unsafe {
1182 // This is safe because the value is already validated.
1183 Self::new_maybe_unchecked(*v.as_bytes_fixed_len())
1184 }
1185 }
1186}
1187
1188impl TryFrom<&[u8]> for Hms6ColonString {
1189 type Error = Error;
1190
1191 #[inline]
1192 fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
1193 Hms6ColonStr::from_bytes(v).map(Into::into)
1194 }
1195}
1196
1197impl TryFrom<&str> for Hms6ColonString {
1198 type Error = Error;
1199
1200 #[inline]
1201 fn try_from(v: &str) -> Result<Self, Self::Error> {
1202 Hms6ColonStr::from_str(v).map(Into::into)
1203 }
1204}
1205
1206impl TryFrom<[u8; 8]> for Hms6ColonString {
1207 type Error = Error;
1208
1209 #[inline]
1210 fn try_from(v: [u8; 8]) -> Result<Self, Self::Error> {
1211 validate_bytes(&v)?;
1212 Ok(unsafe {
1213 // This is safe because the value is successfully validated.
1214 Self::new_maybe_unchecked(v)
1215 })
1216 }
1217}
1218
1219#[cfg(feature = "chrono04")]
1220#[cfg_attr(docsrs, doc(cfg(feature = "chrono04")))]
1221impl From<&chrono04::NaiveTime> for Hms6ColonString {
1222 fn from(v: &chrono04::NaiveTime) -> Self {
1223 use chrono04::Timelike;
1224
1225 Self::from_hms(v.hour() as u8, v.minute() as u8, v.second() as u8)
1226 .expect("`chrono04::NaiveTime` must always have a valid time")
1227 }
1228}
1229
1230impl fmt::Display for Hms6ColonString {
1231 #[inline]
1232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1233 self.as_deref().fmt(f)
1234 }
1235}
1236
1237impl ops::Deref for Hms6ColonString {
1238 type Target = Hms6ColonStr;
1239
1240 #[inline]
1241 fn deref(&self) -> &Self::Target {
1242 self.as_deref()
1243 }
1244}
1245
1246impl ops::DerefMut for Hms6ColonString {
1247 #[inline]
1248 fn deref_mut(&mut self) -> &mut Self::Target {
1249 self.as_deref_mut()
1250 }
1251}
1252
1253impl str::FromStr for Hms6ColonString {
1254 type Err = Error;
1255
1256 #[inline]
1257 fn from_str(s: &str) -> Result<Self, Self::Err> {
1258 Self::try_from(s)
1259 }
1260}
1261
1262impl_cmp_symmetric!(Hms6ColonStr, Hms6ColonString, &Hms6ColonString);
1263impl_cmp_symmetric!(Hms6ColonStr, Hms6ColonString, Hms6ColonStr);
1264impl_cmp_symmetric!(Hms6ColonStr, Hms6ColonString, &Hms6ColonStr);
1265impl_cmp_symmetric!(str, Hms6ColonString, str);
1266impl_cmp_symmetric!(str, Hms6ColonString, &str);
1267impl_cmp_symmetric!(str, &Hms6ColonString, str);
1268impl_cmp_symmetric!([u8], Hms6ColonString, [u8]);
1269impl_cmp_symmetric!([u8], Hms6ColonString, &[u8]);
1270impl_cmp_symmetric!([u8], &Hms6ColonString, [u8]);
1271
1272#[cfg(feature = "serde")]
1273#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
1274impl serde::Serialize for Hms6ColonString {
1275 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1276 where
1277 S: serde::Serializer,
1278 {
1279 serializer.serialize_str(self.as_str())
1280 }
1281}
1282
1283/// Items for serde support.
1284#[cfg(feature = "serde")]
1285mod serde_ {
1286 use super::*;
1287
1288 use serde::de::{Deserialize, Deserializer, Visitor};
1289
1290 /// Visitor for `&Hms6ColonStr`.
1291 struct StrVisitor;
1292
1293 impl<'de> Visitor<'de> for StrVisitor {
1294 type Value = &'de Hms6ColonStr;
1295
1296 #[inline]
1297 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1298 f.write_str("hh:mm:ss time string")
1299 }
1300
1301 #[inline]
1302 fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
1303 where
1304 E: serde::de::Error,
1305 {
1306 Self::Value::try_from(v).map_err(E::custom)
1307 }
1308
1309 #[inline]
1310 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
1311 where
1312 E: serde::de::Error,
1313 {
1314 Self::Value::try_from(v).map_err(E::custom)
1315 }
1316 }
1317
1318 impl<'de> Deserialize<'de> for &'de Hms6ColonStr {
1319 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1320 where
1321 D: Deserializer<'de>,
1322 {
1323 deserializer.deserialize_any(StrVisitor)
1324 }
1325 }
1326
1327 /// Visitor for `Hms6ColonString`.
1328 struct StringVisitor;
1329
1330 impl<'de> Visitor<'de> for StringVisitor {
1331 type Value = Hms6ColonString;
1332
1333 #[inline]
1334 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1335 f.write_str("hh:mm:ss time string")
1336 }
1337
1338 #[inline]
1339 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
1340 where
1341 E: serde::de::Error,
1342 {
1343 Self::Value::try_from(v).map_err(E::custom)
1344 }
1345
1346 #[inline]
1347 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1348 where
1349 E: serde::de::Error,
1350 {
1351 Self::Value::try_from(v).map_err(E::custom)
1352 }
1353 }
1354
1355 impl<'de> Deserialize<'de> for Hms6ColonString {
1356 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1357 where
1358 D: Deserializer<'de>,
1359 {
1360 deserializer.deserialize_any(StringVisitor)
1361 }
1362 }
1363}
1364
1365#[cfg(test)]
1366mod tests {
1367 #[cfg(feature = "serde")]
1368 use super::*;
1369
1370 use super::validate_bytes as s_validate;
1371
1372 #[cfg(feature = "serde")]
1373 use serde_test::{assert_de_tokens, assert_tokens, Token};
1374
1375 #[test]
1376 fn validate_bytes() {
1377 assert!(s_validate(b"00:00:00").is_ok());
1378 assert!(s_validate(b"00:00:30").is_ok());
1379 assert!(s_validate(b"00:00:59").is_ok());
1380 assert!(s_validate(b"00:00:60").is_ok());
1381 assert!(s_validate(b"00:30:00").is_ok());
1382 assert!(s_validate(b"00:59:00").is_ok());
1383 assert!(s_validate(b"12:00:00").is_ok());
1384 assert!(s_validate(b"23:00:00").is_ok());
1385
1386 assert!(s_validate(b"00:00:61").is_err());
1387 assert!(s_validate(b"00:00:99").is_err());
1388 assert!(s_validate(b"00:60:00").is_err());
1389 assert!(s_validate(b"00:99:00").is_err());
1390 assert!(s_validate(b"24:00:00").is_err());
1391 assert!(s_validate(b"99:00:00").is_err());
1392
1393 assert!(s_validate(b"+0:00:00").is_err());
1394 assert!(s_validate(b"-0:00:00").is_err());
1395 assert!(s_validate(b"00:+0:00").is_err());
1396 assert!(s_validate(b"00:-0:00").is_err());
1397 assert!(s_validate(b"00:00:+0").is_err());
1398 assert!(s_validate(b"00:00:-0").is_err());
1399 }
1400
1401 #[cfg(feature = "serde")]
1402 #[test]
1403 fn ser_de_str() {
1404 let raw: &'static str = "12:34:56";
1405 assert_tokens(
1406 &Hms6ColonStr::from_str(raw).unwrap(),
1407 &[Token::BorrowedStr(raw)],
1408 );
1409 }
1410
1411 #[cfg(feature = "serde")]
1412 #[test]
1413 fn ser_de_string() {
1414 let raw: &'static str = "12:34:56";
1415 assert_tokens(&Hms6ColonString::try_from(raw).unwrap(), &[Token::Str(raw)]);
1416 }
1417
1418 #[cfg(feature = "serde")]
1419 #[test]
1420 fn de_bytes_slice() {
1421 let raw: &'static [u8; 8] = b"12:34:56";
1422 assert_de_tokens(
1423 &Hms6ColonStr::from_bytes(raw).unwrap(),
1424 &[Token::BorrowedBytes(raw)],
1425 );
1426 }
1427
1428 #[cfg(feature = "serde")]
1429 #[test]
1430 fn de_bytes() {
1431 let raw: &'static [u8; 8] = b"12:34:56";
1432 assert_de_tokens(
1433 &Hms6ColonString::try_from(&raw[..]).unwrap(),
1434 &[Token::Bytes(raw)],
1435 );
1436 }
1437}