aimcal_core/datetime/
util.rs1use chrono::{DateTime, NaiveDateTime, NaiveTime, TimeZone, Utc, offset::LocalResult};
6
7pub const STABLE_FORMAT_DATEONLY: &str = "%Y-%m-%d";
9pub const STABLE_FORMAT_FLOATING: &str = "%Y-%m-%dT%H:%M:%S";
10pub const STABLE_FORMAT_LOCAL: &str = "%Y-%m-%dT%H:%M:%S%z";
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum RangePosition {
15 Before,
17
18 InRange,
20
21 After,
23
24 InvalidRange,
26}
27
28pub const fn start_of_day_naive() -> NaiveTime {
29 NaiveTime::from_hms_opt(0, 0, 0).expect("00:00:00 must exist in NaiveTime")
30}
31
32pub const fn end_of_day_naive() -> NaiveTime {
34 NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999)
35 .expect("23:59:59:1_999_999_999 must exist in NaiveTime")
36}
37
38pub fn start_of_day<Tz: TimeZone>(dt: &DateTime<Tz>) -> DateTime<Tz> {
40 let naive = NaiveDateTime::new(dt.date_naive(), start_of_day_naive());
41 from_local_datetime(&dt.timezone(), naive)
42}
43
44pub fn end_of_day<Tz: TimeZone>(dt: &DateTime<Tz>) -> DateTime<Tz> {
46 let last_nano_sec = end_of_day_naive();
47 let naive = NaiveDateTime::new(dt.date_naive(), last_nano_sec);
48 from_local_datetime(&dt.timezone(), naive)
49}
50
51pub fn from_local_datetime<Tz: TimeZone>(tz: &Tz, naive: NaiveDateTime) -> DateTime<Tz> {
57 match tz.from_local_datetime(&naive) {
58 LocalResult::Single(x) => x,
59 LocalResult::Ambiguous(a, b) => {
60 if a <= b { a } else { b }
62 }
63 LocalResult::None => Utc.from_utc_datetime(&naive).with_timezone(tz),
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use chrono::{TimeZone, Utc};
70
71 use super::*;
72
73 #[test]
74 fn test_start_of_day_naive() {
75 let time = start_of_day_naive();
76 assert!(time <= NaiveTime::from_hms_opt(0, 0, 0).unwrap());
77 }
78
79 #[test]
80 fn test_end_of_day_naive() {
81 let time = end_of_day_naive();
82 assert!(time >= NaiveTime::from_hms_opt(23, 59, 59).unwrap());
83 }
84
85 #[test]
86 fn test_start_of_day() {
87 let dt = Utc.with_ymd_and_hms(2025, 1, 15, 14, 30, 45).unwrap();
88 let start = start_of_day(&dt);
89 assert_eq!(start.date_naive(), dt.date_naive());
90 assert!(start <= Utc.with_ymd_and_hms(2025, 1, 15, 14, 30, 45).unwrap());
91 }
92
93 #[test]
94 fn test_end_of_day() {
95 let dt = Utc.with_ymd_and_hms(2025, 1, 15, 14, 30, 45).unwrap();
96 let end = end_of_day(&dt);
97 assert_eq!(end.date_naive(), dt.date_naive());
98 assert!(end >= Utc.with_ymd_and_hms(2025, 1, 15, 14, 30, 45).unwrap());
99 }
100
101 #[test]
102 fn test_from_local_datetime_single() {
103 let tz = Utc;
104 let dt = DateTime::from_timestamp(1609459200, 0).unwrap(); let naive = dt.naive_utc();
107 let result = from_local_datetime(&tz, naive);
108 assert_eq!(result.timestamp(), 1609459200);
109 }
110
111 #[test]
112 fn test_start_end_of_day_constants() {
113 let start = start_of_day_naive();
115 let end = end_of_day_naive();
116
117 assert_eq!(start, NaiveTime::from_hms_opt(0, 0, 0).unwrap());
118 assert_eq!(
119 end,
120 NaiveTime::from_hms_nano_opt(23, 59, 59, 1_999_999_999).unwrap()
121 );
122 }
123}