1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct FatTimestamp {
11 pub unix_seconds: i64,
13 pub subsec_nanos: u32,
15}
16
17pub fn decode(date: u16, time: u16, tenths: u8) -> Option<FatTimestamp> {
21 if date == 0 {
22 return None;
23 }
24 let day = i64::from(date & 0x1F);
25 let month = i64::from((date >> 5) & 0x0F);
26 let year = 1980 + i64::from((date >> 9) & 0x7F);
27
28 let two_sec = i64::from(time & 0x1F);
29 let minute = i64::from((time >> 5) & 0x3F);
30 let hour = i64::from((time >> 11) & 0x1F);
31
32 let days = days_from_civil(year, month.clamp(1, 12), day.clamp(1, 31));
33 let base = days * 86_400 + hour.min(23) * 3600 + minute.min(59) * 60 + two_sec * 2;
34
35 let tenths = i64::from(tenths);
36 let unix_seconds = base + tenths / 100;
37 let subsec_nanos = u32::try_from((tenths % 100) * 10_000_000).unwrap_or(0);
38 Some(FatTimestamp {
39 unix_seconds,
40 subsec_nanos,
41 })
42}
43
44fn days_from_civil(y: i64, m: i64, d: i64) -> i64 {
47 let y = if m <= 2 { y - 1 } else { y };
48 let era = if y >= 0 { y } else { y - 399 } / 400;
49 let yoe = y - era * 400;
50 let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d - 1;
51 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
52 era * 146_097 + doe - 719_468
53}
54
55#[cfg(test)]
56mod tests {
57 use super::{decode, FatTimestamp};
58
59 fn mk_date(year: u16, month: u16, day: u16) -> u16 {
61 ((year - 1980) << 9) | (month << 5) | day
62 }
63
64 fn mk_time(hour: u16, minute: u16, second: u16) -> u16 {
66 (hour << 11) | (minute << 5) | (second / 2)
67 }
68
69 #[test]
70 fn decodes_a_known_datetime() {
71 let ts = decode(mk_date(2021, 6, 15), mk_time(13, 37, 20), 0).unwrap();
72 assert_eq!(ts.unix_seconds, 1_623_764_240);
74 assert_eq!(ts.subsec_nanos, 0);
75 }
76
77 #[test]
78 fn tenths_add_sub_second_and_carry() {
79 let ts = decode(mk_date(2021, 6, 15), mk_time(13, 37, 20), 150).unwrap();
81 assert_eq!(ts.unix_seconds, 1_623_764_241);
82 assert_eq!(ts.subsec_nanos, 500_000_000);
83 }
84
85 #[test]
86 fn zero_date_is_unset() {
87 assert!(decode(0, 0, 0).is_none());
88 }
89
90 #[test]
91 fn epoch_1980_is_positive() {
92 let ts = decode(mk_date(1980, 1, 1), 0, 0).unwrap();
93 assert_eq!(ts.unix_seconds, 315_532_800); }
95
96 #[test]
97 fn typed_fields_accessible() {
98 let _ = FatTimestamp {
99 unix_seconds: 0,
100 subsec_nanos: 0,
101 };
102 }
103}