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}