aimcal_core/
event.rs

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