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 { 7 + lhs - rhs } else { lhs - rhs } |
165 | } |
166 | } |
167 | |
168 | impl fmt::Display for Weekday { |
169 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
170 | f.pad(match *self { |
171 | Weekday::Mon => "Mon" , |
172 | Weekday::Tue => "Tue" , |
173 | Weekday::Wed => "Wed" , |
174 | Weekday::Thu => "Thu" , |
175 | Weekday::Fri => "Fri" , |
176 | Weekday::Sat => "Sat" , |
177 | Weekday::Sun => "Sun" , |
178 | }) |
179 | } |
180 | } |
181 | |
182 | /// Any weekday can be represented as an integer from 0 to 6, which equals to |
183 | /// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. |
184 | /// Do not heavily depend on this though; use explicit methods whenever possible. |
185 | impl TryFrom<u8> for Weekday { |
186 | type Error = OutOfRange; |
187 | |
188 | fn try_from(value: u8) -> Result<Self, Self::Error> { |
189 | match value { |
190 | 0 => Ok(Weekday::Mon), |
191 | 1 => Ok(Weekday::Tue), |
192 | 2 => Ok(Weekday::Wed), |
193 | 3 => Ok(Weekday::Thu), |
194 | 4 => Ok(Weekday::Fri), |
195 | 5 => Ok(Weekday::Sat), |
196 | 6 => Ok(Weekday::Sun), |
197 | _ => Err(OutOfRange::new()), |
198 | } |
199 | } |
200 | } |
201 | |
202 | /// Any weekday can be represented as an integer from 0 to 6, which equals to |
203 | /// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. |
204 | /// Do not heavily depend on this though; use explicit methods whenever possible. |
205 | impl num_traits::FromPrimitive for Weekday { |
206 | #[inline ] |
207 | fn from_i64(n: i64) -> Option<Weekday> { |
208 | match n { |
209 | 0 => Some(Weekday::Mon), |
210 | 1 => Some(Weekday::Tue), |
211 | 2 => Some(Weekday::Wed), |
212 | 3 => Some(Weekday::Thu), |
213 | 4 => Some(Weekday::Fri), |
214 | 5 => Some(Weekday::Sat), |
215 | 6 => Some(Weekday::Sun), |
216 | _ => None, |
217 | } |
218 | } |
219 | |
220 | #[inline ] |
221 | fn from_u64(n: u64) -> Option<Weekday> { |
222 | match n { |
223 | 0 => Some(Weekday::Mon), |
224 | 1 => Some(Weekday::Tue), |
225 | 2 => Some(Weekday::Wed), |
226 | 3 => Some(Weekday::Thu), |
227 | 4 => Some(Weekday::Fri), |
228 | 5 => Some(Weekday::Sat), |
229 | 6 => Some(Weekday::Sun), |
230 | _ => None, |
231 | } |
232 | } |
233 | } |
234 | |
235 | /// An error resulting from reading `Weekday` value with `FromStr`. |
236 | #[derive (Clone, PartialEq, Eq)] |
237 | pub struct ParseWeekdayError { |
238 | pub(crate) _dummy: (), |
239 | } |
240 | |
241 | #[cfg (feature = "std" )] |
242 | impl std::error::Error for ParseWeekdayError {} |
243 | |
244 | impl fmt::Display for ParseWeekdayError { |
245 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
246 | f.write_fmt(format_args!(" {:?}" , self)) |
247 | } |
248 | } |
249 | |
250 | impl fmt::Debug for ParseWeekdayError { |
251 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
252 | write!(f, "ParseWeekdayError {{ .. }}" ) |
253 | } |
254 | } |
255 | |
256 | // the actual `FromStr` implementation is in the `format` module to leverage the existing code |
257 | |
258 | #[cfg (feature = "serde" )] |
259 | mod weekday_serde { |
260 | use super::Weekday; |
261 | use core::fmt; |
262 | use serde::{de, ser}; |
263 | |
264 | impl ser::Serialize for Weekday { |
265 | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
266 | where |
267 | S: ser::Serializer, |
268 | { |
269 | serializer.collect_str(&self) |
270 | } |
271 | } |
272 | |
273 | struct WeekdayVisitor; |
274 | |
275 | impl de::Visitor<'_> for WeekdayVisitor { |
276 | type Value = Weekday; |
277 | |
278 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { |
279 | f.write_str("Weekday" ) |
280 | } |
281 | |
282 | fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> |
283 | where |
284 | E: de::Error, |
285 | { |
286 | value.parse().map_err(|_| E::custom("short or long weekday names expected" )) |
287 | } |
288 | } |
289 | |
290 | impl<'de> de::Deserialize<'de> for Weekday { |
291 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
292 | where |
293 | D: de::Deserializer<'de>, |
294 | { |
295 | deserializer.deserialize_str(WeekdayVisitor) |
296 | } |
297 | } |
298 | } |
299 | |
300 | #[cfg (test)] |
301 | mod tests { |
302 | use super::Weekday; |
303 | |
304 | #[test ] |
305 | fn test_days_since() { |
306 | for i in 0..7 { |
307 | let base_day = Weekday::try_from(i).unwrap(); |
308 | |
309 | assert_eq!(base_day.num_days_from_monday(), base_day.days_since(Weekday::Mon)); |
310 | assert_eq!(base_day.num_days_from_sunday(), base_day.days_since(Weekday::Sun)); |
311 | |
312 | assert_eq!(base_day.days_since(base_day), 0); |
313 | |
314 | assert_eq!(base_day.days_since(base_day.pred()), 1); |
315 | assert_eq!(base_day.days_since(base_day.pred().pred()), 2); |
316 | assert_eq!(base_day.days_since(base_day.pred().pred().pred()), 3); |
317 | assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred()), 4); |
318 | assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred()), 5); |
319 | assert_eq!(base_day.days_since(base_day.pred().pred().pred().pred().pred().pred()), 6); |
320 | |
321 | assert_eq!(base_day.days_since(base_day.succ()), 6); |
322 | assert_eq!(base_day.days_since(base_day.succ().succ()), 5); |
323 | assert_eq!(base_day.days_since(base_day.succ().succ().succ()), 4); |
324 | assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ()), 3); |
325 | assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ()), 2); |
326 | assert_eq!(base_day.days_since(base_day.succ().succ().succ().succ().succ().succ()), 1); |
327 | } |
328 | } |
329 | |
330 | #[test ] |
331 | fn test_formatting_alignment() { |
332 | // No exhaustive testing here as we just delegate the |
333 | // implementation to Formatter::pad. Just some basic smoke |
334 | // testing to ensure that it's in fact being done. |
335 | assert_eq!(format!("{:x>7}" , Weekday::Mon), "xxxxMon" ); |
336 | assert_eq!(format!("{:^7}" , Weekday::Mon), " Mon " ); |
337 | assert_eq!(format!("{:Z<7}" , Weekday::Mon), "MonZZZZ" ); |
338 | } |
339 | |
340 | #[test ] |
341 | #[cfg (feature = "serde" )] |
342 | fn test_serde_serialize() { |
343 | use Weekday::*; |
344 | use serde_json::to_string; |
345 | |
346 | let cases: Vec<(Weekday, &str)> = vec![ |
347 | (Mon, " \"Mon \"" ), |
348 | (Tue, " \"Tue \"" ), |
349 | (Wed, " \"Wed \"" ), |
350 | (Thu, " \"Thu \"" ), |
351 | (Fri, " \"Fri \"" ), |
352 | (Sat, " \"Sat \"" ), |
353 | (Sun, " \"Sun \"" ), |
354 | ]; |
355 | |
356 | for (weekday, expected_str) in cases { |
357 | let string = to_string(&weekday).unwrap(); |
358 | assert_eq!(string, expected_str); |
359 | } |
360 | } |
361 | |
362 | #[test ] |
363 | #[cfg (feature = "serde" )] |
364 | fn test_serde_deserialize() { |
365 | use Weekday::*; |
366 | use serde_json::from_str; |
367 | |
368 | let cases: Vec<(&str, Weekday)> = vec![ |
369 | (" \"mon \"" , Mon), |
370 | (" \"MONDAY \"" , Mon), |
371 | (" \"MonDay \"" , Mon), |
372 | (" \"mOn \"" , Mon), |
373 | (" \"tue \"" , Tue), |
374 | (" \"tuesday \"" , Tue), |
375 | (" \"wed \"" , Wed), |
376 | (" \"wednesday \"" , Wed), |
377 | (" \"thu \"" , Thu), |
378 | (" \"thursday \"" , Thu), |
379 | (" \"fri \"" , Fri), |
380 | (" \"friday \"" , Fri), |
381 | (" \"sat \"" , Sat), |
382 | (" \"saturday \"" , Sat), |
383 | (" \"sun \"" , Sun), |
384 | (" \"sunday \"" , Sun), |
385 | ]; |
386 | |
387 | for (str, expected_weekday) in cases { |
388 | let weekday = from_str::<Weekday>(str).unwrap(); |
389 | assert_eq!(weekday, expected_weekday); |
390 | } |
391 | |
392 | let errors: Vec<&str> = |
393 | vec![" \"not a weekday \"" , " \"monDAYs \"" , " \"mond \"" , "mon" , " \"thur \"" , " \"thurs \"" ]; |
394 | |
395 | for str in errors { |
396 | from_str::<Weekday>(str).unwrap_err(); |
397 | } |
398 | } |
399 | |
400 | #[test ] |
401 | #[cfg (feature = "rkyv-validation" )] |
402 | fn test_rkyv_validation() { |
403 | let mon = Weekday::Mon; |
404 | let bytes = rkyv::to_bytes::<_, 1>(&mon).unwrap(); |
405 | |
406 | assert_eq!(rkyv::from_bytes::<Weekday>(&bytes).unwrap(), mon); |
407 | } |
408 | } |
409 | |