fat_date_time/
lib.rs

1//! Functions for parsing DOS FAT filesystem date and time values.
2//! This crate provides functions for parsing DOS FAT dates and times.
3//!
4//! It provides two main functions, parse_fat_date and parse_fat_time.
5//! These functions return Date and Time structures from the time crate.
6#![warn(missing_docs)]
7#![warn(unsafe_code)]
8
9use time::{Date, Month, Time};
10
11/// Parse a FAT DOS time.
12/// Assume a value of zero is an invalid date / reserved field
13/// Return None if the time is invalid
14///
15/// From FAT: General Overview of On-Disk Format \
16/// MS-DOS epoch is 01/01/1980 \
17/// Bits 0-4: 2-second count, valid value range 0-29 inclusive (0 - 58 seconds). \
18/// Bits 5-10: Minutes, valid value range 0-59 inclusive. \
19/// Bits 11-15: Hours, valid value range 0-23 inclusive. \
20///
21/// # Examples
22///
23/// ```
24/// use fat_date_time::parse_fat_time;
25///
26/// let time = parse_fat_time(0xbf7d);
27///
28/// assert!(time.is_some());
29/// assert_eq!(time.unwrap().hour(), 23);
30/// assert_eq!(time.unwrap().minute(), 59);
31/// assert_eq!(time.unwrap().second(), 58);
32/// ```
33pub fn parse_fat_time(dos_time: u16) -> Option<Time> {
34    // Assume a value of zero is an "invalid" time and the field is a
35    // "reserved" field
36    // This isn't always true, some utilities may not write a time
37    let hours = ((dos_time >> 11) as u8) & 0x1F;
38    if hours > 23 {
39        return None;
40    }
41    let minutes = ((dos_time >> 5) as u8) & 0x3F;
42    if minutes > 59 {
43        return None;
44    }
45    let seconds = (dos_time & 0x1F) as u8;
46    if seconds > 29 {
47        return None;
48    }
49
50    let time = Time::from_hms(hours, minutes, seconds * 2);
51
52    match time {
53        Ok(t) => Some(t),
54        Err(e) => panic!("Couldn't parse time: {}", e),
55    }
56}
57
58/// Parse a FAT DOS date.
59/// If a date is invalid, a value of None is returned.
60///
61/// From FAT: General Overview of On-Disk Format \
62/// The valid time range is from Midnight 00:00:00 to 23:59:58. \
63/// Bits 0-4: Day of month, valid value range 1-31 inclusive. \
64/// Bits 5-8: Month of year, 1 = January, valid value range 1-12 inclusive. \
65/// Bits 9-15: Count of years from 1980, valid value range 0-127 inclusive (1980-2107). \
66///
67/// # Examples
68///
69/// ```
70/// use fat_date_time::parse_fat_date;
71/// use time::Month;
72///
73/// let date = parse_fat_date(0xff9f);
74///
75/// assert!(date.is_some());
76/// assert_eq!(date.unwrap().year(), 2107);
77/// assert_eq!(date.unwrap().month(), Month::December);
78/// assert_eq!(date.unwrap().day(), 31);
79///
80/// ```
81pub fn parse_fat_date(dos_date: u16) -> Option<Date> {
82    // Assume a value of zero is an "invalid" date and the field is a
83    // "reserved" field
84    // This isn't always true, some utilities may not write a date
85    if dos_date == 0 {
86        return None;
87    }
88
89    let year: i32 = ((dos_date >> 9) & 0x7F) as i32;
90    // equivalent to (year < 0) || (year > 127)
91    if !(0..=127).contains(&year) {
92        return None;
93    }
94
95    let year = year + 1980;
96
97    let month = (dos_date >> 5) & 0x0f;
98
99    let month = match month {
100        1 => Month::January,
101        2 => Month::February,
102        3 => Month::March,
103        4 => Month::April,
104        5 => Month::May,
105        6 => Month::June,
106        7 => Month::July,
107        8 => Month::August,
108        9 => Month::September,
109        10 => Month::October,
110        11 => Month::November,
111        12 => Month::December,
112        _ => return None,
113    };
114
115    let day = (dos_date & 0x1F) as u8;
116
117    // Check that the day value is in range
118    // equivalent to (day < 1) || (day > 31)
119    if !(1..=31).contains(&day) {
120        return None;
121    }
122
123    let date = Date::from_calendar_date(year, month, day);
124
125    match date {
126        Ok(d) => Some(d),
127        Err(e) => panic!("Couldn't parse date: {}", e),
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn parse_fat_date_works() {
137        // Date value of zero
138        let date = parse_fat_date(0);
139        assert!(date.is_none());
140
141        // The earliest possible "valid" date, given the specification in
142        // FAT: General Overview of On-Disk Format
143        let date = parse_fat_date(0b0000000000100001);
144
145        assert!(date.is_some());
146        assert_eq!(date.unwrap().year(), 1980);
147        assert_eq!(date.unwrap().month(), Month::January);
148        assert_eq!(date.unwrap().day(), 1);
149
150        // The latest possible date
151        let date = parse_fat_date(0b1111111110011111);
152
153        assert!(date.is_some());
154        assert_eq!(date.unwrap().year(), 2107);
155        assert_eq!(date.unwrap().month(), Month::December);
156        assert_eq!(date.unwrap().day(), 31);
157
158        // The date with bit 1 set for year, month and day.
159        let date = parse_fat_date(0b0000001000100001);
160
161        assert!(date.is_some());
162        assert_eq!(date.unwrap().year(), 1981);
163        assert_eq!(date.unwrap().month(), Month::January);
164        assert_eq!(date.unwrap().day(), 1);
165
166        // Test date with day < 1
167        let date = parse_fat_date(0b0000000000100000);
168        assert!(date.is_none());
169
170        // Test date with month < 1
171        let date = parse_fat_date(0b0000000000000001);
172        assert!(date.is_none());
173
174        // Test date with month > 12
175        let date = parse_fat_date(0b0000000110100001);
176        assert!(date.is_none());
177    }
178
179    #[test]
180    fn parse_fat_time_works() {
181        // Test the earlier possible time
182        let time = parse_fat_time(0);
183        assert!(time.is_some());
184        assert_eq!(time.unwrap().hour(), 0);
185        assert_eq!(time.unwrap().minute(), 0);
186        assert_eq!(time.unwrap().second(), 0);
187
188        // Test the latest possible time
189        let time = parse_fat_time(0b1011111101111101);
190        assert!(time.is_some());
191        assert_eq!(time.unwrap().hour(), 23);
192        assert_eq!(time.unwrap().minute(), 59);
193        assert_eq!(time.unwrap().second(), 58);
194
195        // Test second value > 29
196        let time = parse_fat_time(0b1011111101111110);
197        assert!(time.is_none());
198
199        // Test minute value > 59
200        let time = parse_fat_time(0b1011111110011101);
201        assert!(time.is_none());
202
203        // Test hour value > 23
204        let time = parse_fat_time(0b1100011101111101);
205        assert!(time.is_none());
206    }
207
208    /// Tests from pyfatfs Python module
209    #[test]
210    fn external_tests_pass() {
211        let date = parse_fat_date(0xFF9F);
212        assert!(date.is_some());
213        assert_eq!(date.unwrap().year(), 2107);
214        assert_eq!(date.unwrap().month(), Month::December);
215        assert_eq!(date.unwrap().day(), 31);
216
217        // maximum time value
218        let date = parse_fat_date(0xBF7D);
219        assert!(date.is_some());
220        assert_eq!(date.unwrap().year(), 2075);
221        assert_eq!(date.unwrap().month(), Month::November);
222        assert_eq!(date.unwrap().day(), 29);
223
224        let time = parse_fat_time(0xBF7D);
225        assert!(time.is_some());
226        assert_eq!(time.unwrap().hour(), 23);
227        assert_eq!(time.unwrap().minute(), 59);
228        assert_eq!(time.unwrap().second(), 58);
229
230        let date = parse_fat_date(0xFF9F);
231        assert!(date.is_some());
232        assert_eq!(date.unwrap().year(), 2107);
233        assert_eq!(date.unwrap().month(), Month::December);
234        assert_eq!(date.unwrap().day(), 31);
235
236        let date = parse_fat_date(0x0021);
237        assert!(date.is_some());
238        assert_eq!(date.unwrap().year(), 1980);
239        assert_eq!(date.unwrap().month(), Month::January);
240        assert_eq!(date.unwrap().day(), 1);
241
242        let time = parse_fat_time(0x477D);
243        assert!(time.is_some());
244        assert_eq!(time.unwrap().hour(), 8);
245        assert_eq!(time.unwrap().minute(), 59);
246        assert_eq!(time.unwrap().second(), 58);
247    }
248}