Skip to content

Commit ad351eb

Browse files
authored
Merge pull request #46 from hoodie/feature/fix-duration-permissiveness
Feature/fix duration permissiveness
2 parents 2a5e104 + c0ffee6 commit ad351eb

File tree

5 files changed

+158
-83
lines changed

5 files changed

+158
-83
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
## [Unreleased](https://github.com/badboy/iso8601/compare/v0.5.0...main) - ReleaseDate
66

7+
* Fix accepted duration representations
8+
79
## [0.5.0](https://github.com/badboy/iso8601/compare/v0.4.2...v0.5.0) - 2022-07-29
810

911
* Replace rounding-error prone floating point code with robust integer code ([#36](https://github.com/badboy/iso8601/pull/36) by @plugwash)

src/assert.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ macro_rules! assert_parser {
1313
use std::string::ToString;
1414

1515
let (rest, parsed) = $parser($line.as_bytes()).unwrap();
16-
crate::assert::print_result($line, &rest, &parsed);
17-
16+
if std::env::var("VERBOSE_TEST_OUTPUT").is_ok() {
17+
$crate::assert::print_result($line, &rest, &parsed);
18+
}
1819
assert_eq!(
1920
parsed, $expectation,
2021
"{:?} not parsed as expected (leftover: {:?})",

src/lib.rs

+13
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,19 @@ pub struct DateTime {
8080
}
8181

8282
/// A time duration.
83+
84+
/// Durations:
85+
/// https://www.rfc-editor.org/rfc/rfc3339#page-13
86+
/// dur-second = 1*DIGIT "S"
87+
/// dur-minute = 1*DIGIT "M" [dur-second]
88+
/// dur-hour = 1*DIGIT "H" [dur-minute]
89+
/// dur-time = "T" (dur-hour / dur-minute / dur-second)
90+
/// dur-day = 1*DIGIT "D"
91+
/// dur-week = 1*DIGIT "W"
92+
/// dur-month = 1*DIGIT "M" [dur-day]
93+
/// dur-year = 1*DIGIT "Y" [dur-month]
94+
/// dur-date = (dur-day / dur-month / dur-year) [dur-time]
95+
/// duration = "P" (dur-date / dur-time / dur-week)
8396
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
8497
pub enum Duration {
8598
/// A duration specified by year, month, day, hour, minute and second units

src/parsers.rs

+35-53
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,6 @@ fn take_n_digits(i: &[u8], n: usize) -> IResult<&[u8], u32> {
5454
Ok((i, res))
5555
}
5656

57-
fn take_m_to_n_digits(i: &[u8], m: usize, n: usize) -> IResult<&[u8], u32> {
58-
let (i, digits) = take_while_m_n(m, n, is_digit)(i)?;
59-
60-
let s = str::from_utf8(digits).expect("Invalid data, expected UTF-8 string");
61-
let res = s
62-
.parse()
63-
.expect("Invalid string, expected ASCII representation of a number");
64-
65-
Ok((i, res))
66-
}
67-
6857
fn n_digit_in_range(
6958
i: &[u8],
7059
n: usize,
@@ -79,21 +68,6 @@ fn n_digit_in_range(
7968
}
8069
}
8170

82-
fn m_to_n_digit_in_range(
83-
i: &[u8],
84-
m: usize,
85-
n: usize,
86-
range: impl core::ops::RangeBounds<u32>,
87-
) -> IResult<&[u8], u32> {
88-
let (new_i, number) = take_m_to_n_digits(i, m, n)?;
89-
90-
if range.contains(&number) {
91-
Ok((new_i, number))
92-
} else {
93-
Err(Err::Error(Error::new(i, nom::error::ErrorKind::Eof)))
94-
}
95-
}
96-
9771
fn sign(i: &[u8]) -> IResult<&[u8], i32> {
9872
map(alt((tag(b"-"), tag(b"+"))), |s: &[u8]| match s {
9973
b"-" => -1,
@@ -273,50 +247,61 @@ pub fn parse_datetime(i: &[u8]) -> IResult<&[u8], DateTime> {
273247

274248
// DURATION
275249

276-
// Y[YYY...]
250+
/// dur-year = 1*DIGIT "Y" [dur-month]
277251
fn duration_year(i: &[u8]) -> IResult<&[u8], u32> {
278-
take_digits(i)
252+
terminated(take_digits, tag(b"Y"))(i)
279253
}
280254

281-
// M[M]
255+
/// dur-month = 1*DIGIT "M" [dur-day]
282256
fn duration_month(i: &[u8]) -> IResult<&[u8], u32> {
283-
m_to_n_digit_in_range(i, 1, 2, 0..=12)
257+
terminated(take_digits, tag(b"M"))(i)
284258
}
285259

286-
// W[W]
260+
/// dur-week = 1*DIGIT "W"
287261
fn duration_week(i: &[u8]) -> IResult<&[u8], u32> {
288-
m_to_n_digit_in_range(i, 1, 2, 0..=52)
262+
terminated(take_digits, tag(b"W"))(i)
289263
}
290264

291-
// D[D]
265+
// dur-day = 1*DIGIT "D"
292266
fn duration_day(i: &[u8]) -> IResult<&[u8], u32> {
293-
m_to_n_digit_in_range(i, 1, 2, 0..=31)
267+
terminated(take_digits, tag(b"D"))(i)
294268
}
295269

296-
// H[H]
270+
/// dur-hour = 1*DIGIT "H" [dur-minute]
271+
/// dur-time = "T" (dur-hour / dur-minute / dur-second)
297272
fn duration_hour(i: &[u8]) -> IResult<&[u8], u32> {
298-
m_to_n_digit_in_range(i, 1, 2, 0..=24)
273+
terminated(take_digits, tag(b"H"))(i)
299274
}
300275

301-
// M[M]
276+
/// dur-minute = 1*DIGIT "M" [dur-second]
302277
fn duration_minute(i: &[u8]) -> IResult<&[u8], u32> {
303-
m_to_n_digit_in_range(i, 1, 2, 0..=60)
278+
terminated(take_digits, tag(b"M"))(i)
304279
}
305280

306-
// S[S][[,.][MS]]
307-
fn duration_second_and_millisecond(i: &[u8]) -> IResult<&[u8], (u32, u32)> {
308-
let (i, s) = m_to_n_digit_in_range(i, 1, 2, 0..=60)?;
309-
let (i, ms) = opt(preceded(one_of(",."), fraction_millisecond))(i)?;
281+
//// dur-second = 1*DIGIT "S"
282+
fn duration_second(i: &[u8]) -> IResult<&[u8], u32> {
283+
terminated(take_digits, tag(b"S"))(i)
284+
}
310285

311-
Ok((i, (s, ms.unwrap_or(0))))
286+
/// dur-second-ext = 1*DIGIT (,|.) 1*DIGIT "S"
287+
fn duration_second_and_millisecond(i: &[u8]) -> IResult<&[u8], (u32, u32)> {
288+
alt((
289+
// no milliseconds
290+
map(duration_second, |m| (m, 0)),
291+
terminated(
292+
// with milliseconds
293+
separated_pair(take_digits, one_of(",."), fraction_millisecond),
294+
tag(b"S"),
295+
),
296+
))(i)
312297
}
313298

314299
fn duration_time(i: &[u8]) -> IResult<&[u8], (u32, u32, u32, u32)> {
315300
map(
316301
tuple((
317-
opt(terminated(duration_hour, tag(b"H"))),
318-
opt(terminated(duration_minute, tag(b"M"))),
319-
opt(terminated(duration_second_and_millisecond, tag(b"S"))),
302+
opt(duration_hour),
303+
opt(duration_minute),
304+
opt(duration_second_and_millisecond),
320305
)),
321306
|(h, m, s)| {
322307
let (s, ms) = s.unwrap_or((0, 0));
@@ -331,9 +316,9 @@ fn duration_ymdhms(i: &[u8]) -> IResult<&[u8], Duration> {
331316
preceded(
332317
tag(b"P"),
333318
tuple((
334-
opt(terminated(duration_year, tag(b"Y"))),
335-
opt(terminated(duration_month, tag(b"M"))),
336-
opt(terminated(duration_day, tag(b"D"))),
319+
opt(duration_year),
320+
opt(duration_month),
321+
opt(duration_day),
337322
opt(preceded(tag(b"T"), duration_time)),
338323
)),
339324
),
@@ -359,10 +344,7 @@ fn duration_ymdhms(i: &[u8]) -> IResult<&[u8], Duration> {
359344
}
360345

361346
fn duration_weeks(i: &[u8]) -> IResult<&[u8], Duration> {
362-
map(
363-
preceded(tag(b"P"), terminated(duration_week, tag(b"W"))),
364-
Duration::Weeks,
365-
)(i)
347+
map(preceded(tag(b"P"), duration_week), Duration::Weeks)(i)
366348
}
367349

368350
// YYYY, no sign

src/parsers/tests.rs

+105-28
Original file line numberDiff line numberDiff line change
@@ -162,90 +162,93 @@ fn disallows_notallowed() {
162162

163163
#[test]
164164
fn test_duration_year() {
165-
assert_eq!(Ok((&[][..], 2019)), duration_year(b"2019"));
166-
assert_eq!(Ok((&[][..], 0)), duration_year(b"0"));
167-
assert_eq!(Ok((&[][..], 10000)), duration_year(b"10000"));
165+
assert_eq!(Ok((&[][..], 2019)), duration_year(b"2019Y"));
166+
assert_eq!(Ok((&[][..], 0)), duration_year(b"0Y"));
167+
assert_eq!(Ok((&[][..], 10000)), duration_year(b"10000Y"));
168168
assert!(duration_year(b"abcd").is_err());
169169
assert!(duration_year(b"-1").is_err());
170170
}
171171

172172
#[test]
173173
fn test_duration_month() {
174-
assert_eq!(Ok((&[][..], 6)), duration_month(b"6"));
175-
assert_eq!(Ok((&[][..], 0)), duration_month(b"0"));
176-
assert_eq!(Ok((&[][..], 12)), duration_month(b"12"));
174+
assert_eq!(Ok((&[][..], 6)), duration_month(b"6M"));
175+
assert_eq!(Ok((&[][..], 0)), duration_month(b"0M"));
176+
assert_eq!(Ok((&[][..], 12)), duration_month(b"12M"));
177177
assert!(duration_month(b"ab").is_err());
178178
assert!(duration_month(b"-1").is_err());
179179
assert!(duration_month(b"13").is_err());
180180
}
181181

182182
#[test]
183183
fn test_duration_week() {
184-
assert_eq!(Ok((&[][..], 26)), duration_week(b"26"));
185-
assert_eq!(Ok((&[][..], 0)), duration_week(b"0"));
186-
assert_eq!(Ok((&[][..], 52)), duration_week(b"52"));
184+
assert_eq!(Ok((&[][..], 26)), duration_week(b"26W"));
185+
assert_eq!(Ok((&[][..], 0)), duration_week(b"0W"));
186+
assert_eq!(Ok((&[][..], 52)), duration_week(b"52W"));
187187
assert!(duration_week(b"ab").is_err());
188188
assert!(duration_week(b"-1").is_err());
189189
assert!(duration_week(b"53").is_err());
190190
}
191191

192192
#[test]
193193
fn test_duration_day() {
194-
assert_eq!(Ok((&[][..], 16)), duration_day(b"16"));
195-
assert_eq!(Ok((&[][..], 0)), duration_day(b"0"));
196-
assert_eq!(Ok((&[][..], 31)), duration_day(b"31"));
194+
assert_eq!(Ok((&[][..], 16)), duration_day(b"16D"));
195+
assert_eq!(Ok((&[][..], 0)), duration_day(b"0D"));
196+
assert_eq!(Ok((&[][..], 31)), duration_day(b"31D"));
197197
assert!(duration_day(b"ab").is_err());
198198
assert!(duration_day(b"-1").is_err());
199199
assert!(duration_day(b"32").is_err());
200200
}
201201

202202
#[test]
203203
fn test_duration_hour() {
204-
assert_eq!(Ok((&[][..], 12)), duration_hour(b"12"));
205-
assert_eq!(Ok((&[][..], 0)), duration_hour(b"0"));
206-
assert_eq!(Ok((&[][..], 24)), duration_hour(b"24"));
204+
assert_eq!(Ok((&[][..], 12)), duration_hour(b"12H"));
205+
assert_eq!(Ok((&[][..], 0)), duration_hour(b"0H"));
206+
assert_eq!(Ok((&[][..], 24)), duration_hour(b"24H"));
207207
assert!(duration_hour(b"ab").is_err());
208208
assert!(duration_hour(b"-1").is_err());
209209
assert!(duration_hour(b"25").is_err());
210210
}
211211

212212
#[test]
213213
fn test_duration_minute() {
214-
assert_eq!(Ok((&[][..], 30)), duration_minute(b"30"));
215-
assert_eq!(Ok((&[][..], 0)), duration_minute(b"0"));
216-
assert_eq!(Ok((&[][..], 60)), duration_minute(b"60"));
214+
assert_eq!(Ok((&[][..], 30)), duration_minute(b"30M"));
215+
assert_eq!(Ok((&[][..], 0)), duration_minute(b"0M"));
216+
assert_eq!(Ok((&[][..], 60)), duration_minute(b"60M"));
217217
assert!(duration_minute(b"ab").is_err());
218218
assert!(duration_minute(b"-1").is_err());
219219
assert!(duration_minute(b"61").is_err());
220220
}
221221

222222
#[test]
223-
fn test_duration_second_and_millisecond() {
223+
fn test_duration_second_and_millisecond1() {
224224
assert_eq!(
225225
Ok((&[][..], (30, 0))),
226-
duration_second_and_millisecond(b"30")
226+
duration_second_and_millisecond(b"30S")
227+
);
228+
assert_eq!(
229+
Ok((&[][..], (0, 0))),
230+
duration_second_and_millisecond(b"0S")
227231
);
228-
assert_eq!(Ok((&[][..], (0, 0))), duration_second_and_millisecond(b"0"));
229232
assert_eq!(
230233
Ok((&[][..], (60, 0))),
231-
duration_second_and_millisecond(b"60")
234+
duration_second_and_millisecond(b"60S")
232235
);
233236
assert_eq!(
234237
Ok((&[][..], (1, 230))),
235-
duration_second_and_millisecond(b"1,23")
238+
duration_second_and_millisecond(b"1,23S")
236239
);
237240
assert_eq!(
238-
Ok((&[][..], (1, 230))),
239-
duration_second_and_millisecond(b"1.23")
241+
Ok((&[][..], (2, 340))),
242+
duration_second_and_millisecond(b"2.34S")
240243
);
241-
assert!(duration_second_and_millisecond(b"ab").is_err());
242-
assert!(duration_second_and_millisecond(b"-1").is_err());
243-
assert!(duration_second_and_millisecond(b"61").is_err());
244+
assert!(duration_second_and_millisecond(b"abS").is_err());
245+
assert!(duration_second_and_millisecond(b"-1S").is_err());
244246
}
245247

246248
#[test]
247249
fn test_duration_time() {
248250
assert_eq!(Ok((&[][..], (1, 2, 3, 0))), duration_time(b"1H2M3S"));
251+
assert_eq!(Ok((&[][..], (10, 12, 30, 0))), duration_time(b"10H12M30S"));
249252
assert_eq!(Ok((&[][..], (1, 0, 3, 0))), duration_time(b"1H3S"));
250253
assert_eq!(Ok((&[][..], (0, 2, 0, 0))), duration_time(b"2M"));
251254
assert_eq!(Ok((&[][..], (1, 2, 3, 400))), duration_time(b"1H2M3,4S"));
@@ -277,6 +280,27 @@ fn test_duration_datetime_error() {
277280
assert!(duration_datetime(b"0001-02-03T04:05:06").is_err()); // missing P at start
278281
}
279282

283+
#[rustfmt::skip]
284+
#[test]
285+
fn test_duration_second_and_millisecond2() {
286+
assert_parser!(
287+
parse_duration, "PT30S",
288+
Duration::YMDHMS { year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 30, millisecond: 0 }
289+
290+
);
291+
292+
assert_parser!(
293+
parse_duration, "PT30.123S",
294+
Duration::YMDHMS { year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 30, millisecond: 123 }
295+
296+
);
297+
298+
assert_parser!(
299+
parse_duration, "P2021Y11M16DT23H26M59.123S",
300+
Duration::YMDHMS { year: 2021, month: 11, day: 16, hour: 23, minute: 26, second: 59, millisecond: 123 }
301+
);
302+
}
303+
280304
#[rustfmt::skip]
281305
#[test]
282306
fn duration_roundtrip() {
@@ -330,6 +354,59 @@ fn duration_roundtrip() {
330354
);
331355
}
332356

357+
#[rustfmt::skip]
358+
#[test]
359+
fn duration_multi_digit_hour() {
360+
assert_parser!(
361+
parse_duration, "PT12H",
362+
Duration::YMDHMS { year: 0, month: 0, day: 0, hour: 12, minute: 0, second: 0, millisecond: 0 }
363+
);
364+
assert_parser!(
365+
parse_duration, "PT8760H",
366+
Duration::YMDHMS { year: 0, month: 0, day: 0, hour: 365*24, minute: 0, second: 0, millisecond: 0 }
367+
);
368+
}
369+
370+
#[rustfmt::skip]
371+
#[test]
372+
fn duration_multi_digit_minute() {
373+
assert_parser!(
374+
parse_duration, "PT15M",
375+
Duration::YMDHMS { year: 0, month: 0, day: 0, hour: 0, minute: 15, second: 0, millisecond: 0 }
376+
);
377+
assert_parser!(
378+
parse_duration, "PT600M",
379+
Duration::YMDHMS { year: 0, month: 0, day: 0, hour: 0, minute: 600, second: 0, millisecond: 0 }
380+
);
381+
}
382+
383+
#[rustfmt::skip]
384+
#[test]
385+
fn duration_multi_digit_second() {
386+
assert_parser!(
387+
parse_duration, "PT16S",
388+
Duration::YMDHMS { year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 16, millisecond: 0 }
389+
);
390+
391+
assert_parser!(
392+
parse_duration, "PT900S",
393+
Duration::YMDHMS { year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 900, millisecond: 0 }
394+
);
395+
}
396+
397+
#[rustfmt::skip]
398+
#[test]
399+
fn duration_multi_digit_day() {
400+
assert_parser!(
401+
parse_duration, "P365D",
402+
Duration::YMDHMS { year: 0, month: 0, day: 365, hour: 0, minute: 0, second: 0, millisecond: 0 }
403+
);
404+
assert_parser!(
405+
parse_duration, "P36500D",
406+
Duration::YMDHMS { year: 0, month: 0, day: 36500, hour: 0, minute: 0, second: 0, millisecond: 0 }
407+
);
408+
}
409+
333410
// #[test]
334411
// fn corner_cases() {
335412
// // how to deal with left overs?

0 commit comments

Comments
 (0)