orbital_date_pickers/shared/
clock_time.rs1use orbital_base_components::{DatetimeTimezone, OrbitalDateTime};
2
3pub fn resolve_anchor(
5 value: Option<OrbitalDateTime>,
6 reference_date: OrbitalDateTime,
7 timezone: DatetimeTimezone,
8) -> OrbitalDateTime {
9 let base = value.unwrap_or(reference_date);
10 OrbitalDateTime::from_instant(base.instant(), timezone).start_of_day()
11}
12
13pub fn snap_minute(minute: u32, step: u32) -> u32 {
15 let step = step.max(1);
16 let snapped = ((minute + step / 2) / step) * step;
17 snapped.min(59)
18}
19
20pub fn commit_time(
22 anchor: OrbitalDateTime,
23 hour: u32,
24 minute: u32,
25 second: u32,
26 timezone: DatetimeTimezone,
27) -> Option<OrbitalDateTime> {
28 let anchor = OrbitalDateTime::from_instant(anchor.instant(), timezone).start_of_day();
29 anchor.apply_hms(hour, minute, second)
30}
31
32pub fn generate_time_slots(step_minutes: u32) -> Vec<(u32, u32)> {
34 let step = step_minutes.max(1);
35 let mut slots = Vec::new();
36 let mut total = 0u32;
37 while total < 24 * 60 {
38 slots.push((total / 60, total % 60));
39 total += step;
40 }
41 slots
42}
43
44pub fn format_slot_label(hour: u32, minute: u32, ampm: bool) -> String {
46 if ampm {
47 let (display_hour, period) = to_twelve_hour(hour);
48 format!("{display_hour}:{minute:02} {period}")
49 } else {
50 format!("{hour:02}:{minute:02}")
51 }
52}
53
54pub fn to_twelve_hour(hour: u32) -> (u32, &'static str) {
56 let period = if hour >= 12 { "PM" } else { "AM" };
57 let h = hour % 12;
58 let display = if h == 0 { 12 } else { h };
59 (display, period)
60}
61
62pub fn to_twenty_four_hour(display_hour: u32, is_pm: bool) -> u32 {
64 let h = display_hour % 12;
65 match (h, is_pm) {
66 (0, false) => 0,
67 (0, true) => 12,
68 (hour, false) => hour,
69 (hour, true) => hour + 12,
70 }
71}
72
73pub fn hour_markers(ampm: bool) -> Vec<u32> {
75 if ampm {
76 std::iter::once(12).chain(1..=11).collect()
77 } else {
78 (0..24).collect()
79 }
80}
81
82pub fn minute_markers(step: u32) -> Vec<u32> {
84 let step = step.max(1);
85 (0..60).step_by(step as usize).collect()
86}
87
88pub fn now_on_anchor(anchor: OrbitalDateTime) -> (u32, u32, bool) {
90 let now = OrbitalDateTime::utc_now(anchor.timezone());
91 let (hour, minute, _) = now.hour_minute_second().unwrap_or((0, 0, 0));
92 (hour, minute, hour >= 12)
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use orbital_base_components::{DatetimeTimezone, TryFromUnixSeconds};
99
100 fn sample_ref() -> OrbitalDateTime {
101 OrbitalDateTime::try_from_unix_seconds(1_735_689_600, DatetimeTimezone::Local)
102 .expect("valid reference")
103 }
104
105 #[test]
106 fn snap_minute_rounds_to_step() {
107 assert_eq!(snap_minute(7, 5), 5);
108 assert_eq!(snap_minute(8, 5), 10);
109 assert_eq!(snap_minute(0, 0), 0);
110 }
111
112 #[test]
113 fn generate_time_slots_respects_step() {
114 let slots = generate_time_slots(30);
115 assert_eq!(slots.first(), Some(&(0, 0)));
116 assert!(slots.contains(&(9, 30)));
117 assert_eq!(slots.last(), Some(&(23, 30)));
118 }
119
120 #[test]
121 fn commit_time_applies_hms() {
122 let anchor = sample_ref().start_of_day();
123 let committed = commit_time(anchor, 14, 30, 0, DatetimeTimezone::Local).expect("commit");
124 assert_eq!(committed.hour_minute_second(), Some((14, 30, 0)));
125 }
126
127 #[test]
128 fn resolve_anchor_uses_value_when_present() {
129 let anchor = sample_ref().start_of_day();
130 let with_time = commit_time(anchor, 9, 15, 0, DatetimeTimezone::Local).expect("time");
131 let resolved = resolve_anchor(Some(with_time), sample_ref(), DatetimeTimezone::Local);
132 assert_eq!(resolved.start_of_day(), anchor);
133 }
134
135 #[test]
136 fn to_twelve_hour_converts_correctly() {
137 assert_eq!(to_twelve_hour(0), (12, "AM"));
138 assert_eq!(to_twelve_hour(12), (12, "PM"));
139 assert_eq!(to_twelve_hour(15), (3, "PM"));
140 }
141
142 #[test]
143 fn to_twenty_four_hour_converts_correctly() {
144 assert_eq!(to_twenty_four_hour(12, false), 0);
145 assert_eq!(to_twenty_four_hour(12, true), 12);
146 assert_eq!(to_twenty_four_hour(3, true), 15);
147 }
148}