Skip to main content

ptsd/
utils.rs

1use chrono::{DateTime, Local, Datelike, Timelike, TimeZone};
2use prism::{Context, drawable::Drawable};
3
4// #[derive(Clone, Copy, Deserialize, Serialize, Debug)]
5// pub struct InternetConnection(pub bool);
6
7/// `Timestamp` contains the date time in an easy-to-read format.
8#[derive(Clone, Debug, PartialEq, Default)]
9pub struct Timestamp(String, String);
10
11impl std::fmt::Display for Timestamp {
12    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13        write!(f, "{}", self.friendly())
14    }
15}
16
17impl Timestamp {
18    /// Create a `Timestamp` from a local [`DateTime<Local>`].
19    ///
20    /// Formats as `M/D/YY` for the date and `H:MM AM/PM` for the time.
21    pub fn new(dt: DateTime<Local>) -> Self {
22        Timestamp(
23            dt.format("%-m/%-d/%y").to_string(), 
24            dt.format("%-I:%M %p").to_string()
25        )
26    }
27
28    /// Create a `Timestamp` with date and time set as pending (`"-"`).
29    pub fn pending() -> Self {
30        Timestamp("-".to_string(), "-".to_string())
31    }
32
33    /// Tries to convert the `Timestamp` into a `DateTime<Local>`.
34    ///
35    /// Parses the stored date and time strings using the format `M/D/YY H:MM AM/PM`.
36    pub fn to_datetime(&self) -> Option<DateTime<Local>> {
37        let combined = format!("{} {}", self.date(), self.time());
38        let format = "%m/%d/%y %I:%M %p";
39        let naive = chrono::NaiveDateTime::parse_from_str(&combined, format).expect("Could not parse time");
40        Local.from_local_datetime(&naive).single()
41    }
42
43    /// Returns a human-readable, "direct" representation of the timestamp.
44    ///
45    /// Formats the timestamp based on how recent it is:
46    /// - **Today**: `"H:MM am/pm"`
47    /// - **Yesterday**: `"yesterday, H:MM am/pm"`
48    /// - **Same week**: day of the week (e.g., `"Monday"`)
49    /// - **Same year**: `"Month D"` (e.g., `"August 16"`)
50    /// - **Otherwise**: `"MM/DD/YY"`
51    ///
52    /// Returns `None` if the timestamp cannot be converted to a local datetime.
53    pub fn friendly(&self) -> String {
54        let dt = self.to_datetime().expect("Invalid date and time");
55        let today = Local::now().date_naive();
56        let date = dt.date_naive();
57        let hour = dt.hour();
58        let minute = dt.minute();
59        let (hour12, am_pm) = match hour == 0 {
60            true => (12, "am"),
61            false if hour < 12 => (hour, "am"),
62            false if hour == 12 => (12, "pm"),
63            false => (hour - 12, "pm")
64        };
65
66        let the_time = format!("{hour12}:{minute:02} {am_pm}");
67
68        match date == today {
69            true => the_time,
70            false if date == today.pred_opt().unwrap_or(today) => format!("yesterday, {the_time}"),
71            false if date.iso_week() == today.iso_week() => format!("{}", dt.format("%A")),
72            false if date.year() == today.year() => format!("{}", dt.format("%B %-d")),
73            false => format!("{}", dt.format("%m/%d/%y")),
74        }
75    }
76
77    pub fn date_friendly(&self) -> String {
78        let dt = self.to_datetime().expect("Invalid date and time");
79        let today = Local::now().date_naive();
80        let date = dt.date_naive();
81
82        if date == today {return "Today".to_string();}
83        if date.iso_week() == today.iso_week() { return format!("{}", dt.format("%A")); }
84        if date.year() == today.year() { return format!("{}", dt.format("%B %-d")); }
85        format!("{}", dt.format("%m/%d/%y"))
86    }
87
88    /// Returns the date.
89    pub fn date(&self) -> String {self.0.clone()}
90    /// Returns the time.
91    pub fn time(&self) -> String {self.1.clone()}
92}
93
94// impl From<String> for PelicanError {
95//     fn from(s: String, ap: impl AppPage) -> Self {
96//         PelicanError::Err(s, ap)
97//     }
98// }
99
100#[derive(Debug, Clone)]
101pub struct TitleSubtitle {
102    pub title: String, 
103    pub subtitle: Option<String>
104}
105
106impl TitleSubtitle {
107    pub fn new(title: &str, subtitle: Option<&str>) -> Self {
108        TitleSubtitle{
109            title: title.to_string(), 
110            subtitle: subtitle.map(|s| s.to_string())
111        }
112    }
113}
114
115
116pub trait ValidationFn: FnMut(&Vec<Box<dyn Drawable>>) -> bool + 'static {
117    fn clone_box(&self) -> Box<dyn ValidationFn>;
118}
119
120impl<F> ValidationFn for F where F: FnMut(&Vec<Box<dyn Drawable>>) -> bool + Clone + 'static {
121    fn clone_box(&self) -> Box<dyn ValidationFn> { Box::new(self.clone()) }
122}
123
124impl Clone for Box<dyn ValidationFn> { fn clone(&self) -> Self { self.as_ref().clone_box() } }
125
126impl std::fmt::Debug for dyn ValidationFn {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {  write!(f, "ValidationFn...") }
128}
129
130
131pub trait Callback: FnMut(&mut Context) + 'static {
132    fn clone_box(&self) -> Box<dyn Callback>;
133}
134
135impl PartialEq for dyn Callback{fn eq(&self, _: &Self) -> bool {true}}
136
137impl<F> Callback for F where F: FnMut(&mut Context) + Clone + 'static {
138    fn clone_box(&self) -> Box<dyn Callback> {
139        Box::new(self.clone())
140    }
141}
142
143impl Clone for Box<dyn Callback> {
144    fn clone(&self) -> Self {
145        self.as_ref().clone_box()
146    }
147}
148
149impl std::fmt::Debug for dyn Callback {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        write!(f, "Clonable Closure")
152    }
153}