datetime_string/rfc3339/secfrac.rs
1//! RFC 3339 [`time-secfrac`] string types.
2//!
3//! [`time-secfrac`]: 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},
12 str,
13};
14
15use crate::{
16 common::SecfracDigitsStr,
17 error::{ComponentKind, Error, ErrorKind},
18};
19
20#[cfg(feature = "alloc")]
21pub use self::owned::SecfracString;
22
23/// Range of digits.
24const DIGITS_RANGE: RangeFrom<usize> = 1..;
25
26/// Validates the given string as an RFC 3339 [`time-secfrac`] string.
27///
28/// [`time-secfrac`]: https://tools.ietf.org/html/rfc3339#section-5.6
29fn validate_bytes(s: &[u8]) -> Result<(), Error> {
30 if s.len() <= 1 {
31 return Err(ErrorKind::TooShort.into());
32 }
33
34 if s[0] != b'.' {
35 return Err(ErrorKind::InvalidSeparator.into());
36 }
37
38 let secfrac_s = &s[DIGITS_RANGE];
39 if !secfrac_s.iter().all(u8::is_ascii_digit) {
40 return Err(ErrorKind::InvalidComponentType(ComponentKind::Secfrac).into());
41 }
42
43 Ok(())
44}
45
46/// String slice for a time in RFC 3339 [`time-secfrac`] format, such as `.7890`.
47///
48/// [`time-secfrac`]: https://tools.ietf.org/html/rfc3339#section-5.6
49#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
50#[repr(transparent)]
51// Note that `derive(Serialize)` cannot used here, because it encodes this as
52// `[u8]` rather than as a string.
53//
54// Comparisons implemented for the type are consistent (at least it is intended to be so).
55// See <https://github.com/rust-lang/rust-clippy/issues/2025>.
56// Note that `clippy::derive_ord_xor_partial_ord` would be introduced since Rust 1.47.0.
57#[allow(clippy::derive_hash_xor_eq)]
58#[allow(unknown_lints, clippy::derive_ord_xor_partial_ord)]
59pub struct SecfracStr([u8]);
60
61impl SecfracStr {
62 /// Creates a `&SecfracStr` from the given byte slice.
63 ///
64 /// This performs assertion in debug build, but not in release build.
65 ///
66 /// # Safety
67 ///
68 /// `validate_bytes(s)` should return `Ok(())`.
69 #[inline]
70 #[must_use]
71 pub(crate) unsafe fn from_bytes_maybe_unchecked(s: &[u8]) -> &Self {
72 debug_assert_ok!(validate_bytes(s));
73 &*(s as *const [u8] as *const Self)
74 }
75
76 /// Creates a `&mut SecfracStr` from the given mutable byte slice.
77 ///
78 /// This performs assertion in debug build, but not in release build.
79 ///
80 /// # Safety
81 ///
82 /// `validate_bytes(s)` should return `Ok(())`.
83 #[inline]
84 #[must_use]
85 pub(crate) unsafe fn from_bytes_maybe_unchecked_mut(s: &mut [u8]) -> &mut Self {
86 debug_assert_ok!(validate_bytes(s));
87 &mut *(s as *mut [u8] as *mut Self)
88 }
89
90 /// Creates a `&mut SecfracStr` from the given mutable string slice.
91 ///
92 /// This performs assertion in debug build, but not in release build.
93 ///
94 /// # Safety
95 ///
96 /// `validate_bytes(s.as_bytes())` should return `Ok(())`.
97 #[inline]
98 #[must_use]
99 unsafe fn from_str_maybe_unchecked_mut(s: &mut str) -> &mut Self {
100 // This is safe because `SecfracStr` ensures that the underlying
101 // bytes are ASCII string after modification.
102 Self::from_bytes_maybe_unchecked_mut(s.as_bytes_mut())
103 }
104
105 /// Creates a new `&SecfracStr` from a string slice.
106 ///
107 /// # Examples
108 ///
109 /// ```
110 /// # use datetime_string::rfc3339::SecfracStr;
111 /// let secfrac = SecfracStr::from_str(".1234")?;
112 /// assert_eq!(secfrac.as_str(), ".1234");
113 ///
114 /// assert!(SecfracStr::from_str(".0").is_ok());
115 /// assert!(SecfracStr::from_str(".0000000000").is_ok());
116 /// assert!(SecfracStr::from_str(".9999999999").is_ok());
117 ///
118 /// assert!(SecfracStr::from_str("0").is_err(), "A leading period is required");
119 /// assert!(SecfracStr::from_str(".").is_err(), "One or more digits are required");
120 /// # Ok::<_, datetime_string::Error>(())
121 /// ```
122 #[inline]
123 // `FromStr` trait cannot be implemented for a slice.
124 #[allow(clippy::should_implement_trait)]
125 pub fn from_str(s: &str) -> Result<&Self, Error> {
126 TryFrom::try_from(s)
127 }
128
129 /// Creates a new `&mut SecfracStr` from a mutable string slice.
130 ///
131 /// # Examples
132 ///
133 /// ```
134 /// # use datetime_string::rfc3339::SecfracStr;
135 /// let mut buf = ".1234".to_owned();
136 /// let secfrac = SecfracStr::from_mut_str(&mut buf)?;
137 /// assert_eq!(secfrac.as_str(), ".1234");
138 ///
139 /// secfrac.digits_mut().fill_with_zero();
140 /// assert_eq!(secfrac.as_str(), ".0000");
141 ///
142 /// assert_eq!(buf, ".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 `&SecfracStr` from a byte slice.
151 ///
152 /// # Examples
153 ///
154 /// ```
155 /// # use datetime_string::rfc3339::SecfracStr;
156 /// let secfrac = SecfracStr::from_bytes(b".1234")?;
157 /// assert_eq!(secfrac.as_str(), ".1234");
158 ///
159 /// assert!(SecfracStr::from_bytes(b".0").is_ok());
160 /// assert!(SecfracStr::from_bytes(b".0000000000").is_ok());
161 /// assert!(SecfracStr::from_bytes(b".9999999999").is_ok());
162 ///
163 /// assert!(SecfracStr::from_bytes(b"0").is_err(), "A leading period is required");
164 /// assert!(SecfracStr::from_bytes(b".").is_err(), "One or more digits are required");
165 /// # Ok::<_, datetime_string::Error>(())
166 /// ```
167 #[inline]
168 pub fn from_bytes(s: &[u8]) -> Result<&Self, Error> {
169 TryFrom::try_from(s)
170 }
171
172 /// Creates a new `&mut SecfracStr` from a mutable byte slice.
173 ///
174 /// # Examples
175 ///
176 /// ```
177 /// # use datetime_string::rfc3339::SecfracStr;
178 /// let mut buf: [u8; 5] = *b".1234";
179 /// let secfrac = SecfracStr::from_bytes_mut(&mut buf)?;
180 /// assert_eq!(secfrac.as_str(), ".1234");
181 ///
182 /// secfrac.digits_mut().fill_with_zero();
183 /// assert_eq!(secfrac.as_str(), ".0000");
184 ///
185 /// assert_eq!(&buf[..], b".0000");
186 /// # Ok::<_, datetime_string::Error>(())
187 /// ```
188 #[inline]
189 pub fn from_bytes_mut(s: &mut [u8]) -> Result<&mut Self, Error> {
190 TryFrom::try_from(s)
191 }
192
193 /// Returns a string slice.
194 ///
195 /// # Examples
196 ///
197 /// ```
198 /// # use datetime_string::rfc3339::SecfracStr;
199 /// let secfrac = SecfracStr::from_str(".1234")?;
200 ///
201 /// assert_eq!(secfrac.as_str(), ".1234");
202 /// # Ok::<_, datetime_string::Error>(())
203 /// ```
204 #[inline]
205 #[must_use]
206 pub fn as_str(&self) -> &str {
207 unsafe {
208 // This is safe because the `SecfracStr` ensures that the
209 // underlying bytes are ASCII string.
210 debug_assert_safe_version_ok!(str::from_utf8(&self.0));
211 str::from_utf8_unchecked(&self.0)
212 }
213 }
214
215 /// Returns a byte slice.
216 ///
217 /// # Examples
218 ///
219 /// ```
220 /// # use datetime_string::rfc3339::SecfracStr;
221 /// let secfrac = SecfracStr::from_str(".1234")?;
222 ///
223 /// assert_eq!(secfrac.as_str(), ".1234");
224 /// # Ok::<_, datetime_string::Error>(())
225 /// ```
226 #[inline]
227 #[must_use]
228 pub fn as_bytes(&self) -> &[u8] {
229 &self.0
230 }
231
232 /// Returns the digits.
233 ///
234 /// # Examples
235 ///
236 /// ```
237 /// # use datetime_string::rfc3339::SecfracStr;
238 /// use datetime_string::common::SecfracDigitsStr;
239 ///
240 /// let secfrac = SecfracStr::from_str(".1234")?;
241 /// assert_eq!(secfrac.digits().as_str(), "1234");
242 ///
243 /// let secfrac2 = SecfracStr::from_str(".012340")?;
244 /// assert_eq!(secfrac2.digits().as_str(), "012340");
245 /// # Ok::<_, datetime_string::Error>(())
246 /// ```
247 #[inline]
248 #[must_use]
249 pub fn digits(&self) -> &SecfracDigitsStr {
250 unsafe {
251 // This is safe because the digits part contains only ASCII digits.
252 debug_assert_safe_version_ok!(SecfracDigitsStr::from_bytes(&self.0[DIGITS_RANGE]));
253 SecfracDigitsStr::from_bytes_maybe_unchecked(self.0.get_unchecked(DIGITS_RANGE))
254 }
255 }
256
257 /// Returns the digits as a mutable reference.
258 ///
259 /// # Examples
260 ///
261 /// ```
262 /// # use datetime_string::rfc3339::SecfracStr;
263 /// use datetime_string::common::SecfracDigitsStr;
264 ///
265 /// let mut buf = ".1234".to_owned();
266 /// let secfrac = SecfracStr::from_mut_str(&mut buf)?;
267 /// let digits = secfrac.digits_mut();
268 /// assert_eq!(digits.as_str(), "1234");
269 ///
270 /// digits.fill_with_zero();
271 /// assert_eq!(digits.as_str(), "0000");
272 /// assert_eq!(secfrac.as_str(), ".0000");
273 /// assert_eq!(buf, ".0000");
274 /// # Ok::<_, datetime_string::Error>(())
275 /// ```
276 #[inline]
277 #[must_use]
278 pub fn digits_mut(&mut self) -> &mut SecfracDigitsStr {
279 unsafe {
280 // This is safe because a `SecfracStr` string is an ASCII string,
281 // and `SecfracDigitsStr` ensures that the underlying bytes are
282 // also ASCII string after modification.
283 debug_assert_ok!(SecfracDigitsStr::from_bytes(&self.0[DIGITS_RANGE]));
284 SecfracDigitsStr::from_bytes_maybe_unchecked_mut(self.0.get_unchecked_mut(DIGITS_RANGE))
285 }
286 }
287
288 /// Returns a milliseconds precision secfrac if there are enough digits.
289 ///
290 /// # Examples
291 ///
292 /// ```
293 /// # use datetime_string::rfc3339::SecfracStr;
294 /// let not_precise = SecfracStr::from_str(".1")?;
295 /// assert_eq!(not_precise.milliseconds_secfrac(), None);
296 ///
297 /// let expected = SecfracStr::from_str(".012")?;
298 /// assert_eq!(expected.milliseconds_secfrac(), Some(expected));
299 ///
300 /// let more_precise = SecfracStr::from_str(".012345678901")?;
301 /// assert_eq!(more_precise.milliseconds_secfrac(), Some(expected));
302 /// # Ok::<_, datetime_string::Error>(())
303 /// ```
304 #[inline]
305 #[must_use]
306 pub fn milliseconds_secfrac(&self) -> Option<&SecfracStr> {
307 self.0.get(..4).map(|s| unsafe {
308 // This is safe because ".NNN" value (where Ns are digits) is a
309 // valid time-secfrac string.
310 debug_assert_safe_version_ok!(Self::from_bytes(s));
311 Self::from_bytes_maybe_unchecked(s)
312 })
313 }
314
315 /// Returns a microseconds precision secfrac if there are enough digits.
316 ///
317 /// # Examples
318 ///
319 /// ```
320 /// # use datetime_string::rfc3339::SecfracStr;
321 /// let not_precise = SecfracStr::from_str(".1234")?;
322 /// assert_eq!(not_precise.microseconds_secfrac(), None);
323 ///
324 /// let expected = SecfracStr::from_str(".012345")?;
325 /// assert_eq!(expected.microseconds_secfrac(), Some(expected));
326 ///
327 /// let more_precise = SecfracStr::from_str(".012345678901")?;
328 /// assert_eq!(more_precise.microseconds_secfrac(), Some(expected));
329 /// # Ok::<_, datetime_string::Error>(())
330 /// ```
331 #[inline]
332 #[must_use]
333 pub fn microseconds_secfrac(&self) -> Option<&SecfracStr> {
334 self.0.get(..7).map(|s| unsafe {
335 // This is safe because ".NNNNNN" value (where Ns are digits) is a
336 // valid time-secfrac string.
337 debug_assert_safe_version_ok!(Self::from_bytes(s));
338 Self::from_bytes_maybe_unchecked(s)
339 })
340 }
341
342 /// Returns a nanoseconds precision secfrac if there are enough digits.
343 ///
344 /// # Examples
345 ///
346 /// ```
347 /// # use datetime_string::rfc3339::SecfracStr;
348 /// let not_precise = SecfracStr::from_str(".1234")?;
349 /// assert_eq!(not_precise.nanoseconds_secfrac(), None);
350 ///
351 /// let expected = SecfracStr::from_str(".012345678")?;
352 /// assert_eq!(expected.nanoseconds_secfrac(), Some(expected));
353 ///
354 /// let more_precise = SecfracStr::from_str(".012345678901")?;
355 /// assert_eq!(more_precise.nanoseconds_secfrac(), Some(expected));
356 /// # Ok::<_, datetime_string::Error>(())
357 /// ```
358 #[inline]
359 #[must_use]
360 pub fn nanoseconds_secfrac(&self) -> Option<&SecfracStr> {
361 self.0.get(..10).map(|s| unsafe {
362 // This is safe because ".NNNNNNNNN" value (where Ns are digits) is
363 // a valid time-secfrac string.
364 debug_assert_safe_version_ok!(Self::from_bytes(s));
365 Self::from_bytes_maybe_unchecked(s)
366 })
367 }
368}
369
370impl AsRef<[u8]> for SecfracStr {
371 #[inline]
372 fn as_ref(&self) -> &[u8] {
373 self.as_bytes()
374 }
375}
376
377impl AsRef<str> for SecfracStr {
378 #[inline]
379 fn as_ref(&self) -> &str {
380 self.as_str()
381 }
382}
383
384impl AsRef<SecfracStr> for SecfracStr {
385 #[inline]
386 fn as_ref(&self) -> &SecfracStr {
387 self
388 }
389}
390
391impl AsMut<SecfracStr> for SecfracStr {
392 #[inline]
393 fn as_mut(&mut self) -> &mut SecfracStr {
394 self
395 }
396}
397
398impl<'a> From<&'a SecfracStr> for &'a str {
399 #[inline]
400 fn from(v: &'a SecfracStr) -> Self {
401 v.as_str()
402 }
403}
404
405impl<'a> TryFrom<&'a [u8]> for &'a SecfracStr {
406 type Error = Error;
407
408 #[inline]
409 fn try_from(v: &'a [u8]) -> Result<Self, Self::Error> {
410 validate_bytes(v)?;
411 Ok(unsafe {
412 // This is safe because a valid `time-secfrac` string is also an ASCII string.
413 SecfracStr::from_bytes_maybe_unchecked(v)
414 })
415 }
416}
417
418impl<'a> TryFrom<&'a mut [u8]> for &'a mut SecfracStr {
419 type Error = Error;
420
421 #[inline]
422 fn try_from(v: &'a mut [u8]) -> Result<Self, Self::Error> {
423 validate_bytes(v)?;
424 Ok(unsafe {
425 // This is safe because a valid `time-secfrac` string is also an ASCII string.
426 SecfracStr::from_bytes_maybe_unchecked_mut(v)
427 })
428 }
429}
430
431impl<'a> TryFrom<&'a str> for &'a SecfracStr {
432 type Error = Error;
433
434 #[inline]
435 fn try_from(v: &'a str) -> Result<Self, Self::Error> {
436 Self::try_from(v.as_bytes())
437 }
438}
439
440impl<'a> TryFrom<&'a mut str> for &'a mut SecfracStr {
441 type Error = Error;
442
443 #[inline]
444 fn try_from(v: &'a mut str) -> Result<Self, Self::Error> {
445 validate_bytes(v.as_bytes())?;
446 Ok(unsafe {
447 // This is safe because it is successfully validated, and
448 // `SecfracStr` ensures that the underlying bytes are ASCII string
449 // after modification.
450 SecfracStr::from_str_maybe_unchecked_mut(v)
451 })
452 }
453}
454
455impl fmt::Display for SecfracStr {
456 #[inline]
457 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458 self.as_str().fmt(f)
459 }
460}
461
462impl ops::Deref for SecfracStr {
463 type Target = str;
464
465 #[inline]
466 fn deref(&self) -> &Self::Target {
467 self.as_str()
468 }
469}
470
471impl_cmp_symmetric!(str, SecfracStr, &SecfracStr);
472impl_cmp_symmetric!([u8], SecfracStr, [u8]);
473impl_cmp_symmetric!([u8], SecfracStr, &[u8]);
474impl_cmp_symmetric!([u8], &SecfracStr, [u8]);
475impl_cmp_symmetric!(str, SecfracStr, str);
476impl_cmp_symmetric!(str, SecfracStr, &str);
477impl_cmp_symmetric!(str, &SecfracStr, str);
478
479#[cfg(feature = "serde")]
480#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
481impl serde::Serialize for SecfracStr {
482 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
483 where
484 S: serde::Serializer,
485 {
486 serializer.serialize_str(self.as_str())
487 }
488}
489
490/// Items for serde support.
491#[cfg(feature = "serde")]
492mod serde_ {
493 use super::*;
494
495 use serde::de::{Deserialize, Deserializer, Visitor};
496
497 /// Visitor for `&SecfracStr`.
498 struct StrVisitor;
499
500 impl<'de> Visitor<'de> for StrVisitor {
501 type Value = &'de SecfracStr;
502
503 #[inline]
504 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505 f.write_str("RFC 3339 time-secfrac string")
506 }
507
508 #[inline]
509 fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
510 where
511 E: serde::de::Error,
512 {
513 Self::Value::try_from(v).map_err(E::custom)
514 }
515
516 #[inline]
517 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
518 where
519 E: serde::de::Error,
520 {
521 Self::Value::try_from(v).map_err(E::custom)
522 }
523 }
524
525 impl<'de> Deserialize<'de> for &'de SecfracStr {
526 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
527 where
528 D: Deserializer<'de>,
529 {
530 deserializer.deserialize_any(StrVisitor)
531 }
532 }
533}
534
535#[cfg(test)]
536mod tests {
537 #[cfg(feature = "serde")]
538 use super::*;
539
540 use super::validate_bytes as s_validate;
541
542 #[cfg(feature = "serde")]
543 use serde_test::{assert_de_tokens, assert_tokens, Token};
544
545 #[test]
546 fn validate_bytes() {
547 assert!(s_validate(b".0").is_ok());
548 assert!(s_validate(b".9").is_ok());
549 assert!(s_validate(b".1234").is_ok());
550 assert!(s_validate(b".001200").is_ok());
551 assert!(s_validate(b".0000000").is_ok());
552 assert!(s_validate(b".9999999").is_ok());
553 assert!(s_validate(b".00000000000000000000000000000000").is_ok());
554 assert!(s_validate(b".99999999999999999999999999999999").is_ok());
555
556 assert!(s_validate(b".").is_err());
557 assert!(s_validate(b"0").is_err());
558 assert!(s_validate(b".+0").is_err());
559 assert!(s_validate(b".-0").is_err());
560 assert!(s_validate(b".0 ").is_err());
561 }
562
563 #[cfg(feature = "serde")]
564 #[test]
565 fn ser_de_str() {
566 let raw: &'static str = ".1234";
567 assert_tokens(
568 &SecfracStr::from_str(raw).unwrap(),
569 &[Token::BorrowedStr(raw)],
570 );
571 }
572
573 #[cfg(feature = "serde")]
574 #[test]
575 fn de_bytes_slice() {
576 let raw: &'static [u8; 5] = b".1234";
577 assert_de_tokens(
578 &SecfracStr::from_bytes(raw).unwrap(),
579 &[Token::BorrowedBytes(raw)],
580 );
581 }
582}