Skip to content

Commit

Permalink
Merge pull request #499 from epage/signed-offset
Browse files Browse the repository at this point in the history
fix(date)!: Allow negative minute Offsets
  • Loading branch information
epage committed Jan 27, 2023
2 parents 9296c21 + 6f0187e commit 10ea227
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 54 deletions.
8 changes: 8 additions & 0 deletions crates/toml/tests/testsuite/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1050,3 +1050,11 @@ fn serialize_datetime_issue_333() {
.unwrap();
assert_eq!(toml, "date = 2022-01-01\n");
}

#[test]
fn datetime_offset_issue_496() {
let original = "value = 1911-01-01T10:11:12-00:36\n";
let toml = original.parse::<toml::Table>().unwrap();
let output = toml.to_string();
snapbox::assert_eq(original, output);
}
75 changes: 44 additions & 31 deletions crates/toml_datetime/src/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,8 @@ pub struct Datetime {

/// Error returned from parsing a `Datetime` in the `FromStr` implementation.
#[derive(Debug, Clone)]
pub struct DatetimeParseError {
_private: (),
}
#[non_exhaustive]
pub struct DatetimeParseError {}

// Currently serde itself doesn't have a datetime type, so we map our `Datetime`
// to a special value in the serde data model. Namely one with these special
Expand Down Expand Up @@ -180,11 +179,8 @@ pub enum Offset {

/// Offset between local time and UTC
Custom {
/// Hours: -12 to +12
hours: i8,

/// Minutes: 0 to 59
minutes: u8,
/// Minutes: -1_440..1_440
minutes: i16,
},
}

Expand Down Expand Up @@ -247,7 +243,16 @@ impl fmt::Display for Offset {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Offset::Z => write!(f, "Z"),
Offset::Custom { hours, minutes } => write!(f, "{:+03}:{:02}", hours, minutes),
Offset::Custom { mut minutes } => {
let mut sign = '+';
if minutes < 0 {
minutes *= -1;
sign = '-';
}
let hours = minutes / 60;
let minutes = minutes % 60;
write!(f, "{}{:02}:{:02}", sign, hours, minutes)
}
}
}
}
Expand All @@ -263,7 +268,7 @@ impl FromStr for Datetime {
// 0000-00-00
// 00:00:00.00
if date.len() < 3 {
return Err(DatetimeParseError { _private: () });
return Err(DatetimeParseError {});
}
let mut offset_allowed = true;
let mut chars = date.chars();
Expand All @@ -280,15 +285,15 @@ impl FromStr for Datetime {

match chars.next() {
Some('-') => {}
_ => return Err(DatetimeParseError { _private: () }),
_ => return Err(DatetimeParseError {}),
}

let m1 = digit(&mut chars)?;
let m2 = digit(&mut chars)?;

match chars.next() {
Some('-') => {}
_ => return Err(DatetimeParseError { _private: () }),
_ => return Err(DatetimeParseError {}),
}

let d1 = digit(&mut chars)?;
Expand All @@ -301,10 +306,10 @@ impl FromStr for Datetime {
};

if date.month < 1 || date.month > 12 {
return Err(DatetimeParseError { _private: () });
return Err(DatetimeParseError {});
}
if date.day < 1 || date.day > 31 {
return Err(DatetimeParseError { _private: () });
return Err(DatetimeParseError {});
}

Some(date)
Expand All @@ -326,13 +331,13 @@ impl FromStr for Datetime {
let h2 = digit(&mut chars)?;
match chars.next() {
Some(':') => {}
_ => return Err(DatetimeParseError { _private: () }),
_ => return Err(DatetimeParseError {}),
}
let m1 = digit(&mut chars)?;
let m2 = digit(&mut chars)?;
match chars.next() {
Some(':') => {}
_ => return Err(DatetimeParseError { _private: () }),
_ => return Err(DatetimeParseError {}),
}
let s1 = digit(&mut chars)?;
let s2 = digit(&mut chars)?;
Expand All @@ -358,7 +363,7 @@ impl FromStr for Datetime {
}
}
if end == 0 {
return Err(DatetimeParseError { _private: () });
return Err(DatetimeParseError {});
}
chars = whole[end..].chars();
}
Expand All @@ -371,16 +376,16 @@ impl FromStr for Datetime {
};

if time.hour > 24 {
return Err(DatetimeParseError { _private: () });
return Err(DatetimeParseError {});
}
if time.minute > 59 {
return Err(DatetimeParseError { _private: () });
return Err(DatetimeParseError {});
}
if time.second > 59 {
return Err(DatetimeParseError { _private: () });
return Err(DatetimeParseError {});
}
if time.nanosecond > 999_999_999 {
return Err(DatetimeParseError { _private: () });
return Err(DatetimeParseError {});
}

Some(time)
Expand All @@ -401,21 +406,29 @@ impl FromStr for Datetime {
let sign = match next {
Some('+') => 1,
Some('-') => -1,
_ => return Err(DatetimeParseError { _private: () }),
_ => return Err(DatetimeParseError {}),
};
chars.next();
let h1 = digit(&mut chars)? as i8;
let h2 = digit(&mut chars)? as i8;
let h1 = digit(&mut chars)? as i16;
let h2 = digit(&mut chars)? as i16;
match chars.next() {
Some(':') => {}
_ => return Err(DatetimeParseError { _private: () }),
_ => return Err(DatetimeParseError {}),
}
let m1 = digit(&mut chars)? as i16;
let m2 = digit(&mut chars)? as i16;

let hours = h1 * 10 + h2;
let minutes = m1 * 10 + m2;

let total_minutes = sign * (hours * 60 + minutes);

if !((-24 * 60)..=(24 * 60)).contains(&total_minutes) {
return Err(DatetimeParseError {});
}
let m1 = digit(&mut chars)?;
let m2 = digit(&mut chars)?;

Some(Offset::Custom {
hours: sign * (h1 * 10 + h2),
minutes: m1 * 10 + m2,
minutes: total_minutes,
})
}
} else {
Expand All @@ -425,7 +438,7 @@ impl FromStr for Datetime {
// Return an error if we didn't hit eof, otherwise return our parsed
// date
if chars.next().is_some() {
return Err(DatetimeParseError { _private: () });
return Err(DatetimeParseError {});
}

Ok(Datetime {
Expand All @@ -439,7 +452,7 @@ impl FromStr for Datetime {
fn digit(chars: &mut str::Chars<'_>) -> Result<u8, DatetimeParseError> {
match chars.next() {
Some(c) if ('0'..='9').contains(&c) => Ok(c as u8 - b'0'),
_ => Err(DatetimeParseError { _private: () }),
_ => Err(DatetimeParseError {}),
}
}

Expand Down
Loading

0 comments on commit 10ea227

Please sign in to comment.