1#[derive(Default, PartialEq, Clone, Debug)]
3pub struct Duration(std::time::Duration);
4
5impl Duration {
6 const NANOS_PER_MILLI: u32 = 1_000_000;
7 const SECS_PER_HOURS: u64 = Self::SECS_PER_MINUTE * 60;
8 const SECS_PER_MINUTE: u64 = 60;
9
10 pub fn new(hours: u64, minutes: u64, secs: u64, milli_secs: u32) -> Self {
11 let secs = hours * Self::SECS_PER_HOURS + minutes * Self::SECS_PER_MINUTE + secs;
12 let nanos = milli_secs * Self::NANOS_PER_MILLI;
13 Self(std::time::Duration::new(secs, nanos))
14 }
15}
16
17impl From<std::time::Duration> for Duration {
18 fn from(value: std::time::Duration) -> Self {
19 Self(value)
20 }
21}
22
23impl From<Duration> for std::time::Duration {
24 fn from(value: Duration) -> Self {
25 value.0
26 }
27}
28
29impl std::fmt::Display for Duration {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 let secs = self.0.as_secs();
32 let millis = self.0.subsec_millis();
33
34 let hh = secs / Self::SECS_PER_HOURS;
35 let mm = (secs % Self::SECS_PER_HOURS) / Self::SECS_PER_MINUTE;
36 let ss = secs % Self::SECS_PER_MINUTE;
37
38 write!(f, "{hh:02}:{mm:02}:{ss:02}")?;
39 if millis > 0 {
40 write!(f, ".{millis:03}")?;
41 }
42 Ok(())
43 }
44}
45
46impl std::str::FromStr for Duration {
47 type Err = crate::VastParseError;
48
49 fn from_str(s: &str) -> Result<Self, Self::Err> {
50 macro_rules! error {
51 () => {
52 crate::VastParseError::new(format!("duration parse error: '{s}'"))
53 };
54 }
55 macro_rules! parse {
56 ($var:ident, $ty:ty) => {
57 $var.parse::<$ty>().map_err(|_| error!())
58 };
59 }
60
61 let s = s.trim();
63 if s.is_empty() {
64 return Ok(Duration::new(0, 0, 0, 0));
65 }
66
67 let mut iter = s.split(':');
68 match (iter.next(), iter.next(), iter.next(), iter.next()) {
69 (Some(hh), None, None, None) => {
70 let hh = parse!(hh, u64)?;
71 Ok(Duration::new(hh, 0, 0, 0))
72 }
73 (Some(hh), Some(mm), None, None) => {
74 let hh = parse!(hh, u64)?;
75 let mm = parse!(mm, u64)?;
76 Ok(Duration::new(hh, mm, 0, 0))
77 }
78 (Some(hh), Some(mm), Some(ss_ms), None) => {
79 let hh = parse!(hh, u64)?;
80 let mm = parse!(mm, u64)?;
81 Ok(match ss_ms.split_once('.') {
82 Some((ss, ms)) => Duration::new(hh, mm, parse!(ss, u64)?, parse!(ms, u32)?),
83 None => Duration::new(hh, mm, parse!(ss_ms, u64)?, 0),
84 })
85 }
86 _ => Ok(Duration::new(0, 0, 0, 0)),
88 }
89 }
90}
91
92impl hard_xml::XmlWrite for Duration {
93 fn to_writer<W: std::io::Write>(
94 &self,
95 writer: &mut hard_xml::XmlWriter<W>,
96 ) -> hard_xml::XmlResult<()> {
97 if !self.0.is_zero() {
98 writer.write_element_start("Duration")?;
99 writer.write_element_end_open()?;
100 write!(writer.inner, "{self}")?;
101 writer.write_element_end_close("Duration")?;
102 }
103 Ok(())
104 }
105}
106
107impl<'a> hard_xml::XmlRead<'a> for Duration {
108 fn from_reader(reader: &mut hard_xml::XmlReader<'a>) -> hard_xml::XmlResult<Self> {
109 reader.read_till_element_start("Duration")?;
110 let text = reader.read_text("Duration")?;
111 <Duration as std::str::FromStr>::from_str(text.as_ref())
112 .map_err(|e| hard_xml::XmlError::FromStr(e.into()))
113 }
114}
115
116#[cfg(test)]
117#[test]
118fn test_duration_parse_and_format() {
119 use std::str::FromStr;
120
121 macro_rules! parse_and_format {
122 ($str:expr, $dur:expr) => {
123 let got = Duration::from_str($str).unwrap();
124 assert_eq!(got, $dur);
125 assert_eq!(format!("{got}"), $str);
126 };
127 }
128
129 parse_and_format!("11:11:11.111", Duration::new(11, 11, 11, 111));
130 parse_and_format!("12:34:56", Duration::new(12, 34, 56, 0));
131 parse_and_format!("00:00:00", Duration::from_str("").unwrap());
132 parse_and_format!("00:00:00", Duration::from_str("12:34:56:78").unwrap());
133}
134
135crate::declare_test!(
136 test_duration,
137 Duration,
138 r#"<Duration>11:11:11.111</Duration>"#,
139 Duration::new(11, 11, 11, 111)
140);
141
142#[cfg(test)]
145#[derive(hard_xml::XmlWrite, hard_xml::XmlRead, PartialEq, Debug)]
146#[xml(tag = "Xml")]
147struct Xml {
148 #[xml(child = "Duration", default)]
149 dur: Duration,
150}
151
152crate::declare_test!(
153 test_duration_property,
154 Xml,
155 "<Xml><Duration>12:34:56.999</Duration></Xml>",
156 Xml {
157 dur: Duration::new(12, 34, 56, 999)
158 }
159);
160
161crate::declare_test!(
162 test_duration_property_empty,
163 Xml,
164 "<Xml></Xml>",
165 Xml {
166 dur: Default::default()
167 }
168);