datetime_string/common/
ymd8_hyphen.rs

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