use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Error;
use std::fmt::Formatter;
use std::fmt::Result;
use std::fmt::Write;
use crate::Date;
pub struct FormattedDate<'a> {
pub(crate) date: Date,
pub(crate) format: &'a str,
}
impl Debug for FormattedDate<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
Display::fmt(self, f)
}
}
impl Display for FormattedDate<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let d = self.date;
let ymd = self.date.ymd();
let mut flag = false;
let mut padding = Padding::Default;
for c in self.format.chars() {
if flag {
#[rustfmt::skip]
match c {
'0' => { padding = Padding::Zero; continue; },
'-' => { padding = Padding::Suppress; continue; },
'_' => { padding = Padding::Space; continue; },
_ => {},
};
macro_rules! write_padded {
($f:ident, $pad:ident, $level:literal, $e:expr) => {
match $pad {
Padding::Default | Padding::Zero => write!($f, concat!("{:0", $level, "}"), $e),
Padding::Space => write!($f, concat!("{:", $level, "}"), $e),
Padding::Suppress => write!($f, "{}", $e),
}
};
}
flag = false;
match c {
'Y' => write_padded!(f, padding, 4, ymd.0)?,
'C' => write_padded!(f, padding, 2, ymd.0 / 100)?,
'y' => write_padded!(f, padding, 2, ymd.0 % 100)?,
'm' => write_padded!(f, padding, 2, ymd.1)?,
'b' | 'h' => write!(f, "{}", d.month_abbv())?,
'B' => write!(f, "{}", d.month_name())?,
'd' => write_padded!(f, padding, 2, ymd.2)?,
'a' => write!(f, "{}", d.weekday().abbv())?,
'A' => write!(f, "{}", d.weekday())?,
'w' => write!(f, "{}", d.weekday() as u8)?,
'u' => write!(f, "{}", match d.weekday() {
crate::Weekday::Sunday => 7,
_ => self.date.weekday() as u8,
})?,
'j' => write_padded!(f, padding, 3, d.day_of_year())?,
'U' => write_padded!(f, padding, 2, d.week())?,
'D' => write!(f, "{:02}/{:02}/{:02}", ymd.1, ymd.2, ymd.0)?,
'F' => write!(f, "{:04}-{:02}-{:02}", ymd.0, ymd.1, ymd.2)?,
'v' => write!(f, "{:2}-{}-{:04}", d.day(), d.month_abbv(), d.year())?,
't' => f.write_char('\t')?,
'n' => f.write_char('\n')?,
'%' => f.write_char('%')?,
_ => Err(Error)?,
}
} else if c == '%' {
flag = true;
padding = Padding::Default;
} else {
f.write_char(c)?;
}
}
Ok(())
}
}
impl PartialEq<&str> for FormattedDate<'_> {
fn eq(&self, other: &&str) -> bool {
&self.to_string().as_str() == other
}
}
macro_rules! month_str {
($($num:literal => $short:ident ~ $long:ident)*) => {
impl Date {
const fn month_name(&self) -> &'static str {
match self.month() {
$($num => stringify!($long),)*
#[cfg(not(tarpaulin_include))]
_ => panic!("Fictitious month"),
}
}
const fn month_abbv(&self) -> &'static str {
match self.month() {
$($num => stringify!($short),)*
#[cfg(not(tarpaulin_include))]
_ => panic!("Fictitious month"),
}
}
}
}
}
month_str! {
1 => Jan ~ January
2 => Feb ~ February
3 => Mar ~ March
4 => Apr ~ April
5 => May ~ May
6 => Jun ~ June
7 => Jul ~ July
8 => Aug ~ August
9 => Sep ~ September
10 => Oct ~ October
11 => Nov ~ November
12 => Dec ~ December
}
enum Padding {
Default,
Zero,
Space,
Suppress,
}
#[cfg(test)]
mod tests {
use assert2::check;
#[test]
fn test_format() {
let date = date! { 2012-04-21 };
for (fmt_string, date_str) in [
("%Y-%m-%d", "2012-04-21"),
("%F", "2012-04-21"),
("%v", "21-Apr-2012"),
("%B %-d, %Y", "April 21, 2012"),
("%B %-d, %C%y", "April 21, 2012"),
("%A, %B %-d, %Y", "Saturday, April 21, 2012"),
("%d %h %Y", "21 Apr 2012"),
("%a %d %b %Y", "Sat 21 Apr 2012"),
("%m/%d/%y", "04/21/12"),
("year: %Y / day: %j", "year: 2012 / day: 112"),
("%%", "%"),
("%w %u", "6 6"),
("%t %n", "\t \n"),
("%Y week %U", "2012 week 16"),
] {
check!(date.format(fmt_string).to_string() == date_str);
check!(date.format(fmt_string) == date_str);
check!(format!("{:?}", date.format(fmt_string)) == date_str);
}
}
#[test]
fn test_padding() {
let date = date! { 2024-07-04 };
for (fmt_string, date_str) in
[("%Y-%m-%d", "2024-07-04"), ("%B %-d, %Y", "July 4, 2024"), ("%-d-%h-%Y", "4-Jul-2024")]
{
check!(date.format(fmt_string).to_string() == date_str);
check!(date.format(fmt_string) == date_str);
}
}
}