1use bacnet_types::bitstring::DaysOfWeek;
4use bacnet_types::calendar::SpecificDate;
5use bacnet_types::primitives::{Date, Time};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct ClockFrame {
10 pub local_date: Date,
12 pub local_time: Time,
14 pub utc_offset: i16,
16 pub daylight_savings_status: bool,
18}
19
20impl ClockFrame {
21 pub fn day_of_week(self) -> Option<DaysOfWeek> {
24 (1..=7)
25 .contains(&self.local_date.day_of_week)
26 .then(|| DaysOfWeek::from_bits_truncate(1 << (self.local_date.day_of_week - 1)))
27 }
28
29 pub fn is_valid_actual_datetime(self) -> bool {
32 SpecificDate::from_date(&self.local_date)
33 .is_some_and(|day| day.weekday() == self.local_date.day_of_week)
34 && self.local_time.is_specific()
35 }
36}
37
38pub trait ClockReader: Send + Sync {
43 fn read_clock(&self) -> Option<ClockFrame>;
45}
46
47pub(crate) fn stamp_datetime(clock: Option<&dyn ClockReader>) -> (Date, Time) {
52 current_datetime(clock).unwrap_or(UNSPECIFIED_DATETIME)
53}
54
55pub(crate) fn current_datetime(clock: Option<&dyn ClockReader>) -> Option<(Date, Time)> {
58 clock
59 .and_then(ClockReader::read_clock)
60 .filter(|frame| frame.is_valid_actual_datetime())
61 .map(|frame| (frame.local_date, frame.local_time))
62}
63
64pub(crate) const UNSPECIFIED_DATETIME: (Date, Time) = (
67 Date {
68 year: Date::UNSPECIFIED,
69 month: Date::UNSPECIFIED,
70 day: Date::UNSPECIFIED,
71 day_of_week: Date::UNSPECIFIED,
72 },
73 Time {
74 hour: Time::UNSPECIFIED,
75 minute: Time::UNSPECIFIED,
76 second: Time::UNSPECIFIED,
77 hundredths: Time::UNSPECIFIED,
78 },
79);
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 fn leap_day_frame() -> ClockFrame {
86 ClockFrame {
87 local_date: Date {
88 year: 124,
89 month: 2,
90 day: 29,
91 day_of_week: 4,
92 },
93 local_time: Time {
94 hour: 23,
95 minute: 59,
96 second: 59,
97 hundredths: 99,
98 },
99 utc_offset: 0,
100 daylight_savings_status: false,
101 }
102 }
103
104 #[test]
105 fn actual_datetime_validation_checks_calendar_weekday_and_time() {
106 let valid = leap_day_frame();
107 assert!(valid.is_valid_actual_datetime());
108
109 for invalid in [
110 ClockFrame {
111 local_date: Date {
112 day: 30,
113 ..valid.local_date
114 },
115 ..valid
116 },
117 ClockFrame {
118 local_date: Date {
119 day_of_week: 5,
120 ..valid.local_date
121 },
122 ..valid
123 },
124 ClockFrame {
125 local_time: Time {
126 hour: Time::UNSPECIFIED,
127 ..valid.local_time
128 },
129 ..valid
130 },
131 ] {
132 assert!(!invalid.is_valid_actual_datetime());
133 }
134 }
135}