1use fidl_next_fuchsia_hardware_rtc as frtc;
6use log::warn;
7
8pub const LOCAL_EPOCH: u64 = 946684800;
10pub const LOCAL_EPOCH_YEAR: u16 = 2000;
11pub const DEFAULT_YEAR: u16 = 2020;
12pub const MAX_YEAR: u16 = 2099;
13
14pub const DAYS_IN_MONTH: [u64; 13] = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
15
16pub const DEFAULT_RTC: frtc::Time = frtc::Time {
17 seconds: 0,
18 minutes: 0,
19 hours: 0,
20 day: 1,
21 month: 1, year: DEFAULT_YEAR,
23};
24
25pub fn is_leap_year(year: u16) -> bool {
26 ((year % 4) == 0 && (year % 100) != 0) || ((year % 400) == 0)
27}
28
29pub fn days_in_year(year: u16) -> u64 {
30 if is_leap_year(year) { 366 } else { 365 }
31}
32
33pub fn days_in_month(month: u8, year: u16) -> u64 {
34 let mut days = DAYS_IN_MONTH[month as usize];
35 if month == 2 && is_leap_year(year) {
36 days += 1;
37 }
38 days
39}
40
41pub fn is_rtc_valid(rtc: &frtc::Time) -> bool {
42 if rtc.year < LOCAL_EPOCH_YEAR || rtc.year > MAX_YEAR {
43 return false;
44 }
45 if rtc.month < 1 || rtc.month > 12 {
46 return false;
47 }
48 if rtc.day as u64 > days_in_month(rtc.month, rtc.year) {
49 return false;
50 }
51 if rtc.day == 0 {
52 return false;
53 }
54 if rtc.hours > 23 || rtc.minutes > 59 || rtc.seconds > 59 {
55 return false;
56 }
57 true
58}
59
60pub fn seconds_to_rtc(seconds: u64) -> frtc::Time {
61 if seconds < LOCAL_EPOCH {
62 warn!("SecondsToRtc: Seconds value is out of range, returning default");
63 return DEFAULT_RTC;
64 }
65
66 let mut epoch = seconds - LOCAL_EPOCH;
67 let mut rtc = frtc::Time {
68 seconds: (epoch % 60) as u8,
69 minutes: 0,
70 hours: 0,
71 day: 0,
72 month: 0,
73 year: LOCAL_EPOCH_YEAR,
74 };
75 epoch /= 60;
76 rtc.minutes = (epoch % 60) as u8;
77 epoch /= 60;
78 rtc.hours = (epoch % 24) as u8;
79 epoch /= 24;
80
81 for year in LOCAL_EPOCH_YEAR.. {
82 let days = days_in_year(year);
83 if epoch < days {
84 rtc.year = year;
85 break;
86 }
87 epoch -= days;
88 }
89
90 for month in 1..=12 {
91 let days = days_in_month(month, rtc.year);
92 if epoch < days {
93 rtc.month = month;
94 break;
95 }
96 epoch -= days;
97 }
98
99 rtc.day = (epoch + 1) as u8;
100 rtc
101}
102
103pub fn seconds_since_epoch(rtc: &frtc::Time) -> u64 {
104 let mut days_since_local_epoch = 0;
105 for year in LOCAL_EPOCH_YEAR..rtc.year {
106 days_since_local_epoch += days_in_year(year);
107 }
108 for month in 1..rtc.month {
109 days_since_local_epoch += days_in_month(month, rtc.year);
110 }
111 days_since_local_epoch += (rtc.day - 1) as u64;
112
113 let hours = (days_since_local_epoch * 24) + rtc.hours as u64;
114 let minutes = (hours * 60) + rtc.minutes as u64;
115 let seconds = (minutes * 60) + rtc.seconds as u64;
116
117 LOCAL_EPOCH + seconds
118}
119
120pub fn sanitize_rtc(rtc: frtc::Time) -> frtc::Time {
121 if !is_rtc_valid(&rtc) || rtc.year < DEFAULT_YEAR {
122 warn!("RTC is sanitized to constant default");
123 return DEFAULT_RTC;
124 }
125 rtc
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_is_leap_year() {
134 assert!(is_leap_year(2000));
135 assert!(is_leap_year(2020));
136 assert!(is_leap_year(2024));
137 assert!(!is_leap_year(2021));
138 assert!(!is_leap_year(2022));
139 assert!(!is_leap_year(2023));
140 assert!(!is_leap_year(2100));
141 }
142
143 #[test]
144 fn test_is_rtc_valid() {
145 let valid =
146 frtc::Time { seconds: 30, minutes: 15, hours: 10, day: 15, month: 6, year: 2024 };
147 assert!(is_rtc_valid(&valid));
148
149 let invalid_year = frtc::Time { year: 1999, ..valid };
150 assert!(!is_rtc_valid(&invalid_year));
151
152 let invalid_year_high = frtc::Time { year: 2100, ..valid };
153 assert!(!is_rtc_valid(&invalid_year_high));
154
155 let invalid_month = frtc::Time { month: 13, ..valid };
156 assert!(!is_rtc_valid(&invalid_month));
157
158 let invalid_day_zero = frtc::Time { day: 0, ..valid };
159 assert!(!is_rtc_valid(&invalid_day_zero));
160
161 let invalid_feb = frtc::Time { day: 29, month: 2, year: 2023, ..valid };
162 assert!(!is_rtc_valid(&invalid_feb));
163
164 let valid_feb = frtc::Time { day: 29, month: 2, year: 2024, ..valid };
165 assert!(is_rtc_valid(&valid_feb));
166
167 let invalid_hours = frtc::Time { hours: 24, ..valid };
168 assert!(!is_rtc_valid(&invalid_hours));
169
170 let invalid_minutes = frtc::Time { minutes: 60, ..valid };
171 assert!(!is_rtc_valid(&invalid_minutes));
172
173 let invalid_seconds = frtc::Time { seconds: 60, ..valid };
174 assert!(!is_rtc_valid(&invalid_seconds));
175 }
176
177 #[test]
178 fn test_seconds_conversion_roundtrip() {
179 let time =
180 frtc::Time { seconds: 45, minutes: 30, hours: 14, day: 28, month: 2, year: 2024 };
181 let seconds = seconds_since_epoch(&time);
182 let converted = seconds_to_rtc(seconds);
183 assert_eq!(converted.seconds, time.seconds);
184 assert_eq!(converted.minutes, time.minutes);
185 assert_eq!(converted.hours, time.hours);
186 assert_eq!(converted.day, time.day);
187 assert_eq!(converted.month, time.month);
188 assert_eq!(converted.year, time.year);
189 }
190
191 #[test]
192 fn test_sanitize_rtc() {
193 let invalid = frtc::Time { seconds: 0, minutes: 0, hours: 0, day: 1, month: 1, year: 2010 };
194 let sanitized = sanitize_rtc(invalid);
195 assert_eq!(sanitized.year, DEFAULT_YEAR);
196 assert_eq!(sanitized.month, 1);
197 assert_eq!(sanitized.day, 1);
198
199 let valid =
200 frtc::Time { seconds: 10, minutes: 20, hours: 8, day: 14, month: 10, year: 2026 };
201 let kept = sanitize_rtc(valid);
202 assert_eq!(kept.year, 2026);
203 assert_eq!(kept.month, 10);
204 assert_eq!(kept.day, 14);
205 }
206}