1 | use core::fmt; |
2 | |
3 | #[cfg (feature = "rkyv" )] |
4 | use rkyv::{Archive, Deserialize, Serialize}; |
5 | |
6 | use crate::OutOfRange; |
7 | |
8 | /// The day of week. |
9 | /// |
10 | /// The order of the days of week depends on the context. |
11 | /// (This is why this type does *not* implement `PartialOrd` or `Ord` traits.) |
12 | /// One should prefer `*_from_monday` or `*_from_sunday` methods to get the correct result. |
13 | /// |
14 | /// # Example |
15 | /// ``` |
16 | /// use chrono::Weekday; |
17 | /// |
18 | /// let monday = "Monday" .parse::<Weekday>().unwrap(); |
19 | /// assert_eq!(monday, Weekday::Mon); |
20 | /// |
21 | /// let sunday = Weekday::try_from(6).unwrap(); |
22 | /// assert_eq!(sunday, Weekday::Sun); |
23 | /// |
24 | /// assert_eq!(sunday.num_days_from_monday(), 6); // starts counting with Monday = 0 |
25 | /// assert_eq!(sunday.number_from_monday(), 7); // starts counting with Monday = 1 |
26 | /// assert_eq!(sunday.num_days_from_sunday(), 0); // starts counting with Sunday = 0 |
27 | /// assert_eq!(sunday.number_from_sunday(), 1); // starts counting with Sunday = 1 |
28 | /// |
29 | /// assert_eq!(sunday.succ(), monday); |
30 | /// assert_eq!(sunday.pred(), Weekday::Sat); |
31 | /// ``` |
32 | #[derive (PartialEq, Eq, Copy, Clone, Debug, Hash)] |
33 | #[cfg_attr (feature = "rustc-serialize" , derive(RustcEncodable, RustcDecodable))] |
34 | #[cfg_attr (feature = "rkyv" , derive(Archive, Deserialize, Serialize))] |
35 | #[cfg_attr (feature = "arbitrary" , derive(arbitrary::Arbitrary))] |
36 | pub enum Weekday { |
37 | /// Monday. |
38 | Mon = 0, |
39 | /// Tuesday. |
40 | Tue = 1, |
41 | /// Wednesday. |
42 | Wed = 2, |
43 | /// Thursday. |
44 | Thu = 3, |
45 | /// Friday. |
46 | Fri = 4, |
47 | /// Saturday. |
48 | Sat = 5, |
49 | /// Sunday. |
50 | Sun = 6, |
51 | } |
52 | |
53 | impl Weekday { |
54 | /// The next day in the week. |
55 | /// |
56 | /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` |
57 | /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
58 | /// `w.succ()`: | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` | `Mon` |
59 | #[inline ] |
60 | #[must_use ] |
61 | pub const fn succ(&self) -> Weekday { |
62 | match *self { |
63 | Weekday::Mon => Weekday::Tue, |
64 | Weekday::Tue => Weekday::Wed, |
65 | Weekday::Wed => Weekday::Thu, |
66 | Weekday::Thu => Weekday::Fri, |
67 | Weekday::Fri => Weekday::Sat, |
68 | Weekday::Sat => Weekday::Sun, |
69 | Weekday::Sun => Weekday::Mon, |
70 | } |
71 | } |
72 | |
73 | /// The previous day in the week. |
74 | /// |
75 | /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` |
76 | /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
77 | /// `w.pred()`: | `Sun` | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` |
78 | #[inline ] |
79 | #[must_use ] |
80 | pub const fn pred(&self) -> Weekday { |
81 | match *self { |
82 | Weekday::Mon => Weekday::Sun, |
83 | Weekday::Tue => Weekday::Mon, |
84 | Weekday::Wed => Weekday::Tue, |
85 | Weekday::Thu => Weekday::Wed, |
86 | Weekday::Fri => Weekday::Thu, |
87 | Weekday::Sat => Weekday::Fri, |
88 | Weekday::Sun => Weekday::Sat, |
89 | } |
90 | } |
91 | |
92 | /// Returns a day-of-week number starting from Monday = 1. (ISO 8601 weekday number) |
93 | /// |
94 | /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` |
95 | /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
96 | /// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
97 | #[inline ] |
98 | pub const fn number_from_monday(&self) -> u32 { |
99 | self.num_days_from(Weekday::Mon) + 1 |
100 | } |
101 | |
102 | /// Returns a day-of-week number starting from Sunday = 1. |
103 | /// |
104 | /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` |
105 | /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
106 | /// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1 |
107 | #[inline ] |
108 | pub const fn number_from_sunday(&self) -> u32 { |
109 | self.num_days_from(Weekday::Sun) + 1 |
110 | } |
111 | |
112 | /// Returns a day-of-week number starting from Monday = 0. |
113 | /// |
114 | /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` |
115 | /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
116 | /// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
117 | #[inline ] |
118 | pub const fn num_days_from_monday(&self) -> u32 { |
119 | self.num_days_from(Weekday::Mon) |
120 | } |
121 | |
122 | /// Returns a day-of-week number starting from Sunday = 0. |
123 | /// |
124 | /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` |
125 | /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
126 | /// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0 |
127 | #[inline ] |
128 | pub const fn num_days_from_sunday(&self) -> u32 { |
129 | self.num_days_from(Weekday::Sun) |
130 | } |
131 | |
132 | /// Returns a day-of-week number starting from the parameter `day` (D) = 0. |
133 | /// |
134 | /// `w`: | `D` | `D+1` | `D+2` | `D+3` | `D+4` | `D+5` | `D+6` |
135 | /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
136 | /// `w.num_days_from(wd)`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
137 | #[inline ] |
138 | pub(crate) const fn num_days_from(&self, day: Weekday) -> u32 { |
139 | (*self as u32 + 7 - day as u32) % 7 |
140 | } |
141 | } |
142 | |
143 | impl fmt::Display for Weekday { |
144 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
145 | f.write_str(data:match *self { |
146 | Weekday::Mon => "Mon" , |
147 | Weekday::Tue => "Tue" , |
148 | Weekday::Wed => "Wed" , |
149 | Weekday::Thu => "Thu" , |
150 | Weekday::Fri => "Fri" , |
151 | Weekday::Sat => "Sat" , |
152 | Weekday::Sun => "Sun" , |
153 | }) |
154 | } |
155 | } |
156 | |
157 | /// Any weekday can be represented as an integer from 0 to 6, which equals to |
158 | /// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. |
159 | /// Do not heavily depend on this though; use explicit methods whenever possible. |
160 | impl TryFrom<u8> for Weekday { |
161 | type Error = OutOfRange; |
162 | |
163 | fn try_from(value: u8) -> Result<Self, Self::Error> { |
164 | match value { |
165 | 0 => Ok(Weekday::Mon), |
166 | 1 => Ok(Weekday::Tue), |
167 | 2 => Ok(Weekday::Wed), |
168 | 3 => Ok(Weekday::Thu), |
169 | 4 => Ok(Weekday::Fri), |
170 | 5 => Ok(Weekday::Sat), |
171 | 6 => Ok(Weekday::Sun), |
172 | _ => Err(OutOfRange::new()), |
173 | } |
174 | } |
175 | } |
176 | |
177 | /// Any weekday can be represented as an integer from 0 to 6, which equals to |
178 | /// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. |
179 | /// Do not heavily depend on this though; use explicit methods whenever possible. |
180 | impl num_traits::FromPrimitive for Weekday { |
181 | #[inline ] |
182 | fn from_i64(n: i64) -> Option<Weekday> { |
183 | match n { |
184 | 0 => Some(Weekday::Mon), |
185 | 1 => Some(Weekday::Tue), |
186 | 2 => Some(Weekday::Wed), |
187 | 3 => Some(Weekday::Thu), |
188 | 4 => Some(Weekday::Fri), |
189 | 5 => Some(Weekday::Sat), |
190 | 6 => Some(Weekday::Sun), |
191 | _ => None, |
192 | } |
193 | } |
194 | |
195 | #[inline ] |
196 | fn from_u64(n: u64) -> Option<Weekday> { |
197 | match n { |
198 | 0 => Some(Weekday::Mon), |
199 | 1 => Some(Weekday::Tue), |
200 | 2 => Some(Weekday::Wed), |
201 | 3 => Some(Weekday::Thu), |
202 | 4 => Some(Weekday::Fri), |
203 | 5 => Some(Weekday::Sat), |
204 | 6 => Some(Weekday::Sun), |
205 | _ => None, |
206 | } |
207 | } |
208 | } |
209 | |
210 | /// An error resulting from reading `Weekday` value with `FromStr`. |
211 | #[derive (Clone, PartialEq, Eq)] |
212 | pub struct ParseWeekdayError { |
213 | pub(crate) _dummy: (), |
214 | } |
215 | |
216 | #[cfg (feature = "std" )] |
217 | #[cfg_attr (docsrs, doc(cfg(feature = "std" )))] |
218 | impl std::error::Error for ParseWeekdayError {} |
219 | |
220 | impl fmt::Display for ParseWeekdayError { |
221 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
222 | f.write_fmt(format_args!(" {:?}" , self)) |
223 | } |
224 | } |
225 | |
226 | impl fmt::Debug for ParseWeekdayError { |
227 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
228 | write!(f, "ParseWeekdayError {{ .. }}" ) |
229 | } |
230 | } |
231 | |
232 | #[cfg (test)] |
233 | mod tests { |
234 | use super::Weekday; |
235 | |
236 | #[test ] |
237 | fn test_num_days_from() { |
238 | for i in 0..7 { |
239 | let base_day = Weekday::try_from(i).unwrap(); |
240 | |
241 | assert_eq!(base_day.num_days_from_monday(), base_day.num_days_from(Weekday::Mon)); |
242 | assert_eq!(base_day.num_days_from_sunday(), base_day.num_days_from(Weekday::Sun)); |
243 | |
244 | assert_eq!(base_day.num_days_from(base_day), 0); |
245 | |
246 | assert_eq!(base_day.num_days_from(base_day.pred()), 1); |
247 | assert_eq!(base_day.num_days_from(base_day.pred().pred()), 2); |
248 | assert_eq!(base_day.num_days_from(base_day.pred().pred().pred()), 3); |
249 | assert_eq!(base_day.num_days_from(base_day.pred().pred().pred().pred()), 4); |
250 | assert_eq!(base_day.num_days_from(base_day.pred().pred().pred().pred().pred()), 5); |
251 | assert_eq!( |
252 | base_day.num_days_from(base_day.pred().pred().pred().pred().pred().pred()), |
253 | 6 |
254 | ); |
255 | |
256 | assert_eq!(base_day.num_days_from(base_day.succ()), 6); |
257 | assert_eq!(base_day.num_days_from(base_day.succ().succ()), 5); |
258 | assert_eq!(base_day.num_days_from(base_day.succ().succ().succ()), 4); |
259 | assert_eq!(base_day.num_days_from(base_day.succ().succ().succ().succ()), 3); |
260 | assert_eq!(base_day.num_days_from(base_day.succ().succ().succ().succ().succ()), 2); |
261 | assert_eq!( |
262 | base_day.num_days_from(base_day.succ().succ().succ().succ().succ().succ()), |
263 | 1 |
264 | ); |
265 | } |
266 | } |
267 | } |
268 | |
269 | // the actual `FromStr` implementation is in the `format` module to leverage the existing code |
270 | |
271 | #[cfg (feature = "serde" )] |
272 | #[cfg_attr (docsrs, doc(cfg(feature = "serde" )))] |
273 | mod weekday_serde { |
274 | use super::Weekday; |
275 | use core::fmt; |
276 | use serde::{de, ser}; |
277 | |
278 | impl ser::Serialize for Weekday { |
279 | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
280 | where |
281 | S: ser::Serializer, |
282 | { |
283 | serializer.collect_str(&self) |
284 | } |
285 | } |
286 | |
287 | struct WeekdayVisitor; |
288 | |
289 | impl<'de> de::Visitor<'de> for WeekdayVisitor { |
290 | type Value = Weekday; |
291 | |
292 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { |
293 | f.write_str("Weekday" ) |
294 | } |
295 | |
296 | fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> |
297 | where |
298 | E: de::Error, |
299 | { |
300 | value.parse().map_err(|_| E::custom("short or long weekday names expected" )) |
301 | } |
302 | } |
303 | |
304 | impl<'de> de::Deserialize<'de> for Weekday { |
305 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
306 | where |
307 | D: de::Deserializer<'de>, |
308 | { |
309 | deserializer.deserialize_str(WeekdayVisitor) |
310 | } |
311 | } |
312 | |
313 | #[test ] |
314 | fn test_serde_serialize() { |
315 | use serde_json::to_string; |
316 | use Weekday::*; |
317 | |
318 | let cases: Vec<(Weekday, &str)> = vec![ |
319 | (Mon, " \"Mon \"" ), |
320 | (Tue, " \"Tue \"" ), |
321 | (Wed, " \"Wed \"" ), |
322 | (Thu, " \"Thu \"" ), |
323 | (Fri, " \"Fri \"" ), |
324 | (Sat, " \"Sat \"" ), |
325 | (Sun, " \"Sun \"" ), |
326 | ]; |
327 | |
328 | for (weekday, expected_str) in cases { |
329 | let string = to_string(&weekday).unwrap(); |
330 | assert_eq!(string, expected_str); |
331 | } |
332 | } |
333 | |
334 | #[test ] |
335 | fn test_serde_deserialize() { |
336 | use serde_json::from_str; |
337 | use Weekday::*; |
338 | |
339 | let cases: Vec<(&str, Weekday)> = vec![ |
340 | (" \"mon \"" , Mon), |
341 | (" \"MONDAY \"" , Mon), |
342 | (" \"MonDay \"" , Mon), |
343 | (" \"mOn \"" , Mon), |
344 | (" \"tue \"" , Tue), |
345 | (" \"tuesday \"" , Tue), |
346 | (" \"wed \"" , Wed), |
347 | (" \"wednesday \"" , Wed), |
348 | (" \"thu \"" , Thu), |
349 | (" \"thursday \"" , Thu), |
350 | (" \"fri \"" , Fri), |
351 | (" \"friday \"" , Fri), |
352 | (" \"sat \"" , Sat), |
353 | (" \"saturday \"" , Sat), |
354 | (" \"sun \"" , Sun), |
355 | (" \"sunday \"" , Sun), |
356 | ]; |
357 | |
358 | for (str, expected_weekday) in cases { |
359 | let weekday = from_str::<Weekday>(str).unwrap(); |
360 | assert_eq!(weekday, expected_weekday); |
361 | } |
362 | |
363 | let errors: Vec<&str> = |
364 | vec![" \"not a weekday \"" , " \"monDAYs \"" , " \"mond \"" , "mon" , " \"thur \"" , " \"thurs \"" ]; |
365 | |
366 | for str in errors { |
367 | from_str::<Weekday>(str).unwrap_err(); |
368 | } |
369 | } |
370 | } |
371 | |