Skip to main content

sofie_values/time/
weekday.rs

1use jiff::civil;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use sofie_core::variants::{self, Variants};
4use sofie_core::{Cast, ColumnReader, ColumnSpec, Result, Show, Shown, SqlValue};
5
6#[cfg(feature = "web")]
7use crate::Conventions;
8use crate::{Input, Present};
9
10/// Stored and sent by its name, as an enum's variant is; its labels are the `weekday` enum's.
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
12pub enum Weekday {
13    Monday,
14    Tuesday,
15    Wednesday,
16    Thursday,
17    Friday,
18    Saturday,
19    Sunday,
20}
21
22const NAMES: [&str; 7] = [
23    "MONDAY",
24    "TUESDAY",
25    "WEDNESDAY",
26    "THURSDAY",
27    "FRIDAY",
28    "SATURDAY",
29    "SUNDAY",
30];
31
32const IN_ORDER: [Weekday; 7] = [
33    Weekday::Monday,
34    Weekday::Tuesday,
35    Weekday::Wednesday,
36    Weekday::Thursday,
37    Weekday::Friday,
38    Weekday::Saturday,
39    Weekday::Sunday,
40];
41
42impl Weekday {
43    /// For a day written in code in lowercase, such as a schedule's `monday`: a wrong one fails
44    /// the build in a constant.
45    #[must_use]
46    pub const fn constant(name: &str) -> Self {
47        let mut index = 0;
48        while index < NAMES.len() {
49            if lowercase_equals(NAMES[index].as_bytes(), name.as_bytes()) {
50                return IN_ORDER[index];
51            }
52            index += 1;
53        }
54        panic!("a weekday is written in English and lowercase, such as monday");
55    }
56}
57
58const fn lowercase_equals(upper: &[u8], written: &[u8]) -> bool {
59    if upper.len() != written.len() {
60        return false;
61    }
62    let mut index = 0;
63    while index < upper.len() {
64        if upper[index].to_ascii_lowercase() != written[index] {
65            return false;
66        }
67        index += 1;
68    }
69    true
70}
71
72impl From<civil::Weekday> for Weekday {
73    fn from(weekday: civil::Weekday) -> Self {
74        let offset = weekday.to_monday_zero_offset().unsigned_abs();
75        IN_ORDER[usize::from(offset)]
76    }
77}
78
79impl Variants for Weekday {
80    const TYPE: &'static str = "Weekday";
81    const VARIANTS: &'static [(&'static str, Self)] = &[
82        (NAMES[0], Self::Monday),
83        (NAMES[1], Self::Tuesday),
84        (NAMES[2], Self::Wednesday),
85        (NAMES[3], Self::Thursday),
86        (NAMES[4], Self::Friday),
87        (NAMES[5], Self::Saturday),
88        (NAMES[6], Self::Sunday),
89    ];
90}
91
92impl Show for Weekday {
93    fn show(&self) -> Shown<'_> {
94        Shown::Text(self.name())
95    }
96}
97
98impl Cast for Weekday {
99    const COLUMNS: &'static [ColumnSpec] = &[variants::COLUMNS[0].with_values(&NAMES)];
100
101    fn encode(&self, out: &mut Vec<SqlValue>) {
102        variants::encode(*self, out);
103    }
104
105    fn decode(row: &mut dyn ColumnReader) -> Result<Self> {
106        variants::decode(row)
107    }
108}
109
110impl Serialize for Weekday {
111    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
112        variants::serialize(*self, serializer)
113    }
114}
115
116impl<'de> Deserialize<'de> for Weekday {
117    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
118        variants::deserialize(deserializer)
119    }
120}
121
122impl Present for Weekday {
123    const INPUT: Input = Input::select(&NAMES);
124    const SEARCH: crate::Search = crate::Search::Exact;
125
126    fn csv(&self) -> String {
127        self.name().to_owned()
128    }
129
130    #[cfg(feature = "web")]
131    fn write_localized(&self, out: &mut String, conventions: &dyn Conventions) {
132        conventions.write_variant(out, "weekday", self.name());
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use crate::Date;
140    use crate::testing::round_trip;
141
142    #[test]
143    fn a_day_falls_on_its_weekday() {
144        let sunday = Date::parse("2026-10-25").unwrap();
145
146        assert_eq!(sunday.weekday(), Weekday::Sunday);
147        assert_eq!(
148            Date::parse("2026-10-26").unwrap().weekday(),
149            Weekday::Monday
150        );
151    }
152
153    #[test]
154    fn a_constant_is_its_lowercase_english_name() {
155        assert_eq!(Weekday::constant("monday"), Weekday::Monday);
156        assert_eq!(Weekday::constant("sunday"), Weekday::Sunday);
157    }
158
159    #[test]
160    #[should_panic(expected = "a weekday is written in English and lowercase")]
161    fn a_wrong_constant_day_fails() {
162        let _ = Weekday::constant("Monday");
163    }
164
165    #[test]
166    fn round_trips_through_its_name_like_an_enum() {
167        assert_eq!(
168            round_trip(&Weekday::Wednesday),
169            vec![SqlValue::Text("WEDNESDAY".into())]
170        );
171        assert_eq!(Weekday::COLUMNS[0].values, NAMES);
172        let json = serde_json::to_value(Weekday::Friday).unwrap();
173        assert_eq!(json, serde_json::json!("FRIDAY"));
174        assert_eq!(
175            serde_json::from_value::<Weekday>(json).unwrap(),
176            Weekday::Friday
177        );
178        assert!(serde_json::from_value::<Weekday>(serde_json::json!("friday")).is_err());
179        assert_eq!(Weekday::INPUT.options, NAMES);
180        assert_eq!(Weekday::Saturday.csv(), "SATURDAY");
181    }
182}