1use crate::prelude::*;
4use regex::Regex;
5
6#[derive(Debug, Clone, PartialEq)]
8pub enum Duration {
9 Zero,
11 HM { hours: i64, minutes: i64 },
13}
14
15impl Duration {
16 pub fn days(days: i64) -> Self {
18 Self::HM {
19 hours: days * 24,
20 minutes: 0,
21 }
22 }
23 pub fn parse(duration: String) -> Result<Self, Error> {
25 match Self::parse_hm(&duration)
26 .or(Self::parse_hours(&duration).or(Self::parse_hm2(&duration)))
27 {
28 Duration::Zero => Err(Error::DurationFormat(duration)),
29 duration => Ok(duration),
30 }
31 }
32 pub fn or(self, d: Self) -> Self {
34 match self {
35 Self::Zero => d,
36 _ => self,
37 }
38 }
39 fn parse_hm(duration: &str) -> Self {
41 let re = Regex::new(r"^(\d+):(\d{1,2})$").unwrap();
42 if let Some(cap) = re.captures_iter(duration).next() {
43 return Self::HM {
44 hours: cap[1].parse::<i64>().unwrap(),
45 minutes: cap[2].parse::<i64>().unwrap(),
46 };
47 }
48 Self::Zero
49 }
50 fn parse_hours(duration: &str) -> Self {
52 let re = Regex::new(r"^(\d+)[,.](\d{1,2})$").unwrap();
53 if let Some(cap) = re.captures_iter(duration).next() {
54 return Self::HM {
55 hours: cap[1].parse::<i64>().unwrap(),
56 minutes: (format!(".{}", &cap[2]).parse::<f64>().unwrap() * 60f64)
57 as i64,
58 };
59 }
60 let re = Regex::new(r"^[,.](\d{1,2})$").unwrap();
61 if let Some(cap) = re.captures_iter(duration).next() {
62 return Self::HM {
63 hours: 0,
64 minutes: (format!(".{}", &cap[1]).parse::<f64>().unwrap() * 60f64)
65 as i64,
66 };
67 }
68 let re = Regex::new(r"^(\d{1,2})$").unwrap();
69 if let Some(cap) = re.captures_iter(duration).next() {
70 return Self::HM {
71 hours: cap[1].parse::<u32>().unwrap() as i64,
72 minutes: 0,
73 };
74 }
75 Self::Zero
76 }
77 fn parse_hm2(duration: &str) -> Self {
79 let re = Regex::new(r"^(\d{1,2})h(\d{1,2})m$").unwrap();
80 if let Some(cap) = re.captures_iter(duration).next() {
81 return Self::HM {
82 hours: cap[1].parse::<u32>().unwrap() as i64,
83 minutes: cap[2].parse::<u32>().unwrap() as i64,
84 };
85 }
86 let re = Regex::new(r"^(\d{1,2})m$").unwrap();
87 if let Some(cap) = re.captures_iter(duration).next() {
88 return Self::HM {
89 hours: 0,
90 minutes: cap[1].parse::<u32>().unwrap() as i64,
91 };
92 }
93 let re = Regex::new(r"^(\d{1,2})h$").unwrap();
94 if let Some(cap) = re.captures_iter(duration).next() {
95 return Self::HM {
96 hours: cap[1].parse::<u32>().unwrap() as i64,
97 minutes: 0,
98 };
99 }
100 Self::Zero
101 }
102 pub fn num_minutes(&self) -> i64 {
104 match self {
105 Duration::Zero => 0,
106 Duration::HM { hours, minutes } => hours * 60 + minutes,
107 }
108 }
109}
110impl From<Duration> for chrono::Duration {
111 fn from(val: Duration) -> Self {
112 match val {
113 Duration::Zero => chrono::Duration::zero(),
114 Duration::HM { hours, minutes } => {
115 chrono::Duration::hours(hours) + chrono::Duration::minutes(minutes)
116 }
117 }
118 }
119}
120impl std::fmt::Display for Duration {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 match self {
123 Duration::HM { hours, minutes } => {
124 write!(f, "{}", *hours as f64 + *minutes as f64 / 60f64)
125 }
126 _ => write!(f, "0"),
127 }
128 }
129}
130
131#[test]
132fn test_duration() {
133 assert_eq!(
134 Duration::parse("2:30".to_string()).unwrap(),
135 Duration::HM {
136 hours: 2,
137 minutes: 30
138 }
139 );
140 assert_eq!(
141 Duration::parse("2.5".to_string()).unwrap(),
142 Duration::HM {
143 hours: 2,
144 minutes: 30
145 }
146 );
147 assert_eq!(
148 Duration::parse(".25".to_string()).unwrap(),
149 Duration::HM {
150 hours: 0,
151 minutes: 15
152 }
153 );
154 assert_eq!(
155 Duration::parse(".5".to_string()).unwrap(),
156 Duration::HM {
157 hours: 0,
158 minutes: 30
159 }
160 );
161 assert_eq!(
162 Duration::parse("2".to_string()).unwrap(),
163 Duration::HM {
164 hours: 2,
165 minutes: 0
166 }
167 );
168 assert_eq!(
169 Duration::parse("2h30m".to_string()).unwrap(),
170 Duration::HM {
171 hours: 2,
172 minutes: 30
173 }
174 );
175 assert_eq!(
176 Duration::parse("2h".to_string()).unwrap(),
177 Duration::HM {
178 hours: 2,
179 minutes: 0
180 }
181 );
182 assert_eq!(
183 Duration::parse("15m".to_string()).unwrap(),
184 Duration::HM {
185 hours: 0,
186 minutes: 15
187 }
188 );
189}