1use eyre::{Result, WrapErr, bail, eyre};
2use serde::{Deserialize, Serialize, de};
3
4#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
6pub struct Timelike(pub u32);
7impl Timelike {
8 pub fn inner(&self) -> u32 {
9 self.0
10 }
11}
12
13impl std::fmt::Display for Timelike {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 match self.0 {
16 0..=59 => write!(f, "{:02}", self.0),
17 60..=3599 => write!(f, "{:02}:{:02}", self.0 / 60, self.0 % 60),
18 _ => write!(f, "{}:{:02}:{:02}", self.0 / 3600, (self.0 % 3600) / 60, self.0 % 60),
19 }
20 }
21}
22impl AsRef<u32> for Timelike {
23 fn as_ref(&self) -> &u32 {
24 &self.0
25 }
26}
27impl std::ops::Deref for Timelike {
28 type Target = u32;
29
30 fn deref(&self) -> &Self::Target {
31 &self.0
32 }
33}
34
35#[derive(Deserialize)]
36#[serde(untagged)]
37enum TimelikeHelper {
38 String(String),
39 Number(u32),
40}
41
42impl Serialize for Timelike {
43 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
44 where
45 S: serde::Serializer, {
46 serializer.serialize_str(&self.to_string())
47 }
48}
49
50impl<'de> Deserialize<'de> for Timelike {
51 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
52 where
53 D: de::Deserializer<'de>, {
54 let helper = TimelikeHelper::deserialize(deserializer)?;
55 match helper {
56 TimelikeHelper::String(time) => time_to_units(&time).map_err(|e| de::Error::custom(e.to_string())).map(Timelike),
57 TimelikeHelper::Number(units) => Ok(Timelike(units)),
58 }
59 }
60}
61
62fn time_to_units(time: &str) -> Result<u32> {
63 if !time.contains(':') {
65 return time.parse().wrap_err_with(|| eyre!("Invalid time format: Could not parse '{time}' as seconds"));
66 }
67
68 let mut split = time.split(':');
69
70 let first = split
71 .next()
72 .ok_or_else(|| eyre!("Invalid time format: Expected one of %H:%M, %H:%M:%S, or %M:%S, got '{time}'"))?
73 .parse::<u32>()
74 .wrap_err_with(|| eyre!("Invalid time format: Expected one of %H:%M, %H:%M:%S, or %M:%S, got '{time}'"))?;
75
76 let second = split
77 .next()
78 .ok_or_else(|| eyre!("Invalid time format: Expected one of %H:%M, %H:%M:%S, or %M:%S, got '{time}'"))?
79 .parse::<u32>()
80 .wrap_err_with(|| eyre!("Invalid time format: Expected one of %H:%M, %H:%M:%S, or %M:%S, got '{time}'"))?;
81
82 let units = match split.next() {
83 Some(third) => {
84 let third = third
85 .parse::<u32>()
86 .wrap_err_with(|| eyre!("Invalid time format: Expected one of %H:%M, %H:%M:%S, or %M:%S, got '{time}'"))?;
87 first * 3600 + second * 60 + third
88 }
89 None => first * 60 + second,
90 };
91
92 if split.next().is_some() {
93 bail!("Invalid time format: Expected one of %H:%M, %H:%M:%S, or %M:%S, got '{time}'");
94 }
95
96 Ok(units)
97}
98
99#[cfg(test)]
100mod tests {
101 use serde_json::json;
102
103 use super::*;
104
105 #[test]
106 fn test_time_de() {
107 assert_eq!(serde_json::from_str::<Timelike>(r#""12:34""#).unwrap().inner(), 754);
108 assert_eq!(serde_json::from_str::<Timelike>(r#""12:34:56""#).unwrap().inner(), 45296);
109 assert_eq!(serde_json::from_str::<Timelike>(r#""34:56""#).unwrap().inner(), 2096);
110 assert_eq!(serde_json::from_str::<Timelike>("754").unwrap().inner(), 754);
111 assert_eq!(serde_json::from_str::<Timelike>(r#""34""#).unwrap().inner(), 34);
112 assert!(serde_json::from_str::<Timelike>(r#""12:34:56:78""#).is_err());
113 }
114
115 #[test]
116 fn test_time_ser() {
117 assert_eq!(Timelike(30).to_string(), "30");
118 assert_eq!(Timelike(2096).to_string(), "34:56");
119 assert_eq!(Timelike(3600).to_string(), "1:00:00");
120 assert_eq!(&json!(Timelike(0)).to_string(), "\"00\"");
121 assert_eq!(&json!(Timelike(45296)).to_string(), "\"12:34:56\"");
122 }
123}