Skip to main content

fatfs/
time.rs

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/// A DOS compatible date.
16///
17/// Used by `DirEntry` time-related methods.
18#[derive(Copy, Clone, Eq, PartialEq, Debug)]
19#[non_exhaustive]
20pub struct Date {
21    /// Full year - [1980, 2107]
22    pub year: u16,
23    /// Month of the year - [1, 12]
24    pub month: u16,
25    /// Day of the month - [1, 31]
26    pub day: u16,
27}
28
29impl Date {
30    /// Creates a new `Date` instance.
31    ///
32    /// * `year` - full year number in the range [1980, 2107]
33    /// * `month` - month of the year in the range [1, 12]
34    /// * `day` - a day of the month in the range [1, 31]
35    ///
36    /// # Panics
37    ///
38    /// Panics if one of provided arguments is out of the supported range.
39    #[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/// A DOS compatible time.
58///
59/// Used by `DirEntry` time-related methods.
60#[derive(Copy, Clone, Eq, PartialEq, Debug)]
61#[non_exhaustive]
62pub struct Time {
63    /// Hours after midnight - [0, 23]
64    pub hour: u16,
65    /// Minutes after the hour - [0, 59]
66    pub min: u16,
67    /// Seconds after the minute - [0, 59]
68    pub sec: u16,
69    /// Milliseconds after the second - [0, 999]
70    pub millis: u16,
71}
72
73impl Time {
74    /// Creates a new `Time` instance.
75    ///
76    /// * `hour` - number of hours after midnight in the range [0, 23]
77    /// * `min` - number of minutes after the hour in the range [0, 59]
78    /// * `sec` - number of seconds after the minute in the range [0, 59]
79    /// * `millis` - number of milliseconds after the second in the range [0, 999]
80    ///
81    /// # Panics
82    ///
83    /// Panics if one of provided arguments is out of the supported range.
84    #[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        // safe cast: value in range [0, 199]
105        #[allow(clippy::cast_possible_truncation)]
106        (dos_time, dos_time_hi_res as u8)
107    }
108}
109
110/// A DOS compatible date and time.
111///
112/// Used by `DirEntry` time-related methods.
113#[derive(Copy, Clone, Eq, PartialEq, Debug)]
114#[non_exhaustive]
115pub struct DateTime {
116    /// A date part
117    pub date: Date,
118    // A time part
119    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(); // safe unwrap unless year is below 0 or above u16::MAX
159        assert!((MIN_YEAR..=MAX_YEAR).contains(&year), "year out of range");
160        Self {
161            year,
162            month: date.month() as u16, // safe cast: value in range [1, 12]
163            day: date.day() as u16,     // safe cast: value in range [1, 31]
164        }
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; // value in the range [0, 1999] (> 999 if leap second)
172        let millis = millis_leap.min(999); // during leap second set milliseconds to 999
173        let date = Date::from(date_time.date());
174        #[allow(clippy::cast_possible_truncation)]
175        let time = Time {
176            hour: date_time.hour() as u16,  // safe cast: value in range [0, 23]
177            min: date_time.minute() as u16, // safe cast: value in range [0, 59]
178            sec: date_time.second() as u16, // safe cast: value in range [0, 59]
179            millis: millis as u16,          // safe cast: value in range [0, 999]
180        };
181        Self::new(date, time)
182    }
183}
184
185/// A current time and date provider.
186///
187/// Provides a custom implementation for a time resolution used when updating directory entry time fields.
188/// `TimeProvider` is specified by the `time_provider` property in `FsOptions` struct.
189pub trait TimeProvider: Debug {
190    fn get_current_date(&self) -> Date;
191    fn get_current_date_time(&self) -> DateTime;
192}
193
194/// `TimeProvider` implementation that returns current local time retrieved from `chrono` crate.
195#[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/// `TimeProvider` implementation that always returns DOS minimal date-time (1980-01-01 00:00:00).
221#[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/// Default time provider implementation.
244///
245/// Defined as `ChronoTimeProvider` if `chrono` feature is enabled. Otherwise defined as `NullTimeProvider`.
246#[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}