Skip to content

Commit 10ea227

Browse files
authored
Merge pull request #499 from epage/signed-offset
fix(date)!: Allow negative minute Offsets
2 parents 9296c21 + 6f0187e commit 10ea227

File tree

3 files changed

+231
-54
lines changed

3 files changed

+231
-54
lines changed

crates/toml/tests/testsuite/serde.rs

+8
Original file line numberDiff line numberDiff line change
@@ -1050,3 +1050,11 @@ fn serialize_datetime_issue_333() {
10501050
.unwrap();
10511051
assert_eq!(toml, "date = 2022-01-01\n");
10521052
}
1053+
1054+
#[test]
1055+
fn datetime_offset_issue_496() {
1056+
let original = "value = 1911-01-01T10:11:12-00:36\n";
1057+
let toml = original.parse::<toml::Table>().unwrap();
1058+
let output = toml.to_string();
1059+
snapbox::assert_eq(original, output);
1060+
}

crates/toml_datetime/src/datetime.rs

+44-31
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,8 @@ pub struct Datetime {
9494

9595
/// Error returned from parsing a `Datetime` in the `FromStr` implementation.
9696
#[derive(Debug, Clone)]
97-
pub struct DatetimeParseError {
98-
_private: (),
99-
}
97+
#[non_exhaustive]
98+
pub struct DatetimeParseError {}
10099

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

181180
/// Offset between local time and UTC
182181
Custom {
183-
/// Hours: -12 to +12
184-
hours: i8,
185-
186-
/// Minutes: 0 to 59
187-
minutes: u8,
182+
/// Minutes: -1_440..1_440
183+
minutes: i16,
188184
},
189185
}
190186

@@ -247,7 +243,16 @@ impl fmt::Display for Offset {
247243
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248244
match *self {
249245
Offset::Z => write!(f, "Z"),
250-
Offset::Custom { hours, minutes } => write!(f, "{:+03}:{:02}", hours, minutes),
246+
Offset::Custom { mut minutes } => {
247+
let mut sign = '+';
248+
if minutes < 0 {
249+
minutes *= -1;
250+
sign = '-';
251+
}
252+
let hours = minutes / 60;
253+
let minutes = minutes % 60;
254+
write!(f, "{}{:02}:{:02}", sign, hours, minutes)
255+
}
251256
}
252257
}
253258
}
@@ -263,7 +268,7 @@ impl FromStr for Datetime {
263268
// 0000-00-00
264269
// 00:00:00.00
265270
if date.len() < 3 {
266-
return Err(DatetimeParseError { _private: () });
271+
return Err(DatetimeParseError {});
267272
}
268273
let mut offset_allowed = true;
269274
let mut chars = date.chars();
@@ -280,15 +285,15 @@ impl FromStr for Datetime {
280285

281286
match chars.next() {
282287
Some('-') => {}
283-
_ => return Err(DatetimeParseError { _private: () }),
288+
_ => return Err(DatetimeParseError {}),
284289
}
285290

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

289294
match chars.next() {
290295
Some('-') => {}
291-
_ => return Err(DatetimeParseError { _private: () }),
296+
_ => return Err(DatetimeParseError {}),
292297
}
293298

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

303308
if date.month < 1 || date.month > 12 {
304-
return Err(DatetimeParseError { _private: () });
309+
return Err(DatetimeParseError {});
305310
}
306311
if date.day < 1 || date.day > 31 {
307-
return Err(DatetimeParseError { _private: () });
312+
return Err(DatetimeParseError {});
308313
}
309314

310315
Some(date)
@@ -326,13 +331,13 @@ impl FromStr for Datetime {
326331
let h2 = digit(&mut chars)?;
327332
match chars.next() {
328333
Some(':') => {}
329-
_ => return Err(DatetimeParseError { _private: () }),
334+
_ => return Err(DatetimeParseError {}),
330335
}
331336
let m1 = digit(&mut chars)?;
332337
let m2 = digit(&mut chars)?;
333338
match chars.next() {
334339
Some(':') => {}
335-
_ => return Err(DatetimeParseError { _private: () }),
340+
_ => return Err(DatetimeParseError {}),
336341
}
337342
let s1 = digit(&mut chars)?;
338343
let s2 = digit(&mut chars)?;
@@ -358,7 +363,7 @@ impl FromStr for Datetime {
358363
}
359364
}
360365
if end == 0 {
361-
return Err(DatetimeParseError { _private: () });
366+
return Err(DatetimeParseError {});
362367
}
363368
chars = whole[end..].chars();
364369
}
@@ -371,16 +376,16 @@ impl FromStr for Datetime {
371376
};
372377

373378
if time.hour > 24 {
374-
return Err(DatetimeParseError { _private: () });
379+
return Err(DatetimeParseError {});
375380
}
376381
if time.minute > 59 {
377-
return Err(DatetimeParseError { _private: () });
382+
return Err(DatetimeParseError {});
378383
}
379384
if time.second > 59 {
380-
return Err(DatetimeParseError { _private: () });
385+
return Err(DatetimeParseError {});
381386
}
382387
if time.nanosecond > 999_999_999 {
383-
return Err(DatetimeParseError { _private: () });
388+
return Err(DatetimeParseError {});
384389
}
385390

386391
Some(time)
@@ -401,21 +406,29 @@ impl FromStr for Datetime {
401406
let sign = match next {
402407
Some('+') => 1,
403408
Some('-') => -1,
404-
_ => return Err(DatetimeParseError { _private: () }),
409+
_ => return Err(DatetimeParseError {}),
405410
};
406411
chars.next();
407-
let h1 = digit(&mut chars)? as i8;
408-
let h2 = digit(&mut chars)? as i8;
412+
let h1 = digit(&mut chars)? as i16;
413+
let h2 = digit(&mut chars)? as i16;
409414
match chars.next() {
410415
Some(':') => {}
411-
_ => return Err(DatetimeParseError { _private: () }),
416+
_ => return Err(DatetimeParseError {}),
417+
}
418+
let m1 = digit(&mut chars)? as i16;
419+
let m2 = digit(&mut chars)? as i16;
420+
421+
let hours = h1 * 10 + h2;
422+
let minutes = m1 * 10 + m2;
423+
424+
let total_minutes = sign * (hours * 60 + minutes);
425+
426+
if !((-24 * 60)..=(24 * 60)).contains(&total_minutes) {
427+
return Err(DatetimeParseError {});
412428
}
413-
let m1 = digit(&mut chars)?;
414-
let m2 = digit(&mut chars)?;
415429

416430
Some(Offset::Custom {
417-
hours: sign * (h1 * 10 + h2),
418-
minutes: m1 * 10 + m2,
431+
minutes: total_minutes,
419432
})
420433
}
421434
} else {
@@ -425,7 +438,7 @@ impl FromStr for Datetime {
425438
// Return an error if we didn't hit eof, otherwise return our parsed
426439
// date
427440
if chars.next().is_some() {
428-
return Err(DatetimeParseError { _private: () });
441+
return Err(DatetimeParseError {});
429442
}
430443

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

0 commit comments

Comments
 (0)