use crate::TimezoneOffset;
use core::fmt;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct GMonthDay {
month: u8,
day: u8,
timezone: Option<TimezoneOffset>,
}
impl GMonthDay {
pub const fn new(month: u8, day: u8) -> Option<Self> {
let last_day = match month {
2 => 29,
4 | 6 | 9 | 11 => 30,
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
_ => return None,
};
if day >= 1 && day <= last_day {
Some(Self {
month,
day,
timezone: None,
})
} else {
None
}
}
pub const fn month(self) -> u8 {
self.month
}
pub const fn day(self) -> u8 {
self.day
}
pub const fn with_timezone(mut self, timezone: Option<TimezoneOffset>) -> Self {
self.timezone = timezone;
self
}
pub const fn timezone(self) -> Option<TimezoneOffset> {
self.timezone
}
}
impl fmt::Display for GMonthDay {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "--{:02}-{:02}", self.month, self.day)?;
if let Some(offset) = self.timezone {
offset.fmt(f)?;
}
Ok(())
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for GMonthDay {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(serde::Deserialize)]
#[serde(rename = "GMonthDay")]
struct Fields {
month: u8,
day: u8,
timezone: Option<TimezoneOffset>,
}
let fields = Fields::deserialize(deserializer)?;
Self::new(fields.month, fields.day)
.map(|value| value.with_timezone(fields.timezone))
.ok_or_else(|| serde::de::Error::custom("invalid XSD month/day combination"))
}
}
#[cfg(feature = "borsh")]
impl borsh::BorshSerialize for GMonthDay {
fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> Result<(), borsh::io::Error> {
borsh::BorshSerialize::serialize(&(1u8, self.month, self.day, self.timezone), writer)
}
}
#[cfg(feature = "borsh")]
impl borsh::BorshDeserialize for GMonthDay {
fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> Result<Self, borsh::io::Error> {
use borsh::io::{Error, ErrorKind};
if u8::deserialize_reader(reader)? != 1 {
return Err(Error::new(
ErrorKind::InvalidData,
"unsupported XSD gMonthDay encoding version",
));
}
let (month, day, timezone) =
<(u8, u8, Option<TimezoneOffset>)>::deserialize_reader(reader)?;
Self::new(month, day)
.map(|value| value.with_timezone(timezone))
.ok_or_else(|| Error::new(ErrorKind::InvalidData, "invalid XSD month/day combination"))
}
}