1#[cfg(feature = "chrono")]
2use core::convert::TryFrom;
3use core::fmt::Debug;
4
5#[cfg(feature = "chrono")]
6use chrono::{self, Datelike, Timelike};
7
8const MIN_YEAR: u16 = 1980;
9const MAX_YEAR: u16 = 2107;
10const MIN_MONTH: u16 = 1;
11const MAX_MONTH: u16 = 12;
12const MIN_DAY: u16 = 1;
13const MAX_DAY: u16 = 31;
14
15#[derive(Copy, Clone, Eq, PartialEq, Debug)]
19#[non_exhaustive]
20pub struct Date {
21 pub year: u16,
23 pub month: u16,
25 pub day: u16,
27}
28
29impl Date {
30 #[must_use]
40 pub fn new(year: u16, month: u16, day: u16) -> Self {
41 assert!((MIN_YEAR..=MAX_YEAR).contains(&year), "year out of range");
42 assert!((MIN_MONTH..=MAX_MONTH).contains(&month), "month out of range");
43 assert!((MIN_DAY..=MAX_DAY).contains(&day), "day out of range");
44 Self { year, month, day }
45 }
46
47 pub(crate) fn decode(dos_date: u16) -> Self {
48 let (year, month, day) = ((dos_date >> 9) + MIN_YEAR, (dos_date >> 5) & 0xF, dos_date & 0x1F);
49 Self { year, month, day }
50 }
51
52 pub(crate) fn encode(self) -> u16 {
53 ((self.year - MIN_YEAR) << 9) | (self.month << 5) | self.day
54 }
55}
56
57#[derive(Copy, Clone, Eq, PartialEq, Debug)]
61#[non_exhaustive]
62pub struct Time {
63 pub hour: u16,
65 pub min: u16,
67 pub sec: u16,
69 pub millis: u16,
71}
72
73impl Time {
74 #[must_use]
85 pub fn new(hour: u16, min: u16, sec: u16, millis: u16) -> Self {
86 assert!(hour <= 23, "hour out of range");
87 assert!(min <= 59, "min out of range");
88 assert!(sec <= 59, "sec out of range");
89 assert!(millis <= 999, "millis out of range");
90 Self { hour, min, sec, millis }
91 }
92
93 pub(crate) fn decode(dos_time: u16, dos_time_hi_res: u8) -> Self {
94 let hour = dos_time >> 11;
95 let min = (dos_time >> 5) & 0x3F;
96 let sec = (dos_time & 0x1F) * 2 + u16::from(dos_time_hi_res / 100);
97 let millis = u16::from(dos_time_hi_res % 100) * 10;
98 Self { hour, min, sec, millis }
99 }
100
101 pub(crate) fn encode(self) -> (u16, u8) {
102 let dos_time = (self.hour << 11) | (self.min << 5) | (self.sec / 2);
103 let dos_time_hi_res = (self.millis / 10) + (self.sec % 2) * 100;
104 #[allow(clippy::cast_possible_truncation)]
106 (dos_time, dos_time_hi_res as u8)
107 }
108}
109
110#[derive(Copy, Clone, Eq, PartialEq, Debug)]
114#[non_exhaustive]
115pub struct DateTime {
116 pub date: Date,
118 pub time: Time,
120}
121
122impl DateTime {
123 #[must_use]
124 pub fn new(date: Date, time: Time) -> Self {
125 Self { date, time }
126 }
127
128 pub(crate) fn decode(dos_date: u16, dos_time: u16, dos_time_hi_res: u8) -> Self {
129 Self::new(Date::decode(dos_date), Time::decode(dos_time, dos_time_hi_res))
130 }
131}
132
133#[cfg(feature = "chrono")]
134impl From<Date> for chrono::NaiveDate {
135 fn from(date: Date) -> Self {
136 chrono::NaiveDate::from_ymd_opt(i32::from(date.year), u32::from(date.month), u32::from(date.day)).unwrap()
137 }
138}
139
140#[cfg(feature = "chrono")]
141impl From<DateTime> for chrono::NaiveDateTime {
142 fn from(date_time: DateTime) -> Self {
143 chrono::NaiveDate::from(date_time.date)
144 .and_hms_milli_opt(
145 u32::from(date_time.time.hour),
146 u32::from(date_time.time.min),
147 u32::from(date_time.time.sec),
148 u32::from(date_time.time.millis),
149 )
150 .unwrap()
151 }
152}
153
154#[cfg(feature = "chrono")]
155impl From<chrono::NaiveDate> for Date {
156 fn from(date: chrono::NaiveDate) -> Self {
157 #[allow(clippy::cast_sign_loss)]
158 let year = u16::try_from(date.year()).unwrap(); assert!((MIN_YEAR..=MAX_YEAR).contains(&year), "year out of range");
160 Self {
161 year,
162 month: date.month() as u16, day: date.day() as u16, }
165 }
166}
167
168#[cfg(feature = "chrono")]
169impl From<chrono::NaiveDateTime> for DateTime {
170 fn from(date_time: chrono::NaiveDateTime) -> Self {
171 let millis_leap = date_time.nanosecond() / 1_000_000; let millis = millis_leap.min(999); let date = Date::from(date_time.date());
174 #[allow(clippy::cast_possible_truncation)]
175 let time = Time {
176 hour: date_time.hour() as u16, min: date_time.minute() as u16, sec: date_time.second() as u16, millis: millis as u16, };
181 Self::new(date, time)
182 }
183}
184
185pub trait TimeProvider: Debug {
190 fn get_current_date(&self) -> Date;
191 fn get_current_date_time(&self) -> DateTime;
192}
193
194#[cfg(feature = "chrono")]
196#[derive(Debug, Clone, Copy, Default)]
197pub struct ChronoTimeProvider {
198 _dummy: (),
199}
200
201#[cfg(feature = "chrono")]
202impl ChronoTimeProvider {
203 #[must_use]
204 pub fn new() -> Self {
205 Self { _dummy: () }
206 }
207}
208
209#[cfg(feature = "chrono")]
210impl TimeProvider for ChronoTimeProvider {
211 fn get_current_date(&self) -> Date {
212 Date::from(chrono::Local::now().date_naive())
213 }
214
215 fn get_current_date_time(&self) -> DateTime {
216 DateTime::from(chrono::Local::now().naive_local())
217 }
218}
219
220#[derive(Debug, Clone, Copy, Default)]
222pub struct NullTimeProvider {
223 _dummy: (),
224}
225
226impl NullTimeProvider {
227 #[must_use]
228 pub fn new() -> Self {
229 Self { _dummy: () }
230 }
231}
232
233impl TimeProvider for NullTimeProvider {
234 fn get_current_date(&self) -> Date {
235 Date::decode(0)
236 }
237
238 fn get_current_date_time(&self) -> DateTime {
239 DateTime::decode(0, 0, 0)
240 }
241}
242
243#[cfg(feature = "chrono")]
247pub type DefaultTimeProvider = ChronoTimeProvider;
248#[cfg(not(feature = "chrono"))]
249pub type DefaultTimeProvider = NullTimeProvider;
250
251#[cfg(test)]
252mod tests {
253 use super::{Date, DateTime, Time};
254
255 #[test]
256 fn date_new_no_panic_1980() {
257 let _ = Date::new(1980, 1, 1);
258 }
259
260 #[test]
261 #[should_panic]
262 fn date_new_panic_year_1979() {
263 let _ = Date::new(1979, 12, 31);
264 }
265
266 #[test]
267 fn date_new_no_panic_2107() {
268 let _ = Date::new(2107, 12, 31);
269 }
270
271 #[test]
272 #[should_panic]
273 fn date_new_panic_year_2108() {
274 let _ = Date::new(2108, 1, 1);
275 }
276
277 #[test]
278 fn date_encode_decode() {
279 let d = Date::new(2055, 7, 23);
280 let x = d.encode();
281 assert_eq!(x, 38647);
282 assert_eq!(d, Date::decode(x));
283 }
284
285 #[test]
286 fn time_encode_decode() {
287 let t1 = Time::new(15, 3, 29, 990);
288 let t2 = Time { sec: 18, ..t1 };
289 let t3 = Time { millis: 40, ..t1 };
290 let (x1, y1) = t1.encode();
291 let (x2, y2) = t2.encode();
292 let (x3, y3) = t3.encode();
293 assert_eq!((x1, y1), (30830, 199));
294 assert_eq!((x2, y2), (30825, 99));
295 assert_eq!((x3, y3), (30830, 104));
296 assert_eq!(t1, Time::decode(x1, y1));
297 assert_eq!(t2, Time::decode(x2, y2));
298 assert_eq!(t3, Time::decode(x3, y3));
299 }
300
301 #[test]
302 fn date_time_from_chrono_leap_second() {
303 let chrono_date_time = chrono::NaiveDate::from_ymd_opt(2016, 12, 31)
304 .unwrap()
305 .and_hms_milli_opt(23, 59, 59, 1999)
306 .unwrap();
307 let date_time = DateTime::from(chrono_date_time);
308 assert_eq!(
309 date_time,
310 DateTime::new(Date::new(2016, 12, 31), Time::new(23, 59, 59, 999))
311 );
312 }
313}