use std::error::Error as StdError;
use std::fmt;
use std::str::FromStr;
use crate::all::Timestamp;
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
pub struct FormattedTimestamp {
timestamp: i64,
style: Option<FormattedTimestampStyle>,
}
#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum FormattedTimestampStyle {
ShortTime,
LongTime,
ShortDate,
LongDate,
#[default]
ShortDateTime,
LongDateTime,
RelativeTime,
}
impl FormattedTimestamp {
#[must_use]
pub fn new(timestamp: Timestamp, style: Option<FormattedTimestampStyle>) -> Self {
Self {
timestamp: timestamp.unix_timestamp(),
style,
}
}
#[must_use]
pub fn now() -> Self {
Self {
timestamp: Timestamp::now().unix_timestamp(),
style: None,
}
}
#[must_use]
pub fn timestamp(&self) -> i64 {
self.timestamp
}
#[must_use]
pub fn style(&self) -> Option<FormattedTimestampStyle> {
self.style
}
}
impl From<Timestamp> for FormattedTimestamp {
fn from(timestamp: Timestamp) -> Self {
Self {
timestamp: timestamp.unix_timestamp(),
style: None,
}
}
}
impl fmt::Display for FormattedTimestamp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.style {
Some(style) => write!(f, "<t:{}:{}>", self.timestamp, style),
None => write!(f, "<t:{}>", self.timestamp),
}
}
}
impl fmt::Display for FormattedTimestampStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let style = match self {
Self::ShortTime => "t",
Self::LongTime => "T",
Self::ShortDate => "d",
Self::LongDate => "D",
Self::ShortDateTime => "f",
Self::LongDateTime => "F",
Self::RelativeTime => "R",
};
f.write_str(style)
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct FormattedTimestampParseError {
string: String,
}
impl StdError for FormattedTimestampParseError {}
impl fmt::Display for FormattedTimestampParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid formatted timestamp {:?}", self.string)
}
}
fn parse_formatted_timestamp(s: &str) -> Option<FormattedTimestamp> {
let inner = s.strip_prefix("<t:")?.strip_suffix('>')?;
Some(match inner.split_once(':') {
Some((timestamp, style)) => FormattedTimestamp {
timestamp: timestamp.parse().ok()?,
style: Some(style.parse().ok()?),
},
None => FormattedTimestamp {
timestamp: inner.parse().ok()?,
style: None,
},
})
}
impl FromStr for FormattedTimestamp {
type Err = FormattedTimestampParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match parse_formatted_timestamp(s) {
Some(x) => Ok(x),
None => Err(FormattedTimestampParseError {
string: s.into(),
}),
}
}
}
impl FromStr for FormattedTimestampStyle {
type Err = FormattedTimestampParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"t" => Ok(Self::ShortTime),
"T" => Ok(Self::LongTime),
"d" => Ok(Self::ShortDate),
"D" => Ok(Self::LongDate),
"f" => Ok(Self::ShortDateTime),
"F" => Ok(Self::LongDateTime),
"R" => Ok(Self::RelativeTime),
_ => Err(FormattedTimestampParseError {
string: s.into(),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_message_time() {
let timestamp = Timestamp::now();
let time = FormattedTimestamp::new(timestamp, Some(FormattedTimestampStyle::ShortDateTime));
let time_str = time.to_string();
assert_eq!(
time_str,
format!(
"<t:{}:{}>",
timestamp.unix_timestamp(),
FormattedTimestampStyle::ShortDateTime
)
);
let unstyled = FormattedTimestamp::new(timestamp, None);
let unstyled_str = unstyled.to_string();
assert_eq!(unstyled_str, format!("<t:{}>", timestamp.unix_timestamp()));
}
#[test]
fn test_message_time_style() {
assert_eq!(FormattedTimestampStyle::ShortTime.to_string(), "t");
assert_eq!(FormattedTimestampStyle::LongTime.to_string(), "T");
assert_eq!(FormattedTimestampStyle::ShortDate.to_string(), "d");
assert_eq!(FormattedTimestampStyle::LongDate.to_string(), "D");
assert_eq!(FormattedTimestampStyle::ShortDateTime.to_string(), "f");
assert_eq!(FormattedTimestampStyle::LongDateTime.to_string(), "F");
assert_eq!(FormattedTimestampStyle::RelativeTime.to_string(), "R");
}
#[test]
fn test_message_time_parse() {
let timestamp = Timestamp::now();
let time = FormattedTimestamp::new(timestamp, Some(FormattedTimestampStyle::ShortDateTime));
let time_str = format!(
"<t:{}:{}>",
timestamp.unix_timestamp(),
FormattedTimestampStyle::ShortDateTime
);
let time_parsed = time_str.parse::<FormattedTimestamp>().unwrap();
assert_eq!(time, time_parsed);
let unstyled = FormattedTimestamp::new(timestamp, None);
let unstyled_str = format!("<t:{}>", timestamp.unix_timestamp());
let unstyled_parsed = unstyled_str.parse::<FormattedTimestamp>().unwrap();
assert_eq!(unstyled, unstyled_parsed);
}
#[test]
fn test_message_time_style_parse() {
assert!(matches!("t".parse(), Ok(FormattedTimestampStyle::ShortTime)));
assert!(matches!("T".parse(), Ok(FormattedTimestampStyle::LongTime)));
assert!(matches!("d".parse(), Ok(FormattedTimestampStyle::ShortDate)));
assert!(matches!("D".parse(), Ok(FormattedTimestampStyle::LongDate)));
assert!(matches!("f".parse(), Ok(FormattedTimestampStyle::ShortDateTime)));
assert!(matches!("F".parse(), Ok(FormattedTimestampStyle::LongDateTime)));
assert!(matches!("R".parse(), Ok(FormattedTimestampStyle::RelativeTime)));
}
}