Skip to main content

embedded_sdmmc/filesystem/
timestamp.rs

1//! Time related code
2
3/// Things that impl this can tell you the current time.
4pub trait TimeSource {
5    /// Returns the current time
6    fn get_timestamp(&self) -> Timestamp;
7}
8
9/// A Gregorian Calendar date/time, in the local time zone.
10///
11/// TODO: Consider replacing this with POSIX time as a `u32`, which would save
12/// two bytes at the expense of some maths.
13#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
14#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
15pub struct Timestamp {
16    /// Add 1970 to this file to get the calendar year
17    pub year_since_1970: u8,
18    /// Add one to this value to get the calendar month
19    pub zero_indexed_month: u8,
20    /// Add one to this value to get the calendar day
21    pub zero_indexed_day: u8,
22    /// The number of hours past midnight
23    pub hours: u8,
24    /// The number of minutes past the hour
25    pub minutes: u8,
26    /// The number of seconds past the minute
27    pub seconds: u8,
28}
29
30impl Timestamp {
31    /// Create a `Timestamp` from the 16-bit FAT date and time fields.
32    pub fn from_fat(date: u16, time: u16) -> Timestamp {
33        let year = 1980 + (date >> 9);
34        let month = ((date >> 5) & 0x000F) as u8;
35        let day = (date & 0x001F) as u8;
36        let hours = ((time >> 11) & 0x001F) as u8;
37        let minutes = ((time >> 5) & 0x0003F) as u8;
38        let seconds = ((time << 1) & 0x0003F) as u8;
39        // Volume labels have a zero for month/day, so tolerate that...
40        Timestamp {
41            year_since_1970: (year - 1970) as u8,
42            zero_indexed_month: if month == 0 { 0 } else { month - 1 },
43            zero_indexed_day: if day == 0 { 0 } else { day - 1 },
44            hours,
45            minutes,
46            seconds,
47        }
48    }
49
50    /// Serialize a `Timestamp` to FAT format
51    pub fn serialize_to_fat(self) -> [u8; 4] {
52        let mut data = [0u8; 4];
53
54        let hours = (u16::from(self.hours) << 11) & (0x1F << 11);
55        let minutes = (u16::from(self.minutes) << 5) & (0x3F << 5);
56        let seconds = (u16::from(self.seconds / 2)) & 0x1F;
57        data[..2].copy_from_slice(&(hours | minutes | seconds).to_le_bytes()[..]);
58
59        let year = if self.year_since_1970 < 10 {
60            0
61        } else {
62            (u16::from(self.year_since_1970 - 10) << 9) & 0xFE00
63        };
64        let month = (u16::from(self.zero_indexed_month + 1) << 5) & 0x01E0;
65        let day = u16::from(self.zero_indexed_day + 1) & 0x001F;
66        data[2..].copy_from_slice(&(year | month | day).to_le_bytes()[..]);
67        data
68    }
69
70    /// Create a `Timestamp` from year/month/day/hour/minute/second.
71    ///
72    /// Values should be given as you'd write then (i.e. 1980, 01, 01, 13, 30,
73    /// 05) is 1980-Jan-01, 1:30:05pm.
74    pub fn from_calendar(
75        year: u16,
76        month: u8,
77        day: u8,
78        hours: u8,
79        minutes: u8,
80        seconds: u8,
81    ) -> Result<Timestamp, &'static str> {
82        Ok(Timestamp {
83            year_since_1970: if (1970..=2097).contains(&year) {
84                (year - 1970) as u8
85            } else {
86                return Err("Bad year");
87            },
88            zero_indexed_month: if (1..=12).contains(&month) {
89                month - 1
90            } else {
91                return Err("Bad month");
92            },
93            zero_indexed_day: if (1..=31).contains(&day) {
94                day - 1
95            } else {
96                return Err("Bad day");
97            },
98            hours: if hours <= 23 {
99                hours
100            } else {
101                return Err("Bad hours");
102            },
103            minutes: if minutes <= 59 {
104                minutes
105            } else {
106                return Err("Bad minutes");
107            },
108            seconds: if seconds <= 59 {
109                seconds
110            } else {
111                return Err("Bad seconds");
112            },
113        })
114    }
115}
116
117impl core::fmt::Debug for Timestamp {
118    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
119        write!(f, "Timestamp({})", self)
120    }
121}
122
123impl core::fmt::Display for Timestamp {
124    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
125        write!(
126            f,
127            "{}-{:02}-{:02} {:02}:{:02}:{:02}",
128            u16::from(self.year_since_1970) + 1970,
129            self.zero_indexed_month + 1,
130            self.zero_indexed_day + 1,
131            self.hours,
132            self.minutes,
133            self.seconds
134        )
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn date_encode1() {
144        // 2018-12-09T19:22:00
145        let ts = Timestamp::from_calendar(2018, 12, 9, 19, 21, 38).unwrap();
146
147        let bytes = ts.serialize_to_fat();
148
149        // values taken from disk.img test image
150        assert_eq!(bytes, [0xb3, 0x9a, 0x89, 0x4d], "{:02x?} is wrong", &bytes);
151    }
152
153    #[test]
154    fn date_decode1() {
155        // 2018-12-09T19:22:00
156        let ts = Timestamp::from_calendar(2018, 12, 9, 19, 21, 38).unwrap();
157
158        // values taken from disk.img test image
159        let actual_ts = Timestamp::from_fat(0x4d89, 0x9ab3);
160
161        assert_eq!(ts, actual_ts);
162    }
163
164    #[test]
165    fn date_encode2() {
166        // 2024-10-25T16:31:14
167        let ts = Timestamp::from_calendar(2024, 10, 25, 16, 31, 14).unwrap();
168
169        let bytes = ts.serialize_to_fat();
170
171        // values taken from disk.img test image
172        assert_eq!(bytes, [0xe7, 0x83, 0x59, 0x59], "{:02x?} is wrong", &bytes);
173    }
174
175    #[test]
176    fn date_decode2() {
177        // 2024-10-25T16:31:14
178        let ts = Timestamp::from_calendar(2024, 10, 25, 16, 31, 14).unwrap();
179
180        // values taken from disk.img test image
181        let actual_ts = Timestamp::from_fat(0x5959, 0x83e7);
182        assert_eq!(ts, actual_ts);
183    }
184    //
185}
186
187// ****************************************************************************
188//
189// End Of File
190//
191// ****************************************************************************