use std::rc::Rc;
use gpui::SharedString;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Day(pub i64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MonthKey(pub i64);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MonthCell {
Empty,
Day(Day),
Adjacent(Day),
}
impl MonthCell {
pub fn day(self) -> Option<Day> {
match self {
Self::Empty => None,
Self::Day(day) | Self::Adjacent(day) => Some(day),
}
}
pub fn is_adjacent(self) -> bool {
matches!(self, Self::Adjacent(_))
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct MonthGrid {
pub weeks: Vec<Vec<MonthCell>>,
}
impl MonthGrid {
pub fn new(weeks: impl IntoIterator<Item = Vec<MonthCell>>) -> Self {
Self {
weeks: weeks.into_iter().collect(),
}
}
pub fn is_empty(&self) -> bool {
self.weeks
.iter()
.all(|week| week.iter().all(|cell| matches!(cell, MonthCell::Empty)))
}
pub fn position_of(&self, day: Day) -> Option<(usize, usize)> {
self.weeks.iter().enumerate().find_map(|(week, cells)| {
cells
.iter()
.position(|cell| cell.day() == Some(day))
.map(|column| (week, column))
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Selectability {
Selectable,
Blocked { reason: SharedString },
}
impl Selectability {
pub fn blocked(reason: impl Into<SharedString>) -> Self {
Self::Blocked {
reason: reason.into(),
}
}
pub fn is_selectable(&self) -> bool {
matches!(self, Self::Selectable)
}
pub fn reason(&self) -> Option<&SharedString> {
match self {
Self::Selectable => None,
Self::Blocked { reason } => Some(reason),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Clock {
pub hour_min: u32,
pub hour_max: u32,
pub minute_max: u32,
pub second_max: u32,
pub meridiem: Option<(SharedString, SharedString)>,
}
impl Clock {
pub fn is_twelve_hour(&self) -> bool {
self.meridiem.is_some()
}
pub fn meridiem_label(&self, index: usize) -> Option<SharedString> {
let (first, second) = self.meridiem.as_ref()?;
match index {
0 => Some(first.clone()),
1 => Some(second.clone()),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct TimeOfDay {
pub hour: u32,
pub minute: u32,
pub second: Option<u32>,
pub meridiem: Option<usize>,
}
impl TimeOfDay {
pub fn new(hour: u32, minute: u32) -> Self {
Self {
hour,
minute,
second: None,
meridiem: None,
}
}
pub fn with_second(mut self, second: u32) -> Self {
self.second = Some(second);
self
}
pub fn with_meridiem(mut self, index: usize) -> Self {
self.meridiem = Some(index);
self
}
}
pub trait DateAdapter {
fn today(&self) -> Option<Day>;
fn month_of(&self, day: Day) -> MonthKey;
fn month_grid(&self, month: MonthKey) -> MonthGrid;
fn month_label(&self, month: MonthKey) -> SharedString;
fn weekday_labels(&self) -> Vec<SharedString>;
fn format_day(&self, day: Day) -> SharedString;
fn day_label(&self, day: Day) -> SharedString;
fn parse_day(&self, text: &str) -> Result<Day, SharedString>;
fn shift_month(&self, month: MonthKey, delta: i32) -> Option<MonthKey>;
fn is_selectable(&self, day: Day) -> Selectability;
fn days_in(&self, start: Day, end: Day) -> Option<Vec<Day>>;
fn clock(&self) -> Clock;
fn format_time(&self, time: TimeOfDay) -> SharedString;
}
pub type SharedDateAdapter = Rc<dyn DateAdapter>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_cell_reports_the_day_it_carries_whichever_month_owns_it() {
assert_eq!(MonthCell::Empty.day(), None);
assert_eq!(MonthCell::Day(Day(3)).day(), Some(Day(3)));
assert_eq!(MonthCell::Adjacent(Day(4)).day(), Some(Day(4)));
assert!(MonthCell::Adjacent(Day(4)).is_adjacent());
assert!(!MonthCell::Day(Day(4)).is_adjacent());
}
#[test]
fn a_day_is_found_by_identity_rather_than_by_position() {
let grid = MonthGrid::new([
vec![MonthCell::Empty, MonthCell::Day(Day(1))],
vec![MonthCell::Day(Day(2)), MonthCell::Adjacent(Day(3))],
]);
assert_eq!(grid.position_of(Day(1)), Some((0, 1)));
assert_eq!(grid.position_of(Day(3)), Some((1, 1)));
assert_eq!(grid.position_of(Day(9)), None);
}
#[test]
fn a_grid_of_nothing_but_blanks_is_empty() {
assert!(MonthGrid::new([vec![MonthCell::Empty; 7]]).is_empty());
assert!(!MonthGrid::new([vec![MonthCell::Day(Day(1))]]).is_empty());
}
}