use core::{
convert::TryFrom,
fmt,
ops::{self, Range},
str,
};
use crate::{
datetime::{is_leap_year, validate_ym0d, validate_ym1d},
parse::{parse_digits2, parse_digits4},
str::{write_digit2, write_digit4},
};
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
use crate::error::{ComponentKind, Error, ErrorKind};
const FULL_DATE_LEN: usize = 10;
const YEAR_RANGE: Range<usize> = 0..4;
const MONTH_RANGE: Range<usize> = 5..7;
const MDAY_RANGE: Range<usize> = 8..10;
fn validate_bytes(s: &[u8]) -> Result<(), Error> {
let s: &[u8; FULL_DATE_LEN] = TryFrom::try_from(s).map_err(|_| {
if s.len() < FULL_DATE_LEN {
ErrorKind::TooShort
} else {
ErrorKind::TooLong
}
})?;
if (s[4] != b'-') || (s[7] != b'-') {
return Err(ErrorKind::InvalidSeparator.into());
}
let year_s: [u8; 4] = [s[0], s[1], s[2], s[3]];
let month_s: [u8; 2] = [s[5], s[6]];
let mday_s: [u8; 2] = [s[8], s[9]];
if !year_s.iter().all(u8::is_ascii_digit) {
return Err(ErrorKind::InvalidComponentType(ComponentKind::Year).into());
}
if !month_s.iter().all(u8::is_ascii_digit) {
return Err(ErrorKind::InvalidComponentType(ComponentKind::Month).into());
}
if !mday_s.iter().all(u8::is_ascii_digit) {
return Err(ErrorKind::InvalidComponentType(ComponentKind::Mday).into());
}
let month1 = parse_digits2(month_s);
if !(1..=12).contains(&month1) {
return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Month).into());
}
let mday = parse_digits2(mday_s);
if mday < 1 {
return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Mday).into());
}
let year = parse_digits4(year_s);
validate_ym1d(year, month1, mday).map_err(Into::into)
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
#[allow(clippy::derive_hash_xor_eq)]
#[allow(unknown_lints, clippy::derive_ord_xor_partial_ord)]
pub struct Ymd8HyphenStr([u8]);
impl Ymd8HyphenStr {
#[inline]
#[must_use]
pub(crate) unsafe fn from_bytes_maybe_unchecked(s: &[u8]) -> &Self {
debug_assert_ok!(validate_bytes(s));
&*(s as *const [u8] as *const Self)
}
#[inline]
#[must_use]
pub(crate) unsafe fn from_bytes_maybe_unchecked_mut(s: &mut [u8]) -> &mut Self {
debug_assert_ok!(validate_bytes(s));
&mut *(s as *mut [u8] as *mut Self)
}
#[inline]
#[must_use]
unsafe fn from_str_maybe_unchecked_mut(s: &mut str) -> &mut Self {
Self::from_bytes_maybe_unchecked_mut(s.as_bytes_mut())
}
#[inline]
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Result<&Self, Error> {
TryFrom::try_from(s)
}
#[inline]
pub fn from_mut_str(s: &mut str) -> Result<&mut Self, Error> {
TryFrom::try_from(s)
}
#[inline]
pub fn from_bytes(s: &[u8]) -> Result<&Self, Error> {
TryFrom::try_from(s)
}
#[inline]
pub fn from_bytes_mut(s: &mut [u8]) -> Result<&mut Self, Error> {
TryFrom::try_from(s)
}
#[inline]
pub fn assign(&mut self, v: &Self) {
debug_assert_eq!(self.0.len(), v.0.len());
self.0.copy_from_slice(&v.0);
}
#[inline]
#[must_use]
pub fn as_str(&self) -> &str {
unsafe {
debug_assert_safe_version_ok!(str::from_utf8(&self.0));
str::from_utf8_unchecked(&self.0)
}
}
#[inline]
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
#[inline]
#[must_use]
pub fn as_bytes_fixed_len(&self) -> &[u8; 10] {
debug_assert_eq!(
self.len(),
FULL_DATE_LEN,
"Ymd8HyphenStr must always be 10 bytes"
);
debug_assert_safe_version_ok!(<&[u8; FULL_DATE_LEN]>::try_from(&self.0));
let ptr = self.0.as_ptr() as *const [u8; FULL_DATE_LEN];
unsafe { &*ptr }
}
#[inline]
#[must_use]
pub fn year_str(&self) -> &str {
unsafe {
debug_assert_safe_version_ok!(str::from_utf8(&self.0[YEAR_RANGE]));
str::from_utf8_unchecked(self.0.get_unchecked(YEAR_RANGE))
}
}
#[inline]
#[must_use]
pub fn year_bytes_fixed_len(&self) -> &[u8; 4] {
unsafe {
debug_assert_safe_version_ok!(<&[u8; 4]>::try_from(&self.0[YEAR_RANGE]));
let ptr = self.0.as_ptr().add(YEAR_RANGE.start) as *const [u8; 4];
&*ptr
}
}
#[inline]
#[must_use]
unsafe fn year_bytes_mut_fixed_len(&mut self) -> &mut [u8; 4] {
debug_assert_ok!(<&[u8; 4]>::try_from(&self.0[YEAR_RANGE]));
let ptr = self.0.as_mut_ptr().add(YEAR_RANGE.start) as *mut [u8; 4];
&mut *ptr
}
#[inline]
#[must_use]
pub fn year(&self) -> u16 {
parse_digits4(*self.year_bytes_fixed_len())
}
#[inline]
#[must_use]
pub fn month_str(&self) -> &str {
unsafe {
debug_assert_safe_version_ok!(str::from_utf8(&self.0[MONTH_RANGE]));
str::from_utf8_unchecked(self.0.get_unchecked(MONTH_RANGE))
}
}
#[inline]
#[must_use]
pub fn month_bytes_fixed_len(&self) -> &[u8; 2] {
unsafe {
debug_assert_safe_version_ok!(<&[u8; 2]>::try_from(&self.0[MONTH_RANGE]));
let ptr = self.0.as_ptr().add(MONTH_RANGE.start) as *const [u8; 2];
&*ptr
}
}
#[inline]
#[must_use]
unsafe fn month_bytes_mut_fixed_len(&mut self) -> &mut [u8; 2] {
debug_assert_ok!(<&[u8; 2]>::try_from(&self.0[MONTH_RANGE]));
let ptr = self.0.as_mut_ptr().add(MONTH_RANGE.start) as *mut [u8; 2];
&mut *ptr
}
#[inline]
#[must_use]
pub fn month1(&self) -> u8 {
parse_digits2(*self.month_bytes_fixed_len())
}
#[inline]
#[must_use]
pub fn month0(&self) -> u8 {
parse_digits2(*self.month_bytes_fixed_len()).wrapping_sub(1)
}
#[inline]
#[must_use]
pub fn mday_str(&self) -> &str {
unsafe {
debug_assert_safe_version_ok!(str::from_utf8(&self.0[MDAY_RANGE]));
str::from_utf8_unchecked(self.0.get_unchecked(MDAY_RANGE))
}
}
#[inline]
#[must_use]
pub fn mday_bytes_fixed_len(&self) -> &[u8; 2] {
unsafe {
debug_assert_safe_version_ok!(<&[u8; 2]>::try_from(&self.0[MDAY_RANGE]));
let ptr = self.0.as_ptr().add(MDAY_RANGE.start) as *const [u8; 2];
&*ptr
}
}
#[inline]
#[must_use]
unsafe fn mday_bytes_mut_fixed_len(&mut self) -> &mut [u8; 2] {
debug_assert_ok!(<&[u8; 2]>::try_from(&self.0[MDAY_RANGE]));
let ptr = self.0.as_mut_ptr().add(MDAY_RANGE.start) as *mut [u8; 2];
&mut *ptr
}
#[inline]
#[must_use]
pub fn mday(&self) -> u8 {
parse_digits2(*self.mday_bytes_fixed_len())
}
pub fn set_year(&mut self, year: u16) -> Result<(), Error> {
if year > 9999 {
return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Year).into());
}
validate_ym1d(year, self.month1(), self.mday())?;
unsafe {
write_digit4(self.year_bytes_mut_fixed_len(), year);
}
debug_assert_ok!(validate_bytes(&self.0));
debug_assert_ok!(
validate_ym1d(self.year(), self.month1(), self.mday()),
"Date should be valid after modification"
);
Ok(())
}
pub fn set_month0(&mut self, month0: u8) -> Result<(), Error> {
if month0 >= 12 {
return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Month).into());
}
validate_ym0d(self.year(), month0, self.mday())?;
unsafe {
write_digit2(self.month_bytes_mut_fixed_len(), month0.wrapping_add(1));
}
debug_assert_ok!(validate_bytes(&self.0));
debug_assert_ok!(
validate_ym1d(self.year(), self.month1(), self.mday()),
"Date should be valid after modification"
);
Ok(())
}
#[inline]
pub fn set_month1(&mut self, month1: u8) -> Result<(), Error> {
self.set_month0(month1.wrapping_sub(1))
}
pub fn set_mday(&mut self, mday: u8) -> Result<(), Error> {
validate_ym1d(self.year(), self.month1(), mday)?;
unsafe {
write_digit2(self.mday_bytes_mut_fixed_len(), mday);
}
debug_assert_ok!(validate_bytes(&self.0));
debug_assert_ok!(
validate_ym1d(self.year(), self.month1(), self.mday()),
"Date should be valid after modification"
);
Ok(())
}
pub fn set_month0_mday(&mut self, month0: u8, mday: u8) -> Result<(), Error> {
validate_ym0d(self.year(), month0, mday)?;
unsafe {
write_digit2(self.month_bytes_mut_fixed_len(), month0.wrapping_add(1));
write_digit2(self.mday_bytes_mut_fixed_len(), mday);
}
debug_assert_ok!(validate_bytes(&self.0));
debug_assert_ok!(
validate_ym1d(self.year(), self.month1(), self.mday()),
"Date should be valid after modification"
);
Ok(())
}
pub fn set_month1_mday(&mut self, month1: u8, mday: u8) -> Result<(), Error> {
validate_ym1d(self.year(), month1, mday)?;
unsafe {
write_digit2(self.month_bytes_mut_fixed_len(), month1);
write_digit2(self.mday_bytes_mut_fixed_len(), mday);
}
debug_assert_ok!(validate_bytes(&self.0));
debug_assert_ok!(
validate_ym1d(self.year(), self.month1(), self.mday()),
"Date should be valid after modification"
);
Ok(())
}
pub fn set_ym0d(&mut self, year: u16, month0: u8, mday: u8) -> Result<(), Error> {
validate_ym0d(year, month0, mday)?;
unsafe {
write_digit4(self.year_bytes_mut_fixed_len(), year);
write_digit2(self.month_bytes_mut_fixed_len(), month0.wrapping_add(1));
write_digit2(self.mday_bytes_mut_fixed_len(), mday);
}
debug_assert_ok!(validate_bytes(&self.0));
debug_assert_ok!(
validate_ym1d(self.year(), self.month1(), self.mday()),
"Date should be valid after modification"
);
Ok(())
}
#[inline]
pub fn set_ym1d(&mut self, year: u16, month1: u8, mday: u8) -> Result<(), Error> {
self.set_ym0d(year, month1.wrapping_sub(1), mday)
}
#[inline]
pub fn yday0(&self) -> u16 {
self.yday1() - 1
}
pub fn yday1(&self) -> u16 {
const BASE_YDAYS: [u16; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
let month0 = self.month0();
let non_leap_yday = BASE_YDAYS[usize::from(month0)] + u16::from(self.mday());
if month0 > 1 && is_leap_year(self.year()) {
non_leap_yday + 1
} else {
non_leap_yday
}
}
pub fn days_since_epoch(&self) -> i32 {
let tm_year = i32::from(self.year()) - 1900;
(i32::from(self.yday1()) - 1) + (tm_year - 70) * 365 + (tm_year - 69) / 4
- (tm_year - 1) / 100
+ (tm_year + 299) / 400
}
#[cfg(feature = "time03")]
#[cfg_attr(docsrs, doc(cfg(feature = "time03")))]
#[inline]
#[must_use]
fn time03_month(&self) -> time03::Month {
unsafe {
debug_assert!(
((time03::Month::January as u8)..=(time03::Month::December as u8))
.contains(&self.month1())
);
core::mem::transmute::<u8, time03::Month>(self.month1())
}
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl alloc::borrow::ToOwned for Ymd8HyphenStr {
type Owned = Ymd8HyphenString;
#[inline]
fn to_owned(&self) -> Self::Owned {
self.into()
}
}
impl AsRef<[u8]> for Ymd8HyphenStr {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl AsRef<str> for Ymd8HyphenStr {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<Ymd8HyphenStr> for Ymd8HyphenStr {
#[inline]
fn as_ref(&self) -> &Ymd8HyphenStr {
self
}
}
impl AsMut<Ymd8HyphenStr> for Ymd8HyphenStr {
#[inline]
fn as_mut(&mut self) -> &mut Ymd8HyphenStr {
self
}
}
impl<'a> From<&'a Ymd8HyphenStr> for &'a str {
#[inline]
fn from(v: &'a Ymd8HyphenStr) -> Self {
v.as_str()
}
}
#[cfg(feature = "chrono04")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono04")))]
impl From<&Ymd8HyphenStr> for chrono04::NaiveDate {
fn from(v: &Ymd8HyphenStr) -> Self {
let year = i32::from(v.year());
let month1 = u32::from(v.month1());
let mday = u32::from(v.mday());
Self::from_ymd(year, month1, mday)
}
}
#[cfg(feature = "time03")]
#[cfg_attr(docsrs, doc(cfg(feature = "time03")))]
impl From<&Ymd8HyphenStr> for time03::Date {
fn from(v: &Ymd8HyphenStr) -> Self {
let year = i32::from(v.year());
let month = v.time03_month();
let mday = v.mday();
Self::from_calendar_date(year, month, mday).expect("[validity] the date must be valid")
}
}
impl<'a> TryFrom<&'a [u8]> for &'a Ymd8HyphenStr {
type Error = Error;
#[inline]
fn try_from(v: &'a [u8]) -> Result<Self, Self::Error> {
validate_bytes(v)?;
Ok(unsafe {
Ymd8HyphenStr::from_bytes_maybe_unchecked(v)
})
}
}
impl<'a> TryFrom<&'a mut [u8]> for &'a mut Ymd8HyphenStr {
type Error = Error;
#[inline]
fn try_from(v: &'a mut [u8]) -> Result<Self, Self::Error> {
validate_bytes(v)?;
Ok(unsafe {
Ymd8HyphenStr::from_bytes_maybe_unchecked_mut(v)
})
}
}
impl<'a> TryFrom<&'a str> for &'a Ymd8HyphenStr {
type Error = Error;
#[inline]
fn try_from(v: &'a str) -> Result<Self, Self::Error> {
TryFrom::try_from(v.as_bytes())
}
}
impl<'a> TryFrom<&'a mut str> for &'a mut Ymd8HyphenStr {
type Error = Error;
#[inline]
fn try_from(v: &'a mut str) -> Result<Self, Self::Error> {
validate_bytes(v.as_bytes())?;
Ok(unsafe {
Ymd8HyphenStr::from_str_maybe_unchecked_mut(v)
})
}
}
impl fmt::Display for Ymd8HyphenStr {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl ops::Deref for Ymd8HyphenStr {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl_cmp_symmetric!(str, Ymd8HyphenStr, &Ymd8HyphenStr);
impl_cmp_symmetric!([u8], Ymd8HyphenStr, [u8]);
impl_cmp_symmetric!([u8], Ymd8HyphenStr, &[u8]);
impl_cmp_symmetric!([u8], &Ymd8HyphenStr, [u8]);
impl_cmp_symmetric!(str, Ymd8HyphenStr, str);
impl_cmp_symmetric!(str, Ymd8HyphenStr, &str);
impl_cmp_symmetric!(str, &Ymd8HyphenStr, str);
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl serde::Serialize for Ymd8HyphenStr {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
#[allow(clippy::derive_hash_xor_eq)]
#[allow(unknown_lints, clippy::derive_ord_xor_partial_ord)]
pub struct Ymd8HyphenString([u8; FULL_DATE_LEN]);
impl Ymd8HyphenString {
#[inline]
#[must_use]
unsafe fn new_maybe_unchecked(s: [u8; 10]) -> Self {
debug_assert_ok!(validate_bytes(&s));
Self(s)
}
#[inline]
#[must_use]
fn min() -> Self {
unsafe {
debug_assert_safe_version_ok!(Self::try_from(*b"0000-01-01"));
Self::new_maybe_unchecked(*b"0000-01-01")
}
}
pub fn from_ym0d(year: u16, month0: u8, mday: u8) -> Result<Self, Error> {
let mut v = Self::min();
v.set_ym0d(year, month0, mday)?;
Ok(v)
}
pub fn from_ym1d(year: u16, month1: u8, mday: u8) -> Result<Self, Error> {
let mut v = Self::min();
v.set_ym1d(year, month1, mday)?;
Ok(v)
}
#[inline]
#[must_use]
pub fn as_deref(&self) -> &Ymd8HyphenStr {
unsafe {
debug_assert_ok!(Ymd8HyphenStr::from_bytes(&self.0));
Ymd8HyphenStr::from_bytes_maybe_unchecked(&self.0)
}
}
#[inline]
#[must_use]
pub fn as_deref_mut(&mut self) -> &mut Ymd8HyphenStr {
unsafe {
debug_assert_ok!(Ymd8HyphenStr::from_bytes(&self.0));
Ymd8HyphenStr::from_bytes_maybe_unchecked_mut(&mut self.0)
}
}
}
impl core::borrow::Borrow<Ymd8HyphenStr> for Ymd8HyphenString {
#[inline]
fn borrow(&self) -> &Ymd8HyphenStr {
self.as_deref()
}
}
impl core::borrow::BorrowMut<Ymd8HyphenStr> for Ymd8HyphenString {
#[inline]
fn borrow_mut(&mut self) -> &mut Ymd8HyphenStr {
self.as_deref_mut()
}
}
impl AsRef<[u8]> for Ymd8HyphenString {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl AsRef<str> for Ymd8HyphenString {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<Ymd8HyphenStr> for Ymd8HyphenString {
#[inline]
fn as_ref(&self) -> &Ymd8HyphenStr {
self
}
}
impl AsMut<Ymd8HyphenStr> for Ymd8HyphenString {
#[inline]
fn as_mut(&mut self) -> &mut Ymd8HyphenStr {
self
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl From<Ymd8HyphenString> for Vec<u8> {
#[inline]
fn from(v: Ymd8HyphenString) -> Vec<u8> {
(*v.as_bytes_fixed_len()).into()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl From<Ymd8HyphenString> for String {
#[inline]
fn from(v: Ymd8HyphenString) -> String {
let vec: Vec<u8> = (*v.as_bytes_fixed_len()).into();
unsafe {
String::from_utf8_unchecked(vec)
}
}
}
impl From<&Ymd8HyphenStr> for Ymd8HyphenString {
fn from(v: &Ymd8HyphenStr) -> Self {
unsafe {
Self::new_maybe_unchecked(*v.as_bytes_fixed_len())
}
}
}
#[cfg(feature = "chrono04")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono04")))]
impl TryFrom<&chrono04::NaiveDate> for Ymd8HyphenString {
type Error = Error;
fn try_from(v: &chrono04::NaiveDate) -> Result<Self, Self::Error> {
use chrono04::Datelike;
let year = v.year();
if (0..=9999).contains(&year) {
return Err(ErrorKind::ComponentOutOfRange(ComponentKind::Year).into());
}
Ok(
Self::from_ym1d(v.year() as u16, v.month() as u8, v.day() as u8)
.expect("`chrono04::NaiveTime` must always have a valid date"),
)
}
}
impl TryFrom<&[u8]> for Ymd8HyphenString {
type Error = Error;
#[inline]
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
Ymd8HyphenStr::from_bytes(v).map(Into::into)
}
}
impl TryFrom<&str> for Ymd8HyphenString {
type Error = Error;
#[inline]
fn try_from(v: &str) -> Result<Self, Self::Error> {
Ymd8HyphenStr::from_str(v).map(Into::into)
}
}
impl TryFrom<[u8; 10]> for Ymd8HyphenString {
type Error = Error;
#[inline]
fn try_from(v: [u8; 10]) -> Result<Self, Self::Error> {
validate_bytes(&v)?;
Ok(unsafe {
Self::new_maybe_unchecked(v)
})
}
}
impl fmt::Display for Ymd8HyphenString {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_deref().fmt(f)
}
}
impl ops::Deref for Ymd8HyphenString {
type Target = Ymd8HyphenStr;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_deref()
}
}
impl ops::DerefMut for Ymd8HyphenString {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_deref_mut()
}
}
impl str::FromStr for Ymd8HyphenString {
type Err = Error;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
}
impl_cmp_symmetric!(Ymd8HyphenStr, Ymd8HyphenString, &Ymd8HyphenString);
impl_cmp_symmetric!(Ymd8HyphenStr, Ymd8HyphenString, Ymd8HyphenStr);
impl_cmp_symmetric!(Ymd8HyphenStr, Ymd8HyphenString, &Ymd8HyphenStr);
impl_cmp_symmetric!(str, Ymd8HyphenString, str);
impl_cmp_symmetric!(str, Ymd8HyphenString, &str);
impl_cmp_symmetric!(str, &Ymd8HyphenString, str);
impl_cmp_symmetric!([u8], Ymd8HyphenString, [u8]);
impl_cmp_symmetric!([u8], Ymd8HyphenString, &[u8]);
impl_cmp_symmetric!([u8], &Ymd8HyphenString, [u8]);
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl serde::Serialize for Ymd8HyphenString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
#[cfg(feature = "serde")]
mod serde_ {
use super::*;
use serde::de::{Deserialize, Deserializer, Visitor};
struct StrVisitor;
impl<'de> Visitor<'de> for StrVisitor {
type Value = &'de Ymd8HyphenStr;
#[inline]
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("YYYY-MM-DD date string")
}
#[inline]
fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::Value::try_from(v).map_err(E::custom)
}
#[inline]
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::Value::try_from(v).map_err(E::custom)
}
}
impl<'de> Deserialize<'de> for &'de Ymd8HyphenStr {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(StrVisitor)
}
}
struct StringVisitor;
impl<'de> Visitor<'de> for StringVisitor {
type Value = Ymd8HyphenString;
#[inline]
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("YYYY-MM-DD date string")
}
#[inline]
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::Value::try_from(v).map_err(E::custom)
}
#[inline]
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::Value::try_from(v).map_err(E::custom)
}
}
impl<'de> Deserialize<'de> for Ymd8HyphenString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(StringVisitor)
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "serde")]
use super::*;
use super::validate_bytes as s_validate;
#[cfg(feature = "serde")]
use serde_test::{assert_de_tokens, assert_tokens, Token};
#[test]
fn validate_bytes() {
assert!(s_validate(b"0000-01-01").is_ok());
assert!(s_validate(b"9999-12-31").is_ok());
assert!(s_validate(b"2001-01-01").is_ok());
assert!(s_validate(b"2001-01-31").is_ok());
assert!(s_validate(b"2001-03-31").is_ok());
assert!(s_validate(b"2001-04-30").is_ok());
assert!(s_validate(b"2001-05-31").is_ok());
assert!(s_validate(b"2001-06-30").is_ok());
assert!(s_validate(b"2001-07-31").is_ok());
assert!(s_validate(b"2001-08-31").is_ok());
assert!(s_validate(b"2001-09-30").is_ok());
assert!(s_validate(b"2001-10-31").is_ok());
assert!(s_validate(b"2001-11-30").is_ok());
assert!(s_validate(b"2001-12-31").is_ok());
assert!(s_validate(b"2001-00-01").is_err());
assert!(s_validate(b"2001-13-01").is_err());
assert!(s_validate(b"2001-01-00").is_err());
assert!(s_validate(b"2001-01-32").is_err());
assert!(s_validate(b"2001-03-32").is_err());
assert!(s_validate(b"2001-04-31").is_err());
assert!(s_validate(b"2001-05-32").is_err());
assert!(s_validate(b"2001-06-31").is_err());
assert!(s_validate(b"2001-07-32").is_err());
assert!(s_validate(b"2001-08-32").is_err());
assert!(s_validate(b"2001-09-31").is_err());
assert!(s_validate(b"2001-10-32").is_err());
assert!(s_validate(b"2001-11-31").is_err());
assert!(s_validate(b"2001-12-32").is_err());
assert!(s_validate(b"2001-02-28").is_ok());
assert!(s_validate(b"2001-02-29").is_err());
assert!(s_validate(b"2000-02-28").is_ok());
assert!(s_validate(b"2000-02-29").is_ok());
assert!(s_validate(b"2000-02-30").is_err());
assert!(s_validate(b"2004-02-28").is_ok());
assert!(s_validate(b"2004-02-29").is_ok());
assert!(s_validate(b"2004-02-30").is_err());
assert!(s_validate(b"2100-02-28").is_ok());
assert!(s_validate(b"2100-02-29").is_err());
assert!(s_validate(b"2001+01-01").is_err());
assert!(s_validate(b"2001-01+01").is_err());
assert!(s_validate(b"01-01-01").is_err());
assert!(s_validate(b"+001-01-01").is_err());
assert!(s_validate(b"-001-01-01").is_err());
}
#[cfg(feature = "serde")]
#[test]
fn ser_de_str() {
let raw: &'static str = "2001-12-31";
assert_tokens(
&Ymd8HyphenStr::from_str(raw).unwrap(),
&[Token::BorrowedStr(raw)],
);
}
#[cfg(feature = "serde")]
#[test]
fn ser_de_string() {
let raw: &'static str = "2001-12-31";
assert_tokens(
&Ymd8HyphenString::try_from(raw).unwrap(),
&[Token::Str(raw)],
);
}
#[cfg(feature = "serde")]
#[test]
fn de_bytes_slice() {
let raw: &'static [u8; 10] = b"2001-12-31";
assert_de_tokens(
&Ymd8HyphenStr::from_bytes(raw).unwrap(),
&[Token::BorrowedBytes(raw)],
);
}
#[cfg(feature = "serde")]
#[test]
fn de_bytes() {
let raw: &'static [u8; 10] = b"2001-12-31";
assert_de_tokens(
&Ymd8HyphenString::try_from(&raw[..]).unwrap(),
&[Token::Bytes(raw)],
);
}
}