diff options
Diffstat (limited to 'third_party/rust/msdos_time/src/lib.rs')
-rw-r--r-- | third_party/rust/msdos_time/src/lib.rs | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/third_party/rust/msdos_time/src/lib.rs b/third_party/rust/msdos_time/src/lib.rs new file mode 100644 index 0000000000..6e8643d3a7 --- /dev/null +++ b/third_party/rust/msdos_time/src/lib.rs @@ -0,0 +1,225 @@ +#![warn(missing_docs)] + +//! This crate converts a `Tm` struct to an `MsDosDateTime` and vice-versa +//! +//! MsDosDateTime is based on a FAT datetime and is a compact representation of a date. +//! It is currently mostly used in zip files. + +extern crate time; +#[cfg(windows)] extern crate winapi; + +use std::io; +use time::Tm; + +/// Struct representing the date and time part of an MsDos datetime +#[derive(Copy, Clone, Debug)] +pub struct MsDosDateTime { + /// Part representing the date + pub datepart: u16, + /// Part representing the time + pub timepart: u16, +} + +impl MsDosDateTime { + /// Constructor of an MsDos datetime, from the raw representation + pub fn new(time: u16, date: u16) -> MsDosDateTime { + MsDosDateTime { + datepart: date, + timepart: time, + } + } +} + +/// Trait to convert a time representation to and from a MsDosDateTime +pub trait TmMsDosExt : Sized { + /// Convert a value to MsDosDateTime + fn to_msdos(&self) -> Result<MsDosDateTime, io::Error>; + /// Construct a value from an MsDosDateTime + fn from_msdos(MsDosDateTime) -> Result<Self, io::Error>; +} + +impl TmMsDosExt for Tm { + fn to_msdos(&self) -> Result<MsDosDateTime, io::Error> { + sys::tm_to_msdos(self) + } + + fn from_msdos(ms: MsDosDateTime) -> Result<Self, io::Error> { + sys::msdos_to_tm(ms) + } +} + +#[cfg(not(windows))] +mod sys { + use super::MsDosDateTime; + use time::{self, Tm}; + use std::io; + + pub fn msdos_to_tm(ms: MsDosDateTime) -> Result<Tm, io::Error> { + let seconds = (ms.timepart & 0b0000000000011111) << 1; + let minutes = (ms.timepart & 0b0000011111100000) >> 5; + let hours = (ms.timepart & 0b1111100000000000) >> 11; + let days = (ms.datepart & 0b0000000000011111) >> 0; + let months = (ms.datepart & 0b0000000111100000) >> 5; + let years = (ms.datepart & 0b1111111000000000) >> 9; + + // Month range: Dos [1..12], Tm [0..11] + // Year zero: Dos 1980, Tm 1900 + + let tm = Tm { + tm_sec: seconds as i32, + tm_min: minutes as i32, + tm_hour: hours as i32, + tm_mday: days as i32, + tm_mon: months as i32 - 1, + tm_year: years as i32 + 80, + ..time::empty_tm() + }; + + // Re-parse the possibly incorrect timestamp to get a correct one. + // This ensures every value will be in range + // TODO: Check if this will only panic on Windows, or also other platforms + Ok(time::at_utc(tm.to_timespec())) + } + + pub fn tm_to_msdos(tm: &Tm) -> Result<MsDosDateTime, io::Error> { + let timepart = ((tm.tm_sec >> 1) | (tm.tm_min << 5) | (tm.tm_hour << 11)) as u16; + let datepart = (tm.tm_mday | ((tm.tm_mon + 1) << 5) | ((tm.tm_year - 80) << 9)) as u16; + Ok(MsDosDateTime { datepart: datepart, timepart: timepart }) + } +} + +#[cfg(windows)] +mod sys { + use super::MsDosDateTime; + use time::{self, Tm}; + use winapi::shared::minwindef::{WORD, FILETIME}; + use winapi::um::minwinbase::SYSTEMTIME; + use winapi::um::timezoneapi::{FileTimeToSystemTime, SystemTimeToFileTime}; + use winapi::um::winbase::{DosDateTimeToFileTime, FileTimeToDosDateTime}; + use std::io; + + pub fn msdos_to_tm(ms: MsDosDateTime) -> Result<Tm, io::Error> { + let datepart: WORD = ms.datepart; + let timepart: WORD = ms.timepart; + let mut filetime: FILETIME = unsafe { ::std::mem::zeroed() }; + let mut systemtime: SYSTEMTIME = unsafe { ::std::mem::zeroed() }; + + if unsafe { DosDateTimeToFileTime(datepart, timepart, &mut filetime) } == 0 { + return Err(io::Error::last_os_error()); + } + + if unsafe { FileTimeToSystemTime(&filetime, &mut systemtime) } == 0 { + return Err(io::Error::last_os_error()); + } + + Ok(Tm { + tm_sec: systemtime.wSecond as i32, + tm_min: systemtime.wMinute as i32, + tm_hour: systemtime.wHour as i32, + tm_mday: systemtime.wDay as i32, + tm_wday: systemtime.wDayOfWeek as i32, + tm_mon: (systemtime.wMonth - 1) as i32, + tm_year: (systemtime.wYear - 1900) as i32, + ..time::empty_tm() + }) + } + + pub fn tm_to_msdos(tm: &Tm) -> Result<MsDosDateTime, io::Error> { + let systemtime = SYSTEMTIME { + wYear: (tm.tm_year + 1900) as WORD, + wMonth: (tm.tm_mon + 1) as WORD, + wDayOfWeek: tm.tm_wday as WORD, + wDay: tm.tm_mday as WORD, + wHour: tm.tm_hour as WORD, + wMinute: tm.tm_min as WORD, + wSecond: tm.tm_sec as WORD, + wMilliseconds: 0, + }; + let mut filetime = unsafe { ::std::mem::zeroed() }; + let mut datepart : WORD = 0; + let mut timepart : WORD = 0; + + if unsafe { SystemTimeToFileTime(&systemtime, &mut filetime) } == 0 { + return Err(io::Error::last_os_error()); + } + + if unsafe { FileTimeToDosDateTime(&filetime, &mut datepart, &mut timepart) } == 0 { + return Err(io::Error::last_os_error()); + } + + Ok(MsDosDateTime { datepart: datepart, timepart: timepart }) + } +} + + +#[cfg(test)] +mod test { + use super::*; + use time::{self, Tm}; + + fn check_date(input: Tm, day: i32, month: i32, year: i32) { + assert_eq!(input.tm_mday, day); + assert_eq!(input.tm_mon + 1, month); + assert_eq!(input.tm_year + 1900, year); + } + + fn check_time(input: Tm, hour: i32, minute: i32, second: i32) { + assert_eq!(input.tm_hour, hour); + assert_eq!(input.tm_min, minute); + assert_eq!(input.tm_sec, second); + } + + #[test] + fn dos_zero() { + // The 0 date is not a correct msdos date + // We assert here that it does not panic. What it will return is undefined. + let _ = Tm::from_msdos(MsDosDateTime::new(0, 0)); + } + + #[test] + fn dos_smallest() { + // This is the actual smallest date possible + let tm = Tm::from_msdos(MsDosDateTime::new(0, 0b100001)).unwrap(); + check_date(tm, 1, 1, 1980); + check_time(tm, 0, 0, 0); + } + + #[test] + fn dos_today() { + let tm = Tm::from_msdos(MsDosDateTime::new(0b01001_100000_10101, 0b0100011_0110_11110)).unwrap(); + check_date(tm, 30, 6, 2015); + check_time(tm, 9, 32, 42); + } + + #[test] + fn zero_dos() { + let tm = Tm { + tm_year: 80, + tm_mon: 0, + tm_mday: 1, + tm_hour: 0, + tm_min: 0, + tm_sec: 0, + ..time::empty_tm() + }; + let ms = tm.to_msdos().unwrap(); + assert_eq!(ms.datepart, 0b100001); + assert_eq!(ms.timepart, 0); + } + + #[test] + fn today_dos() { + let tm = Tm { + tm_year: 115, + tm_mon: 5, + tm_mday: 30, + tm_hour: 9, + tm_min: 32, + tm_sec: 42, + ..time::empty_tm() + }; + let ms = tm.to_msdos().unwrap(); + assert_eq!(ms.datepart, 0b0100011_0110_11110); + assert_eq!(ms.timepart, 0b01001_100000_10101); + } +} |