Skip to content

Commit

Permalink
Support time duration more than 23
Browse files Browse the repository at this point in the history
  • Loading branch information
nix010 committed Jun 14, 2024
1 parent aad1d91 commit 2adc047
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 3 deletions.
79 changes: 77 additions & 2 deletions src/duration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,22 @@ impl Duration {
let mut d = match bytes.get(offset).copied() {
Some(b'P') => Self::parse_iso_duration(bytes, offset + 1),
_ => match bytes.get(offset + 2).copied() {
Some(b':') => Self::parse_time(bytes, offset, config),
_ => Self::parse_days_time(bytes, offset),
Some(b':') => {
let h1 = bytes.get(offset).copied().unwrap() - b'0';
let h2 = bytes.get(offset + 1).copied().unwrap() - b'0';
if h1 * 10 + h2 > 23 {
Self::parse_oversize_hour_time_format(bytes, offset)
} else {
Self::parse_time(bytes, offset, config)
}
}
_ => {
if bytes.len() - offset < 8 || Self::is_duration_date_format(bytes) {
Self::parse_days_time(bytes, offset)
} else {
Self::parse_oversize_hour_time_format(bytes, offset)
}
}
},
}?;
d.positive = positive;
Expand Down Expand Up @@ -420,6 +434,67 @@ impl Duration {
})
}

fn parse_oversize_hour_time_format(bytes: &[u8], offset: usize) -> Result<Duration, ParseError> {
// handle format H..H:MM:DD. So we already know the fix format for the last 6 chars and
// minimum length is 8;
let byte_len = bytes.len();
if byte_len < 8 {
return Err(ParseError::TooShort);
}

let hour_numeric_limit = 24 * (1e9 as i64);

// see if the ":" is where it should be in "H..H:MM:DD".
if bytes.get(byte_len - 3) != Some(&b':') || bytes.get(byte_len - 6) != Some(&b':') {
return Err(ParseError::DurationInvalidFraction);
}

let mut hour_numeric_value: i64 = 0;
let hour_byte_len = byte_len - 6;
// the limit for H..H is 1,000,000,000 * 24 => result have length of 11 chars.
// so just checking if it can fail early.
if hour_byte_len > 11 || (hour_byte_len == 11 && (bytes.get(offset) > Some(&b'2'))) {
return Err(ParseError::DurationHourValueTooLarge);
}

// parsing the H..H in to a number.
for idx in offset..hour_byte_len {
let n = bytes.get(idx).ok_or(ParseError::InvalidCharHour)? - b'0';
if n > 9 {
return Err(ParseError::InvalidCharHour);
}
hour_numeric_value = (hour_numeric_value * 10) + (n as i64);
}

if hour_numeric_value >= hour_numeric_limit {
return Err(ParseError::DurationHourValueTooLarge);
}

// extract the Day value and parse the left over time part.
let days = (hour_numeric_value / 24) as u32;
let leftover_hour = (hour_numeric_value % 24) as i32;

let h1 = (leftover_hour / 10) as u8 + b'0';
let h2 = (leftover_hour % 10) as u8 + b'0';

let mut temp_vec: Vec<u8> = vec![h1, h2];
temp_vec.extend_from_slice(&bytes[byte_len - 6..]);

let mut t = Self::parse_time(&temp_vec[..], 0, &TimeConfigBuilder::new().build())?;
t.day = days;
Ok(t)
}

fn is_duration_date_format(bytes: &[u8]) -> bool {
for byte in bytes {
match byte {
b'd' | b'D' => return true,
_ => {}
}
}
false
}

fn parse_days_time(bytes: &[u8], offset: usize) -> Result<Self, ParseError> {
let (day, offset) = match bytes.get(offset).copied() {
Some(c) => Self::parse_number(bytes, c, offset),
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ pub enum ParseError {
/// a numeric value in the duration is too large
DurationValueTooLarge,
/// durations may not exceed 999,999,999 days
DurationHourValueTooLarge,
/// durations hours must less than 1,000,000,000
DurationDaysTooLarge,
/// dates before 1600 are not supported as unix timestamps
DateTooSmall,
Expand Down
5 changes: 4 additions & 1 deletion tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1153,7 +1153,10 @@ param_tests! {
duration_time_fraction: ok => "00:01:03.123", "PT1M3.123S";
duration_time_extra: err => "00:01:03.123x", ExtraCharacters;
duration_time_timezone: err => "00:01:03x", ExtraCharacters;
duration_time_invalid_hour: err => "24:01:03", OutOfRangeHour;
duration_time_more_than_24_hour: ok => "24:01:03", "P1DT1M3S";
duration_time_way_more_than_24_hour: ok => "2400000000:01:03", "P273972Y220DT1M3S";
duration_time_invalid_over_limit_hour: err => "100000000000:01:03", DurationHourValueTooLarge;
duration_time_invalid_format_hour: err => "1000xxx000:01:03", InvalidCharHour;
duration_time_invalid_minute: err => "00:60:03", OutOfRangeMinute;
duration_time_invalid_second: err => "00:00:60", OutOfRangeSecond;
duration_time_fraction_too_long: err => "00:00:00.1234567", SecondFractionTooLong;
Expand Down

0 comments on commit 2adc047

Please sign in to comment.