use crate::str::Str;
use crate::macros::{
impl_traits,impl_common,
impl_const,
};
use crate::date::free::{
ok_year,ok,
};
#[allow(unused_imports)]
use crate::date::Date;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct Nichi((u16, u8, u8), Str<{ Nichi::MAX_LEN }>);
impl_traits!(Nichi, (u16, u8, u8));
impl Nichi {
pub const MAX_LEN: usize = 17;
pub const ZERO: Self = Self::UNKNOWN;
pub const UNKNOWN: Self = Self((0, 0, 0), Str::from_static_str("???"));
}
impl Nichi {
impl_common!((u16, u8, u8));
impl_const!();
#[inline]
#[must_use]
pub const fn year(&self) -> u16 {
self.0.0
}
#[inline]
#[must_use]
pub const fn month(&self) -> u8 {
self.0.1
}
#[inline]
#[must_use]
pub const fn day(&self) -> u8 {
self.0.2
}
#[inline]
#[must_use]
pub const fn weekday(&self) -> nichi::Weekday {
#[allow(clippy::cast_possible_wrap)]
nichi::Date::weekday_raw(self.year() as i16, self.month(), self.day())
}
#[inline]
#[must_use]
pub fn from_nichi(nichi: nichi::Date) -> Self {
let (y,m,d) = nichi.inner();
Self::priv_from(y as u16, m, d)
}
#[inline]
pub fn new(year: u16, month: u8, day: u8) -> Result<Self, Self> {
if ok(year, month, day) {
Ok(Self::priv_from(year, month, day))
} else {
Err(Self::UNKNOWN)
}
}
#[inline]
#[must_use]
pub fn new_silent(year: u16, month: u8, day: u8) -> Self {
if ok(year, month, day) {
Self::priv_from(year, month, day)
} else {
Self::UNKNOWN
}
}
#[inline]
fn __new_silent(t: (u16, u8, u8)) -> Self {
Self::new_silent(t.0, t.1, t.2)
}
#[inline]
pub fn from_unix(unix_timestamp: u64) -> Result<Self, Self> {
let nichi = nichi::Date::from_unix(i128::from(unix_timestamp));
let year = nichi.year().inner() as u16;
if ok_year(year) {
Ok(Self::priv_from(
year,
nichi.month().inner(),
nichi.day().inner(),
))
} else {
Err(Self::UNKNOWN)
}
}
#[inline]
#[must_use]
pub fn from_unix_silent(unix_timestamp: u64) -> Self {
match Self::from_unix(unix_timestamp) {
Ok(s) | Err(s) => s,
}
}
#[inline]
#[must_use]
#[allow(clippy::cast_possible_wrap)]
pub const fn as_unix(&self) -> u64 {
nichi::Date::new(
self.year() as i16,
self.month(),
self.day(),
).as_unix() as u64
}
#[inline]
#[allow(clippy::should_implement_trait)] pub fn from_str(string: &str) -> Result<Self, Self> {
Self::priv_from_str(string)
}
#[inline]
#[must_use]
pub fn from_str_silent(string: &str) -> Self {
match Self::priv_from_str(string) {
Ok(s) | Err(s) => s,
}
}
#[inline]
fn priv_from_str(s: &str) -> Result<Self, Self> {
#[allow(clippy::option_if_let_else)]
match nichi::Date::from_str(s) {
Some(nichi) => {
let (y, m, d) = nichi.inner();
Ok(Self::priv_from(y as u16, m, d))
},
None => Err(Self::UNKNOWN),
}
}
#[inline]
#[must_use]
pub const fn is_unknown(&self) -> bool {
matches!(*self, Self::UNKNOWN)
}
}
impl Nichi {
#[inline]
pub(super) fn priv_from(y: u16, m: u8, d: u8) -> Self {
let mut buf = [0_u8; Self::MAX_LEN];
#[allow(clippy::cast_possible_wrap)]
let nichi = nichi::Date::new(y as i16,m,d);
let weekday = nichi.weekday().as_str_short().as_bytes();
buf[0] = weekday[0];
buf[1] = weekday[1];
buf[2] = weekday[2];
buf[3] = b',';
buf[4] = b' ';
let month = nichi.month().as_str_short().as_bytes();
buf[5] = month[0];
buf[6] = month[1];
buf[7] = month[2];
buf[8] = b' ';
let day = nichi.day().as_str_num().as_bytes();
buf[9] = day[0];
let len = if day.len() > 1 {
buf[10] = day[1];
11
} else {
10
};
buf[len] = b',';
buf[len + 1] = b' ';
let mut year = crate::toa::Itoa64::new();
let year = year.format_str(y).as_bytes();
buf[len + 2] = year[0];
buf[len + 3] = year[1];
buf[len + 4] = year[2];
buf[len + 5] = year[3];
let string = unsafe { Str::from_raw(buf, (len + 6) as u8) };
Self((y,m,d), string)
}
}
impl TryFrom<(u16, u8, u8)> for Nichi {
type Error = Self;
#[inline]
fn try_from(value: (u16, u8, u8)) -> Result<Self, Self> {
Self::new(value.0, value.1, value.2)
}
}
impl From<nichi::Date> for Nichi {
fn from(value: nichi::Date) -> Self {
Self::from_nichi(value)
}
}
impl From<crate::date::Date> for Nichi {
fn from(value: crate::date::Date) -> Self {
if value.ok() {
let (y,m,d) = value.inner();
Self::priv_from(y,m,d)
} else {
Self::UNKNOWN
}
}
}
impl From<crate::date::NichiFull> for Nichi {
fn from(value: crate::date::NichiFull) -> Self {
if value.is_unknown() {
Self::UNKNOWN
} else {
let (y,m,d) = value.inner();
Self::priv_from(y,m,d)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const EXPECTED: (u16, u8, u8) = (2020, 12, 25);
const EXPECTED_STR: &str = "Fri, Dec 25, 2020";
#[test]
fn invalid_years() {
assert_eq!(Nichi::from_str_silent("0"), Nichi::UNKNOWN);
assert_eq!(Nichi::from_str_silent("100"), Nichi::UNKNOWN);
assert_eq!(Nichi::from_str_silent("010"), Nichi::UNKNOWN);
assert_eq!(Nichi::from_str_silent("0010"), Nichi::UNKNOWN);
assert_eq!(Nichi::from_str_silent("0100"), Nichi::UNKNOWN);
assert_eq!(Nichi::from_str_silent("999"), Nichi::UNKNOWN);
assert_eq!(Nichi::from_str_silent("0999"), Nichi::UNKNOWN);
}
#[test]
fn invalid_dates() {
assert_eq!(Nichi::from_str_silent("12-25-0100"), Nichi::UNKNOWN);
assert_eq!(Nichi::from_str_silent("01001225") , Nichi::UNKNOWN);
assert_eq!(Nichi::from_str_silent("25-12-0100"), Nichi::UNKNOWN);
assert_eq!(Nichi::from_str_silent("01000"), Nichi::UNKNOWN);
assert_eq!(Nichi::from_str_silent("010000"), Nichi::UNKNOWN);
assert_eq!(Nichi::from_str_silent("0100000"), Nichi::UNKNOWN);
}
#[test]
fn from_str_ymd() {
assert_eq!(Nichi::from_str("2020-12-25").unwrap(), EXPECTED);
assert_eq!(Nichi::from_str("2020-12-25").unwrap(), EXPECTED_STR);
assert_eq!(Nichi::from_str("2020 12 25").unwrap(), EXPECTED);
assert_eq!(Nichi::from_str("2020 12 25").unwrap(), EXPECTED_STR);
assert_eq!(Nichi::from_str("2020/12/25").unwrap(), EXPECTED);
assert_eq!(Nichi::from_str("2020/12/25").unwrap(), EXPECTED_STR);
assert_eq!(Nichi::from_str("2020.12.25").unwrap(), EXPECTED);
assert_eq!(Nichi::from_str("2020.12.25").unwrap(), EXPECTED_STR);
assert_eq!(Nichi::from_str("2020_12_25").unwrap(), EXPECTED);
assert_eq!(Nichi::from_str("2020_12_25").unwrap(), EXPECTED_STR);
}
#[test]
#[cfg(feature = "serde")]
fn serde() {
let this: Nichi = Nichi::try_from((2024, 1, 1)).unwrap();
let json = serde_json::to_string(&this).unwrap();
assert_eq!(json, r#"[[2024,1,1],"Mon, Jan 1, 2024"]"#);
let this: Nichi = serde_json::from_str(&json).unwrap();
assert_eq!(this, (2024, 1, 1));
assert_eq!(this, "Mon, Jan 1, 2024");
assert!(serde_json::from_str::<Nichi>(&"---").is_err());
let json = serde_json::to_string(&Nichi::UNKNOWN).unwrap();
assert_eq!(json, r#"[[0,0,0],"???"]"#);
assert!(serde_json::from_str::<Nichi>(&json).unwrap().is_unknown());
}
#[test]
#[cfg(feature = "bincode")]
fn bincode() {
let this: Nichi = Nichi::try_from((2024, 1, 1)).unwrap();
let config = bincode::config::standard();
let bytes = bincode::encode_to_vec(&this, config).unwrap();
let this: Nichi = bincode::decode_from_slice(&bytes, config).unwrap().0;
assert_eq!(this, (2024, 1, 1));
assert_eq!(this, "Mon, Jan 1, 2024");
let bytes = bincode::encode_to_vec(&Nichi::UNKNOWN, config).unwrap();
let this: Nichi = bincode::decode_from_slice(&bytes, config).unwrap().0;
assert!(this.is_unknown());
}
#[test]
#[cfg(feature = "borsh")]
fn borsh() {
let this: Nichi = Nichi::try_from((2024, 1, 1)).unwrap();
let bytes = borsh::to_vec(&this).unwrap();
let this: Nichi = borsh::from_slice(&bytes).unwrap();
assert_eq!(this, (2024, 1, 1));
assert_eq!(this, "Mon, Jan 1, 2024");
assert!(borsh::from_slice::<Nichi>(b"bad .-;[]124/ bytes").is_err());
let bytes = borsh::to_vec(&Nichi::UNKNOWN).unwrap();
let this: Nichi = borsh::from_slice(&bytes).unwrap();
assert!(this.is_unknown());
}
}