Add nanosecond and time_offset fields in struct DateTime.

This commit is contained in:
KAMADA Ken'ichi 2017-10-01 21:17:17 +09:00
parent 836244fde3
commit 1ad83d8a82
3 changed files with 100 additions and 4 deletions

View File

@ -49,6 +49,7 @@
//!
//! * Enum Error has two new variants: TooBig and NotSupported.
//! * Value::Undefined has the 2nd member to keep the offset of the value.
//! * Struct DateTime has two new fields: nanosecond and offset.
pub use error::Error;
pub use jpeg::get_exif_attr as get_exif_attr_from_jpeg;

View File

@ -32,7 +32,7 @@ use tag;
use tag_priv::{Context, Tag};
use value::Value;
use value::get_type_info;
use util::atou16;
use util::{atou16, ctou32};
// TIFF header magic numbers [EXIF23 4.5.2].
const TIFF_BE: u16 = 0x4d4d;
@ -177,6 +177,11 @@ pub struct DateTime {
pub hour: u8,
pub minute: u8,
pub second: u8,
/// The subsecond data in nanoseconds. If the Exif attribute has
/// more sigfinicant digits, they are rounded down.
pub nanosecond: Option<u32>,
/// The offset of the time zone in minutes.
pub offset: Option<i16>,
}
impl DateTime {
@ -198,8 +203,55 @@ impl DateTime {
hour: try!(atou16(&data[11..13])) as u8,
minute: try!(atou16(&data[14..16])) as u8,
second: try!(atou16(&data[17..19])) as u8,
nanosecond: None,
offset: None,
})
}
/// Parses an SubsecTime-like field.
pub fn parse_subsec(&mut self, data: &[u8]) -> Result<(), Error> {
let mut subsec = 0;
let mut ndigits = 0;
for &c in data {
if c == b' ' {
break;
}
subsec = subsec * 10 + try!(ctou32(c));
ndigits += 1;
if ndigits >= 9 {
break;
}
}
if ndigits == 0 {
self.nanosecond = None;
} else {
for _ in ndigits..9 {
subsec *= 10;
}
self.nanosecond = Some(subsec);
}
Ok(())
}
/// Parses an OffsetTime-like field.
pub fn parse_offset(&mut self, data: &[u8]) -> Result<(), Error> {
if data == b" : " || data == b" " {
return Err(Error::BlankValue("OffsetTime is blank"));
} else if data.len() < 6 {
return Err(Error::InvalidFormat("OffsetTime too short"));
} else if data[3] != b':' {
return Err(Error::InvalidFormat("Invalid OffsetTime delimiter"));
}
let hour = try!(atou16(&data[1..3]));
let min = try!(atou16(&data[4..6]));
let offset = (hour * 60 + min) as i16;
self.offset = Some(match data[0] {
b'+' => offset,
b'-' => -offset,
_ => return Err(Error::InvalidFormat("Invalid OffsetTime sign")),
});
Ok(())
}
}
impl fmt::Display for DateTime {
@ -232,4 +284,41 @@ mod tests {
assert_eq!(v.len(), 1);
assert_pat!(v[0].value, Value::Unknown(0xffff, 1, 0x12));
}
#[test]
fn date_time() {
let mut dt = DateTime::from_ascii(b"2016:05:04 03:02:01").unwrap();
assert_eq!(dt.year, 2016);
assert_eq!(format!("{}", dt), "2016-05-04 03:02:01");
dt.parse_subsec(b"987").unwrap();
assert_eq!(dt.nanosecond.unwrap(), 987000000);
dt.parse_subsec(b"000987").unwrap();
assert_eq!(dt.nanosecond.unwrap(), 987000);
dt.parse_subsec(b"987654321").unwrap();
assert_eq!(dt.nanosecond.unwrap(), 987654321);
dt.parse_subsec(b"9876543219").unwrap();
assert_eq!(dt.nanosecond.unwrap(), 987654321);
dt.parse_subsec(b"130 ").unwrap();
assert_eq!(dt.nanosecond.unwrap(), 130000000);
dt.parse_subsec(b"0").unwrap();
assert_eq!(dt.nanosecond.unwrap(), 0);
dt.parse_subsec(b"").unwrap();
assert!(dt.nanosecond.is_none());
dt.parse_subsec(b" ").unwrap();
assert!(dt.nanosecond.is_none());
dt.parse_offset(b"+00:00").unwrap();
assert_eq!(dt.offset.unwrap(), 0);
dt.parse_offset(b"+01:23").unwrap();
assert_eq!(dt.offset.unwrap(), 83);
dt.parse_offset(b"+99:99").unwrap();
assert_eq!(dt.offset.unwrap(), 6039);
dt.parse_offset(b"-01:23").unwrap();
assert_eq!(dt.offset.unwrap(), -83);
dt.parse_offset(b"-99:99").unwrap();
assert_eq!(dt.offset.unwrap(), -6039);
assert_err_pat!(dt.parse_offset(b" : "), Error::BlankValue(_));
assert_err_pat!(dt.parse_offset(b" "), Error::BlankValue(_));
}
}

View File

@ -28,6 +28,8 @@ use std::io;
use error::Error;
const ASCII_0: u8 = 0x30;
const ASCII_9: u8 = 0x39;
const ASCII_A: u8 = 0x41;
const ASCII_Z: u8 = 0x5a;
@ -44,9 +46,6 @@ pub fn read16<R>(reader: &mut R) -> Result<u16, io::Error> where R: io::Read {
// This function must not be called with more than 4 bytes.
pub fn atou16(bytes: &[u8]) -> Result<u16, Error> {
const ASCII_0: u8 = 0x30;
const ASCII_9: u8 = 0x39;
if cfg!(debug_assertions) && bytes.len() >= 5 {
panic!("atou16 accepts up to 4 bytes");
}
@ -63,6 +62,13 @@ pub fn atou16(bytes: &[u8]) -> Result<u16, Error> {
Ok(n)
}
pub fn ctou32(c: u8) -> Result<u32, Error> {
if c < ASCII_0 || ASCII_9 < c {
return Err(Error::InvalidFormat("Not a number"));
}
Ok((c - ASCII_0) as u32)
}
pub fn isupper(c: u8) -> bool {
ASCII_A <= c && c <= ASCII_Z
}