1 | use core::fmt; |
2 | |
3 | #[cfg (any(feature = "rkyv" , feature = "rkyv-16" , feature = "rkyv-32" , feature = "rkyv-64" ))] |
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 ( |
34 | any(feature = "rkyv" , feature = "rkyv-16" , feature = "rkyv-32" , feature = "rkyv-64" ), |
35 | derive(Archive, Deserialize, Serialize), |
36 | archive(compare(PartialEq)), |
37 | archive_attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)) |
38 | )] |
39 | #[cfg_attr (feature = "rkyv-validation" , archive(check_bytes))] |
40 | #[cfg_attr (all(feature = "arbitrary" , feature = "std" ), derive(arbitrary::Arbitrary))] |
41 | pub enum Weekday { |
42 | /// Monday. |
43 | Mon = 0, |
44 | /// Tuesday. |
45 | Tue = 1, |
46 | /// Wednesday. |
47 | Wed = 2, |
48 | /// Thursday. |
49 | Thu = 3, |
50 | /// Friday. |
51 | Fri = 4, |
52 | /// Saturday. |
53 | Sat = 5, |
54 | /// Sunday. |
55 | Sun = 6, |
56 | } |
57 | |
58 | impl Weekday { |
59 | /// The next day in the week. |
60 | /// |
61 | /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` |
62 | /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
63 | /// `w.succ()`: | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` | `Mon` |
64 | #[inline ] |
65 | #[must_use ] |
66 | pub const fn succ(&self) -> Weekday { |
67 | match *self { |
68 | Weekday::Mon => Weekday::Tue, |
69 | Weekday::Tue => Weekday::Wed, |
70 | Weekday::Wed => Weekday::Thu, |
71 | Weekday::Thu => Weekday::Fri, |
72 | Weekday::Fri => Weekday::Sat, |
73 | Weekday::Sat => Weekday::Sun, |
74 | Weekday::Sun => Weekday::Mon, |
75 | } |
76 | } |
77 | |
78 | /// The previous day in the week. |
79 | /// |
80 | /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` |
81 | /// ----------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
82 | /// `w.pred()`: | `Sun` | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` |
83 | #[inline ] |
84 | #[must_use ] |
85 | pub const fn pred(&self) -> Weekday { |
86 | match *self { |
87 | Weekday::Mon => Weekday::Sun, |
88 | Weekday::Tue => Weekday::Mon, |
89 | Weekday::Wed => Weekday::Tue, |
90 | Weekday::Thu => Weekday::Wed, |
91 | Weekday::Fri => Weekday::Thu, |
92 | Weekday::Sat => Weekday::Fri, |
93 | Weekday::Sun => Weekday::Sat, |
94 | } |
95 | } |
96 | |
97 | /// Returns a day-of-week number starting from Monday = 1. (ISO 8601 weekday number) |
98 | /// |
99 | /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` |
100 | /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
101 | /// `w.number_from_monday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
102 | #[inline ] |
103 | pub const fn number_from_monday(&self) -> u32 { |
104 | self.days_since(Weekday::Mon) + 1 |
105 | } |
106 | |
107 | /// Returns a day-of-week number starting from Sunday = 1. |
108 | /// |
109 | /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` |
110 | /// ------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
111 | /// `w.number_from_sunday()`: | 2 | 3 | 4 | 5 | 6 | 7 | 1 |
112 | #[inline ] |
113 | pub const fn number_from_sunday(&self) -> u32 { |
114 | self.days_since(Weekday::Sun) + 1 |
115 | } |
116 | |
117 | /// Returns a day-of-week number starting from Monday = 0. |
118 | /// |
119 | /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` |
120 | /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
121 | /// `w.num_days_from_monday()`: | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
122 | /// |
123 | /// # Example |
124 | /// |
125 | /// ``` |
126 | /// # #[cfg (feature = "clock" )] { |
127 | /// # use chrono::{Local, Datelike}; |
128 | /// // MTWRFSU is occasionally used as a single-letter abbreviation of the weekdays. |
129 | /// // Use `num_days_from_monday` to index into the array. |
130 | /// const MTWRFSU: [char; 7] = ['M' , 'T' , 'W' , 'R' , 'F' , 'S' , 'U' ]; |
131 | /// |
132 | /// let today = Local::now().weekday(); |
133 | /// println!("{}" , MTWRFSU[today.num_days_from_monday() as usize]); |
134 | /// # } |
135 | /// ``` |
136 | #[inline ] |
137 | pub const fn num_days_from_monday(&self) -> u32 { |
138 | self.days_since(Weekday::Mon) |
139 | } |
140 | |
141 | /// Returns a day-of-week number starting from Sunday = 0. |
142 | /// |
143 | /// `w`: | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun` |
144 | /// --------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
145 | /// `w.num_days_from_sunday()`: | 1 | 2 | 3 | 4 | 5 | 6 | 0 |
146 | #[inline ] |
147 | pub const fn num_days_from_sunday(&self) -> u32 { |
148 | self.days_since(Weekday::Sun) |
149 | } |
150 | |
151 | /// The number of days since the given day. |
152 | /// |
153 | /// # Examples |
154 | /// |
155 | /// ``` |
156 | /// use chrono::Weekday::*; |
157 | /// assert_eq!(Mon.days_since(Mon), 0); |
158 | /// assert_eq!(Sun.days_since(Tue), 5); |
159 | /// assert_eq!(Wed.days_since(Sun), 3); |
160 | /// ``` |
161 | pub const fn days_since(&self, other: Weekday) -> u32 { |
162 | let lhs = *self as u32; |
163 | let rhs = other as u32; |
164 | if lhs < rhs { |
165 | 7 + lhs - rhs |
166 | } else { |
167 | lhs - rhs |
168 | } |
169 | } |
170 | } |
171 | |
172 | impl fmt::Display for Weekday { |
173 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
174 | f.pad(match *self { |
175 | Weekday::Mon => "Mon" , |
176 | Weekday::Tue => "Tue" , |
177 | Weekday::Wed => "Wed" , |
178 | Weekday::Thu => "Thu" , |
179 | Weekday::Fri => "Fri" , |
180 | Weekday::Sat => "Sat" , |
181 | Weekday::Sun => "Sun" , |
182 | }) |
183 | } |
184 | } |
185 | |
186 | /// Any weekday can be represented as an integer from 0 to 6, which equals to |
187 | /// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. |
188 | /// Do not heavily depend on this though; use explicit methods whenever possible. |
189 | impl TryFrom<u8> for Weekday { |
190 | type Error = OutOfRange; |
191 | |
192 | fn try_from(value: u8) -> Result<Self, Self::Error> { |
193 | match value { |
194 | 0 => Ok(Weekday::Mon), |
195 | 1 => Ok(Weekday::Tue), |
196 | 2 => Ok(Weekday::Wed), |
197 | 3 => Ok(Weekday::Thu), |
198 | 4 => Ok(Weekday::Fri), |
199 | 5 => Ok(Weekday::Sat), |
200 | 6 => Ok(Weekday::Sun), |
201 | _ => Err(OutOfRange::new()), |
202 | } |
203 | } |
204 | } |
205 | |
206 | /// Any weekday can be represented as an integer from 0 to 6, which equals to |
207 | /// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. |
208 | /// Do not heavily depend on this though; use explicit methods whenever possible. |
209 | impl num_traits::FromPrimitive for Weekday { |
210 | #[inline ] |
211 | fn from_i64(n: i64) -> Option<Weekday> { |
212 | match n { |
213 | 0 => Some(Weekday::Mon), |
214 | 1 => Some(Weekday::Tue), |
215 | 2 => Some(Weekday::Wed), |
216 | 3 => Some(Weekday::Thu), |
217 | 4 => Some(Weekday::Fri), |
218 | 5 => Some(Weekday::Sat), |
219 | 6 => Some(Weekday::Sun), |
220 | _ => None, |
221 | } |
222 | } |
223 | |
224 | #[inline ] |
225 | fn from_u64(n: u64) -> Option<Weekday> { |
226 | match n { |
227 | 0 => Some(Weekday::Mon), |
228 | 1 => Some(Weekday::Tue), |
229 | 2 => Some(Weekday::Wed), |
230 | 3 => Some(Weekday::Thu), |
231 | 4 => Some(Weekday::Fri), |
232 | 5 => Some(Weekday::Sat), |
233 | 6 => Some(Weekday::Sun), |
234 | _ => None, |
235 | } |
236 | } |
237 | } |
238 | |
239 | /// An error resulting from reading `Weekday` value with `FromStr`. |
240 | #[derive (Clone, PartialEq, Eq)] |
241 | pub struct ParseWeekdayError { |
242 | pub(crate) _dummy: (), |
243 | } |
244 | |
245 | #[cfg (feature = "std" )] |
246 | impl std::error::Error for ParseWeekdayError {} |
247 | |
248 | impl fmt::Display for ParseWeekdayError { |
249 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
250 | f.write_fmt(format_args!(" {:?}" , self)) |
251 | } |
252 | } |
253 | |
254 | impl fmt::Debug for ParseWeekdayError { |
255 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
256 | write!(f, "ParseWeekdayError {{ .. }}" ) |
257 | } |
258 | } |
259 | |
260 | // the actual `FromStr` implementation is in the `format` module to leverage the existing code |
261 | |
262 | #[cfg (feature = "serde" )] |
263 | mod weekday_serde { |
264 | use super::Weekday; |
265 | use core::fmt; |
266 | use serde::{de, ser}; |
267 | |
268 | impl ser::Serialize for Weekday { |
269 | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
270 | where |
271 | S: ser::Serializer, |
272 | { |
273 | serializer.collect_str(&self) |
274 | } |
275 | } |
276 | |
277 | struct WeekdayVisitor; |
278 | |
279 | impl de::Visitor<'_> for WeekdayVisitor { |
280 | type Value = Weekday; |
281 | |
282 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { |
283 | f.write_str("Weekday" ) |
284 | } |
285 | |
286 | fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> |
287 | where |
288 | E: de::Error, |
289 | { |
290 | value.parse().map_err(|_| E::custom("short or long weekday names expected" )) |
291 | } |
292 | } |
293 | |
294 | impl<'de> de::Deserialize<'de> for Weekday { |
295 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
296 | where |
297 | D: de::Deserializer<'de>, |
298 | { |
299 | deserializer.deserialize_str(WeekdayVisitor) |
300 | } |
301 | } |
302 | } |
303 | |
304 | #[cfg (test)] |
305 | mod tests { |
306 | use super::Weekday; |
307 | |
308 | #[test ] |
309 | fn test_days_since() { |
310 | for i in 0..7 { |
311 | let base_day = Weekday::try_from(i).unwrap(); |
312 | |
313 | assert_eq!(base_day.num_days_from_monday(), base_day.days_since(Weekday::Mon)); |
314 | assert_eq!(base_day.num_days_from_sunday(), base_day.days_since(Weekday::Sun)); |
315 | |
316 | assert_eq!(base_day.days_since(base_day), 0); |
317 | |
318 | assert_eq!(base_day.days_since(base_day.pred()), 1); |
319 | assert_eq!(base_day.days_since(base_day.pred().pred()), 2); |
320 | assert_eq!(base_day.days_since(base_day.pred().pred().pred()), 3); |
321 | assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred()), 4); |
322 | assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred()), 5); |
323 | assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred().pred()), 6); |
324 | |
325 | assert_eq!(base_day.days_since(base_day.succ()), 6); |
326 | assert_eq!(base_day.days_since(base_day.succ().succ()), 5); |
327 | assert_eq!(base_day.days_since(base_day.succ().succ().succ()), 4); |
328 | assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ()), 3); |
329 | assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ()), 2); |
330 | assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ().succ()), 1); |
331 | } |
332 | } |
333 | |
334 | #[test ] |
335 | fn test_formatting_alignment() { |
336 | // No exhaustive testing here as we just delegate the |
337 | // implementation to Formatter::pad. Just some basic smoke |
338 | // testing to ensure that it's in fact being done. |
339 | assert_eq!(format!("{:x>7}" , Weekday::Mon), "xxxxMon" ); |
340 | assert_eq!(format!("{:^7}" , Weekday::Mon), " Mon " ); |
341 | assert_eq!(format!("{:Z<7}" , Weekday::Mon), "MonZZZZ" ); |
342 | } |
343 | |
344 | #[test ] |
345 | #[cfg (feature = "serde" )] |
346 | fn test_serde_serialize() { |
347 | use serde_json::to_string; |
348 | use Weekday::*; |
349 | |
350 | let cases: Vec<(Weekday, &str)> = vec![ |
351 | (Mon, " \"Mon \"" ), |
352 | (Tue, " \"Tue \"" ), |
353 | (Wed, " \"Wed \"" ), |
354 | (Thu, " \"Thu \"" ), |
355 | (Fri, " \"Fri \"" ), |
356 | (Sat, " \"Sat \"" ), |
357 | (Sun, " \"Sun \"" ), |
358 | ]; |
359 | |
360 | for (weekday, expected_str) in cases { |
361 | let string = to_string(&weekday).unwrap(); |
362 | assert_eq!(string, expected_str); |
363 | } |
364 | } |
365 | |
366 | #[test ] |
367 | #[cfg (feature = "serde" )] |
368 | fn test_serde_deserialize() { |
369 | use serde_json::from_str; |
370 | use Weekday::*; |
371 | |
372 | let cases: Vec<(&str, Weekday)> = vec![ |
373 | (" \"mon \"" , Mon), |
374 | (" \"MONDAY \"" , Mon), |
375 | (" \"MonDay \"" , Mon), |
376 | (" \"mOn \"" , Mon), |
377 | (" \"tue \"" , Tue), |
378 | (" \"tuesday \"" , Tue), |
379 | (" \"wed \"" , Wed), |
380 | (" \"wednesday \"" , Wed), |
381 | (" \"thu \"" , Thu), |
382 | (" \"thursday \"" , Thu), |
383 | (" \"fri \"" , Fri), |
384 | (" \"friday \"" , Fri), |
385 | (" \"sat \"" , Sat), |
386 | (" \"saturday \"" , Sat), |
387 | (" \"sun \"" , Sun), |
388 | (" \"sunday \"" , Sun), |
389 | ]; |
390 | |
391 | for (str, expected_weekday) in cases { |
392 | let weekday = from_str::<Weekday>(str).unwrap(); |
393 | assert_eq!(weekday, expected_weekday); |
394 | } |
395 | |
396 | let errors: Vec<&str> = |
397 | vec![" \"not a weekday \"" , " \"monDAYs \"" , " \"mond \"" , "mon" , " \"thur \"" , " \"thurs \"" ]; |
398 | |
399 | for str in errors { |
400 | from_str::<Weekday>(str).unwrap_err(); |
401 | } |
402 | } |
403 | |
404 | #[test ] |
405 | #[cfg (feature = "rkyv-validation" )] |
406 | fn test_rkyv_validation() { |
407 | let mon = Weekday::Mon; |
408 | let bytes = rkyv::to_bytes::<_, 1>(&mon).unwrap(); |
409 | |
410 | assert_eq!(rkyv::from_bytes::<Weekday>(&bytes).unwrap(), mon); |
411 | } |
412 | } |
413 | |