1use std::{
2 fmt::{self, Write},
3 str::FromStr,
4};
5
6use ::nom::{Finish, Parser};
7
8pub mod nom;
9#[cfg(feature = "serde")]
10pub mod serde;
11
12#[derive(Debug, Clone, PartialEq, thiserror::Error)]
13pub enum GoDurationParseError {
14 #[error("time: invalid duration")]
15 InvalidDuration,
16 #[error("time: missing unit in duration")]
17 MissingUnit,
18 #[error("time: unknown unit \"{0}\" in duration")]
19 UnknownUnit(String),
20}
21
22#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
23pub struct GoDuration(
24 pub i64,
26);
27
28impl GoDuration {
29 pub const ZERO: Self = GoDuration(0);
30 pub const MIN: Self = GoDuration(i64::MIN);
31 pub const MAX: Self = GoDuration(i64::MAX);
32
33 #[inline]
34 #[must_use]
35 pub fn nanoseconds(&self) -> i64 {
36 self.0
37 }
38
39 #[must_use]
40 pub fn abs(&self) -> Self {
41 Self(0i64.saturating_add_unsigned(self.0.unsigned_abs()))
42 }
43}
44
45impl TryFrom<&str> for GoDuration {
46 type Error = GoDurationParseError;
47
48 fn try_from(value: &str) -> Result<Self, Self::Error> {
49 value.parse()
50 }
51}
52
53impl From<i64> for GoDuration {
54 fn from(nanoseconds: i64) -> Self {
55 Self(nanoseconds)
56 }
57}
58
59impl fmt::Display for GoDuration {
60 fn fmt(&self, f: &mut std::fmt::Formatter) -> fmt::Result {
61 let nanos = self.0;
62 if nanos.is_negative() {
63 f.write_char('-')?;
64 }
65 let mut nanos = nanos.unsigned_abs();
66 if nanos >= NANOS_PER_HOUR {
67 f.write_fmt(format_args!("{}h", nanos / NANOS_PER_HOUR))?;
68 nanos %= NANOS_PER_HOUR;
69 f.write_fmt(format_args!("{}m", nanos / NANOS_PER_MINUTE))?;
70 nanos %= NANOS_PER_MINUTE;
71 return f.write_fmt(format_args!("{}s", nanos as f64 / NANOS_PER_SECOND as f64));
72 }
73 if nanos >= NANOS_PER_MINUTE {
74 f.write_fmt(format_args!("{}m", nanos / NANOS_PER_MINUTE))?;
75 nanos %= NANOS_PER_MINUTE;
76 return f.write_fmt(format_args!("{}s", nanos as f64 / NANOS_PER_SECOND as f64));
77 }
78 if nanos >= NANOS_PER_SECOND {
79 return f.write_fmt(format_args!("{}s", nanos as f64 / NANOS_PER_SECOND as f64));
80 }
81 if nanos >= NANOS_PER_MILLISECOND {
82 return f.write_fmt(format_args!(
83 "{}ms",
84 nanos as f64 / NANOS_PER_MILLISECOND as f64
85 ));
86 }
87 if nanos >= NANOS_PER_MICROSECOND {
88 return f.write_fmt(format_args!(
89 "{}\u{00B5}s",
90 nanos as f64 / NANOS_PER_MICROSECOND as f64
91 ));
92 }
93 if nanos > 0 {
94 return f.write_fmt(format_args!("{nanos}ns"));
95 }
96 f.write_str("0s")
97 }
98}
99
100impl FromStr for GoDuration {
101 type Err = GoDurationParseError;
102
103 fn from_str(s: &str) -> Result<Self, Self::Err> {
104 parse_go_duration(s)
105 }
106}
107
108pub(crate) const NANOS_PER_MICROSECOND: u64 = 1_000;
109pub(crate) const NANOS_PER_MILLISECOND: u64 = 1_000_000;
110pub(crate) const NANOS_PER_SECOND: u64 = 1_000_000_000;
111pub(crate) const NANOS_PER_MINUTE: u64 = NANOS_PER_SECOND * 60;
112pub(crate) const NANOS_PER_HOUR: u64 = NANOS_PER_MINUTE * 60;
113
114pub fn parse_go_duration(input: &str) -> Result<GoDuration, GoDurationParseError> {
115 nom::go_duration.parse(input).finish().map(|(_, dur)| dur)
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_parse_valid() {
124 let cases = [
125 ("0s", 0),
126 ("+42ns", 42),
127 (".1us", 100),
128 (".1ns0.9ns", 0),
129 ("1ns9ns", 10),
130 ("1.ns", 1),
131 ("2ns", 2),
132 ("2us", 2000),
133 ("-2us", -2000),
134 ("0.2us", 200),
135 ("0.0000000000003h", 1),
136 ("1ns", 1),
137 ("1us", 1_000),
138 ("1\u{00B5}s", 1_000),
139 ("1\u{03BC}s", 1_000),
140 ("1ms", 1_000_000),
141 ("1s", 1_000_000_000),
142 ("1m", 60_000_000_000),
143 ("1h", 3_600_000_000_000),
144 ("9223372036854775807ns", 9_223_372_036_854_775_807),
145 ("-9223372036854775808ns", -9_223_372_036_854_775_808),
146 ];
147
148 for (input, expected) in cases {
149 let output = parse_go_duration(input);
150 let output = output.expect(input);
151 assert_eq!(expected, output.0, "{input}");
152 }
153 }
154
155 #[test]
156 fn test_parse_invalid() {
157 let cases = [
158 ("", GoDurationParseError::InvalidDuration),
159 ("0", GoDurationParseError::MissingUnit),
160 (
161 "-1m-30s",
162 GoDurationParseError::UnknownUnit("m-".to_string()),
163 ),
164 ("-2", GoDurationParseError::MissingUnit),
165 ("0z", GoDurationParseError::UnknownUnit("z".to_string())),
166 (
167 "1m-30s",
168 GoDurationParseError::UnknownUnit("m-".to_string()),
169 ),
170 (
171 "1m+30s",
172 GoDurationParseError::UnknownUnit("m+".to_string()),
173 ),
174 (
175 "-1m+30s",
176 GoDurationParseError::UnknownUnit("m+".to_string()),
177 ),
178 (
179 "9223372036854775808ns",
180 GoDurationParseError::InvalidDuration,
181 ),
182 (
183 "-9223372036854775809ns",
184 GoDurationParseError::InvalidDuration,
185 ),
186 ("-", GoDurationParseError::InvalidDuration),
187 ("+", GoDurationParseError::InvalidDuration),
188 (" ", GoDurationParseError::InvalidDuration),
189 ("-1 m", GoDurationParseError::UnknownUnit(" m".to_string())),
190 ("17h ", GoDurationParseError::UnknownUnit("h ".to_string())),
191 (" 42s", GoDurationParseError::InvalidDuration),
192 ];
193
194 for (input, expected) in cases {
195 let output = parse_go_duration(input);
196 assert!(output.is_err(), "{input} {output:?}");
197
198 let output = output.unwrap_err();
199 assert_eq!(output, expected, "{input}");
200 }
201 }
202
203 #[test]
204 fn test_format() {
205 let cases = [
206 (4000 * NANOS_PER_SECOND as i64, "1h6m40s"),
207 (90 * NANOS_PER_MINUTE as i64, "1h30m0s"),
208 (-1, "-1ns"),
209 (0, "0s"),
210 (1, "1ns"),
211 (NANOS_PER_MICROSECOND as i64 - 1, "999ns"),
212 (NANOS_PER_MICROSECOND as i64, "1\u{00B5}s"),
213 (NANOS_PER_MICROSECOND as i64 + 1, "1.001\u{00B5}s"),
214 (NANOS_PER_MILLISECOND as i64 - 1, "999.999\u{00B5}s"),
215 (NANOS_PER_MILLISECOND as i64, "1ms"),
216 (NANOS_PER_MILLISECOND as i64 + 1, "1.000001ms"),
217 (NANOS_PER_SECOND as i64 - 1, "999.999999ms"),
218 (NANOS_PER_SECOND as i64, "1s"),
219 (NANOS_PER_SECOND as i64 + 1, "1.000000001s"),
220 (NANOS_PER_MINUTE as i64 - 1, "59.999999999s"),
221 (NANOS_PER_MINUTE as i64, "1m0s"),
222 (NANOS_PER_MINUTE as i64 + 1, "1m0.000000001s"),
223 (NANOS_PER_HOUR as i64 - 1, "59m59.999999999s"),
224 (NANOS_PER_HOUR as i64, "1h0m0s"),
225 (NANOS_PER_HOUR as i64 + 1, "1h0m0.000000001s"),
226 (i64::MIN, "-2562047h47m16.854775808s"),
227 (i64::MAX, "2562047h47m16.854775807s"),
228 ];
229 for (input, expected) in cases {
230 let output = GoDuration(input).to_string();
231 assert_eq!(expected, output, "{input}");
232 }
233 }
234
235 #[test]
236 fn test_try_from_trait() {
237 let output = GoDuration::try_from("42ns");
238 assert!(output.is_ok());
239 assert_eq!(GoDuration(42), output.unwrap());
240 }
241
242 #[test]
243 fn test_from_trait() {
244 let output = GoDuration::from(-23);
245 assert_eq!(GoDuration(-23), output);
246 }
247
248 #[test]
249 fn test_duration_abs() {
250 let cases = [
251 (i64::MIN, i64::MAX),
252 (i64::MAX, i64::MAX),
253 (0, 0),
254 (-42, 42),
255 ];
256
257 for (input, expected) in cases {
258 let output = GoDuration(input).abs();
259 assert_eq!(expected, output.nanoseconds(), "{input}");
260 }
261 }
262}