1use std::rc::Rc;
11
12use gpui::SharedString;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
20pub struct Day(pub i64);
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub struct MonthKey(pub i64);
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum MonthCell {
33 Empty,
36 Day(Day),
38 Adjacent(Day),
40}
41
42impl MonthCell {
43 pub fn day(self) -> Option<Day> {
44 match self {
45 Self::Empty => None,
46 Self::Day(day) | Self::Adjacent(day) => Some(day),
47 }
48 }
49
50 pub fn is_adjacent(self) -> bool {
51 matches!(self, Self::Adjacent(_))
52 }
53}
54
55#[derive(Debug, Clone, Default, PartialEq, Eq)]
61pub struct MonthGrid {
62 pub weeks: Vec<Vec<MonthCell>>,
63}
64
65impl MonthGrid {
66 pub fn new(weeks: impl IntoIterator<Item = Vec<MonthCell>>) -> Self {
67 Self {
68 weeks: weeks.into_iter().collect(),
69 }
70 }
71
72 pub fn is_empty(&self) -> bool {
73 self.weeks
74 .iter()
75 .all(|week| week.iter().all(|cell| matches!(cell, MonthCell::Empty)))
76 }
77
78 pub fn position_of(&self, day: Day) -> Option<(usize, usize)> {
80 self.weeks.iter().enumerate().find_map(|(week, cells)| {
81 cells
82 .iter()
83 .position(|cell| cell.day() == Some(day))
84 .map(|column| (week, column))
85 })
86 }
87}
88
89#[derive(Debug, Clone, PartialEq, Eq)]
94pub enum Selectability {
95 Selectable,
96 Blocked { reason: SharedString },
97}
98
99impl Selectability {
100 pub fn blocked(reason: impl Into<SharedString>) -> Self {
101 Self::Blocked {
102 reason: reason.into(),
103 }
104 }
105
106 pub fn is_selectable(&self) -> bool {
107 matches!(self, Self::Selectable)
108 }
109
110 pub fn reason(&self) -> Option<&SharedString> {
111 match self {
112 Self::Selectable => None,
113 Self::Blocked { reason } => Some(reason),
114 }
115 }
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
124pub struct Clock {
125 pub hour_min: u32,
126 pub hour_max: u32,
127 pub minute_max: u32,
128 pub second_max: u32,
129 pub meridiem: Option<(SharedString, SharedString)>,
131}
132
133impl Clock {
134 pub fn is_twelve_hour(&self) -> bool {
135 self.meridiem.is_some()
136 }
137
138 pub fn meridiem_label(&self, index: usize) -> Option<SharedString> {
139 let (first, second) = self.meridiem.as_ref()?;
140 match index {
141 0 => Some(first.clone()),
142 1 => Some(second.clone()),
143 _ => None,
144 }
145 }
146}
147
148#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
153pub struct TimeOfDay {
154 pub hour: u32,
155 pub minute: u32,
156 pub second: Option<u32>,
157 pub meridiem: Option<usize>,
158}
159
160impl TimeOfDay {
161 pub fn new(hour: u32, minute: u32) -> Self {
162 Self {
163 hour,
164 minute,
165 second: None,
166 meridiem: None,
167 }
168 }
169
170 pub fn with_second(mut self, second: u32) -> Self {
171 self.second = Some(second);
172 self
173 }
174
175 pub fn with_meridiem(mut self, index: usize) -> Self {
176 self.meridiem = Some(index);
177 self
178 }
179}
180
181pub trait DateAdapter {
188 fn today(&self) -> Option<Day>;
193
194 fn month_of(&self, day: Day) -> MonthKey;
195
196 fn month_grid(&self, month: MonthKey) -> MonthGrid;
197
198 fn month_label(&self, month: MonthKey) -> SharedString;
200
201 fn weekday_labels(&self) -> Vec<SharedString>;
203
204 fn format_day(&self, day: Day) -> SharedString;
206
207 fn day_label(&self, day: Day) -> SharedString;
209
210 fn parse_day(&self, text: &str) -> Result<Day, SharedString>;
213
214 fn shift_month(&self, month: MonthKey, delta: i32) -> Option<MonthKey>;
217
218 fn is_selectable(&self, day: Day) -> Selectability;
219
220 fn days_in(&self, start: Day, end: Day) -> Option<Vec<Day>>;
227
228 fn clock(&self) -> Clock;
229
230 fn format_time(&self, time: TimeOfDay) -> SharedString;
232}
233
234pub type SharedDateAdapter = Rc<dyn DateAdapter>;
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn a_cell_reports_the_day_it_carries_whichever_month_owns_it() {
243 assert_eq!(MonthCell::Empty.day(), None);
244 assert_eq!(MonthCell::Day(Day(3)).day(), Some(Day(3)));
245 assert_eq!(MonthCell::Adjacent(Day(4)).day(), Some(Day(4)));
246 assert!(MonthCell::Adjacent(Day(4)).is_adjacent());
247 assert!(!MonthCell::Day(Day(4)).is_adjacent());
248 }
249
250 #[test]
251 fn a_day_is_found_by_identity_rather_than_by_position() {
252 let grid = MonthGrid::new([
253 vec![MonthCell::Empty, MonthCell::Day(Day(1))],
254 vec![MonthCell::Day(Day(2)), MonthCell::Adjacent(Day(3))],
255 ]);
256 assert_eq!(grid.position_of(Day(1)), Some((0, 1)));
257 assert_eq!(grid.position_of(Day(3)), Some((1, 1)));
258 assert_eq!(grid.position_of(Day(9)), None);
259 }
260
261 #[test]
262 fn a_grid_of_nothing_but_blanks_is_empty() {
263 assert!(MonthGrid::new([vec![MonthCell::Empty; 7]]).is_empty());
264 assert!(!MonthGrid::new([vec![MonthCell::Day(Day(1))]]).is_empty());
265 }
266}