aimcal_core/
event.rs

1// SPDX-FileCopyrightText: 2025 Zexin Yuan <aim@yzx9.xyz>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{fmt::Display, num::NonZeroU32, str::FromStr};
6
7use chrono::{DateTime, Local};
8use icalendar::Component;
9
10use crate::LooseDateTime;
11
12/// Trait representing a calendar event.
13pub trait Event {
14    /// The short identifier for the event.
15    /// It will be `None` if the event does not have a short ID.
16    /// It is used for display purposes and may not be unique.
17    fn short_id(&self) -> Option<NonZeroU32> {
18        None
19    }
20
21    /// The unique identifier for the event.
22    fn uid(&self) -> &str;
23
24    /// The description of the event, if available.
25    fn description(&self) -> Option<&str>;
26
27    /// The location of the event, if available.
28    fn start(&self) -> Option<LooseDateTime>;
29
30    /// The start date and time of the event, if available.
31    fn end(&self) -> Option<LooseDateTime>;
32
33    /// The status of the event, if available.
34    fn status(&self) -> Option<EventStatus>;
35
36    /// The summary of the event.
37    fn summary(&self) -> &str;
38}
39
40impl Event for icalendar::Event {
41    fn uid(&self) -> &str {
42        self.get_uid().unwrap_or("")
43    }
44
45    fn description(&self) -> Option<&str> {
46        self.get_description()
47    }
48
49    fn start(&self) -> Option<LooseDateTime> {
50        self.get_start().map(Into::into)
51    }
52
53    fn end(&self) -> Option<LooseDateTime> {
54        self.get_end().map(Into::into)
55    }
56
57    fn status(&self) -> Option<EventStatus> {
58        self.get_status().map(EventStatus::from)
59    }
60
61    fn summary(&self) -> &str {
62        self.get_summary().unwrap_or("")
63    }
64}
65
66/// The status of an event, which can be tentative, confirmed, or cancelled.
67#[derive(Debug, Clone, Copy)]
68pub enum EventStatus {
69    /// The event is tentative.
70    Tentative,
71
72    /// The event is confirmed.
73    Confirmed,
74
75    /// The event is cancelled.
76    Cancelled,
77}
78
79const STATUS_TENTATIVE: &str = "TENTATIVE";
80const STATUS_CONFIRMED: &str = "CONFIRMED";
81const STATUS_CANCELLED: &str = "CANCELLED";
82
83impl AsRef<str> for EventStatus {
84    fn as_ref(&self) -> &str {
85        match self {
86            EventStatus::Tentative => STATUS_TENTATIVE,
87            EventStatus::Confirmed => STATUS_CONFIRMED,
88            EventStatus::Cancelled => STATUS_CANCELLED,
89        }
90    }
91}
92
93impl Display for EventStatus {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        write!(f, "{}", self.as_ref())
96    }
97}
98
99impl FromStr for EventStatus {
100    type Err = ();
101
102    fn from_str(value: &str) -> Result<Self, Self::Err> {
103        match value {
104            STATUS_TENTATIVE => Ok(EventStatus::Tentative),
105            STATUS_CONFIRMED => Ok(EventStatus::Confirmed),
106            STATUS_CANCELLED => Ok(EventStatus::Cancelled),
107            _ => Err(()),
108        }
109    }
110}
111
112impl From<EventStatus> for icalendar::EventStatus {
113    fn from(status: EventStatus) -> Self {
114        match status {
115            EventStatus::Tentative => icalendar::EventStatus::Tentative,
116            EventStatus::Confirmed => icalendar::EventStatus::Confirmed,
117            EventStatus::Cancelled => icalendar::EventStatus::Cancelled,
118        }
119    }
120}
121
122impl From<icalendar::EventStatus> for EventStatus {
123    fn from(status: icalendar::EventStatus) -> Self {
124        match status {
125            icalendar::EventStatus::Tentative => EventStatus::Tentative,
126            icalendar::EventStatus::Confirmed => EventStatus::Confirmed,
127            icalendar::EventStatus::Cancelled => EventStatus::Cancelled,
128        }
129    }
130}
131
132/// Conditions for filtering events in a calendar.
133#[derive(Debug, Clone, Copy)]
134pub struct EventConditions {
135    /// Whether to include only startable events.
136    pub startable: bool,
137}
138
139#[derive(Debug)]
140pub(crate) struct ParsedEventConditions {
141    /// The date and time after which the event must end to be considered startable.
142    pub end_after: Option<DateTime<Local>>,
143}
144
145impl ParsedEventConditions {
146    pub fn parse(now: &DateTime<Local>, conds: &EventConditions) -> Self {
147        Self {
148            end_after: conds.startable.then_some(*now),
149        }
150    }
151}