1use std::error::Error as StdError;
2use std::fmt;
3use std::str::FromStr;
4
5use crate::all::Timestamp;
6
7#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
11pub struct FormattedTimestamp {
12 timestamp: i64,
13 style: Option<FormattedTimestampStyle>,
14}
15
16#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, Debug)]
20pub enum FormattedTimestampStyle {
21 ShortTime,
23 LongTime,
25 ShortDate,
27 LongDate,
29 #[default]
31 ShortDateTime,
32 LongDateTime,
34 RelativeTime,
37}
38
39impl FormattedTimestamp {
40 #[must_use]
43 pub fn new(timestamp: Timestamp, style: Option<FormattedTimestampStyle>) -> Self {
44 Self {
45 timestamp: timestamp.unix_timestamp(),
46 style,
47 }
48 }
49
50 #[must_use]
53 pub fn now() -> Self {
54 Self {
55 timestamp: Timestamp::now().unix_timestamp(),
56 style: None,
57 }
58 }
59
60 #[must_use]
62 pub fn timestamp(&self) -> i64 {
63 self.timestamp
64 }
65
66 #[must_use]
68 pub fn style(&self) -> Option<FormattedTimestampStyle> {
69 self.style
70 }
71}
72
73impl From<Timestamp> for FormattedTimestamp {
74 fn from(timestamp: Timestamp) -> Self {
77 Self {
78 timestamp: timestamp.unix_timestamp(),
79 style: None,
80 }
81 }
82}
83
84impl fmt::Display for FormattedTimestamp {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 match self.style {
87 Some(style) => write!(f, "<t:{}:{}>", self.timestamp, style),
88 None => write!(f, "<t:{}>", self.timestamp),
89 }
90 }
91}
92
93impl fmt::Display for FormattedTimestampStyle {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 let style = match self {
96 Self::ShortTime => "t",
97 Self::LongTime => "T",
98 Self::ShortDate => "d",
99 Self::LongDate => "D",
100 Self::ShortDateTime => "f",
101 Self::LongDateTime => "F",
102 Self::RelativeTime => "R",
103 };
104 f.write_str(style)
105 }
106}
107
108#[derive(Debug, Clone)]
110#[non_exhaustive]
111pub struct FormattedTimestampParseError {
112 string: String,
113}
114
115impl StdError for FormattedTimestampParseError {}
116
117impl fmt::Display for FormattedTimestampParseError {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 write!(f, "invalid formatted timestamp {:?}", self.string)
120 }
121}
122
123fn parse_formatted_timestamp(s: &str) -> Option<FormattedTimestamp> {
124 let inner = s.strip_prefix("<t:")?.strip_suffix('>')?;
126
127 Some(match inner.split_once(':') {
128 Some((timestamp, style)) => FormattedTimestamp {
129 timestamp: timestamp.parse().ok()?,
130 style: Some(style.parse().ok()?),
131 },
132 None => FormattedTimestamp {
133 timestamp: inner.parse().ok()?,
134 style: None,
135 },
136 })
137}
138
139impl FromStr for FormattedTimestamp {
140 type Err = FormattedTimestampParseError;
141 fn from_str(s: &str) -> Result<Self, Self::Err> {
142 match parse_formatted_timestamp(s) {
143 Some(x) => Ok(x),
144 None => Err(FormattedTimestampParseError {
145 string: s.into(),
146 }),
147 }
148 }
149}
150
151impl FromStr for FormattedTimestampStyle {
152 type Err = FormattedTimestampParseError;
153 fn from_str(s: &str) -> Result<Self, Self::Err> {
154 match s {
155 "t" => Ok(Self::ShortTime),
156 "T" => Ok(Self::LongTime),
157 "d" => Ok(Self::ShortDate),
158 "D" => Ok(Self::LongDate),
159 "f" => Ok(Self::ShortDateTime),
160 "F" => Ok(Self::LongDateTime),
161 "R" => Ok(Self::RelativeTime),
162 _ => Err(FormattedTimestampParseError {
163 string: s.into(),
164 }),
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_message_time() {
175 let timestamp = Timestamp::now();
176
177 let time = FormattedTimestamp::new(timestamp, Some(FormattedTimestampStyle::ShortDateTime));
178
179 let time_str = time.to_string();
180
181 assert_eq!(
182 time_str,
183 format!(
184 "<t:{}:{}>",
185 timestamp.unix_timestamp(),
186 FormattedTimestampStyle::ShortDateTime
187 )
188 );
189
190 let unstyled = FormattedTimestamp::new(timestamp, None);
191
192 let unstyled_str = unstyled.to_string();
193
194 assert_eq!(unstyled_str, format!("<t:{}>", timestamp.unix_timestamp()));
195 }
196
197 #[test]
198 fn test_message_time_style() {
199 assert_eq!(FormattedTimestampStyle::ShortTime.to_string(), "t");
200 assert_eq!(FormattedTimestampStyle::LongTime.to_string(), "T");
201 assert_eq!(FormattedTimestampStyle::ShortDate.to_string(), "d");
202 assert_eq!(FormattedTimestampStyle::LongDate.to_string(), "D");
203 assert_eq!(FormattedTimestampStyle::ShortDateTime.to_string(), "f");
204 assert_eq!(FormattedTimestampStyle::LongDateTime.to_string(), "F");
205 assert_eq!(FormattedTimestampStyle::RelativeTime.to_string(), "R");
206 }
207
208 #[test]
209 fn test_message_time_parse() {
210 let timestamp = Timestamp::now();
211
212 let time = FormattedTimestamp::new(timestamp, Some(FormattedTimestampStyle::ShortDateTime));
213
214 let time_str = format!(
215 "<t:{}:{}>",
216 timestamp.unix_timestamp(),
217 FormattedTimestampStyle::ShortDateTime
218 );
219
220 let time_parsed = time_str.parse::<FormattedTimestamp>().unwrap();
221
222 assert_eq!(time, time_parsed);
223
224 let unstyled = FormattedTimestamp::new(timestamp, None);
225
226 let unstyled_str = format!("<t:{}>", timestamp.unix_timestamp());
227
228 let unstyled_parsed = unstyled_str.parse::<FormattedTimestamp>().unwrap();
229
230 assert_eq!(unstyled, unstyled_parsed);
231 }
232
233 #[test]
234 fn test_message_time_style_parse() {
235 assert!(matches!("t".parse(), Ok(FormattedTimestampStyle::ShortTime)));
236 assert!(matches!("T".parse(), Ok(FormattedTimestampStyle::LongTime)));
237 assert!(matches!("d".parse(), Ok(FormattedTimestampStyle::ShortDate)));
238 assert!(matches!("D".parse(), Ok(FormattedTimestampStyle::LongDate)));
239 assert!(matches!("f".parse(), Ok(FormattedTimestampStyle::ShortDateTime)));
240 assert!(matches!("F".parse(), Ok(FormattedTimestampStyle::LongDateTime)));
241 assert!(matches!("R".parse(), Ok(FormattedTimestampStyle::RelativeTime)));
242 }
243}