diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:47:55 +0000 |
commit | 2aadc03ef15cb5ca5cc2af8a7c08e070742f0ac4 (patch) | |
tree | 033cc839730fda84ff08db877037977be94e5e3a /vendor/toml_edit/src/parser/datetime.rs | |
parent | Initial commit. (diff) | |
download | cargo-upstream.tar.xz cargo-upstream.zip |
Adding upstream version 0.70.1+ds1.upstream/0.70.1+ds1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/toml_edit/src/parser/datetime.rs')
-rw-r--r-- | vendor/toml_edit/src/parser/datetime.rs | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/vendor/toml_edit/src/parser/datetime.rs b/vendor/toml_edit/src/parser/datetime.rs new file mode 100644 index 0000000..6e89b97 --- /dev/null +++ b/vendor/toml_edit/src/parser/datetime.rs @@ -0,0 +1,446 @@ +use std::ops::RangeInclusive; + +use crate::parser::errors::CustomError; +use crate::parser::prelude::*; +use crate::parser::trivia::from_utf8_unchecked; + +use toml_datetime::*; +use winnow::combinator::alt; +use winnow::combinator::cut_err; +use winnow::combinator::opt; +use winnow::combinator::preceded; +use winnow::token::one_of; +use winnow::token::take_while; +use winnow::trace::trace; + +// ;; Date and Time (as defined in RFC 3339) + +// date-time = offset-date-time / local-date-time / local-date / local-time +// offset-date-time = full-date time-delim full-time +// local-date-time = full-date time-delim partial-time +// local-date = full-date +// local-time = partial-time +// full-time = partial-time time-offset +pub(crate) fn date_time(input: &mut Input<'_>) -> PResult<Datetime> { + trace( + "date-time", + alt(( + (full_date, opt((time_delim, partial_time, opt(time_offset)))) + .map(|(date, opt)| { + match opt { + // Offset Date-Time + Some((_, time, offset)) => Datetime { + date: Some(date), + time: Some(time), + offset, + }, + // Local Date + None => Datetime { + date: Some(date), + time: None, + offset: None, + }, + } + }) + .context(StrContext::Label("date-time")), + partial_time + .map(|t| t.into()) + .context(StrContext::Label("time")), + )), + ) + .parse_next(input) +} + +// full-date = date-fullyear "-" date-month "-" date-mday +pub(crate) fn full_date(input: &mut Input<'_>) -> PResult<Date> { + trace( + "full-date", + (date_fullyear, b'-', cut_err((date_month, b'-', date_mday))) + .map(|(year, _, (month, _, day))| Date { year, month, day }), + ) + .parse_next(input) +} + +// partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] +pub(crate) fn partial_time(input: &mut Input<'_>) -> PResult<Time> { + trace( + "partial-time", + ( + time_hour, + b':', + cut_err((time_minute, b':', time_second, opt(time_secfrac))), + ) + .map(|(hour, _, (minute, _, second, nanosecond))| Time { + hour, + minute, + second, + nanosecond: nanosecond.unwrap_or_default(), + }), + ) + .parse_next(input) +} + +// time-offset = "Z" / time-numoffset +// time-numoffset = ( "+" / "-" ) time-hour ":" time-minute +pub(crate) fn time_offset(input: &mut Input<'_>) -> PResult<Offset> { + trace( + "time-offset", + alt(( + one_of((b'Z', b'z')).value(Offset::Z), + ( + one_of((b'+', b'-')), + cut_err((time_hour, b':', time_minute)), + ) + .map(|(sign, (hours, _, minutes))| { + let sign = match sign { + b'+' => 1, + b'-' => -1, + _ => unreachable!("Parser prevents this"), + }; + sign * (hours as i16 * 60 + minutes as i16) + }) + .verify(|minutes| ((-24 * 60)..=(24 * 60)).contains(minutes)) + .map(|minutes| Offset::Custom { minutes }), + )) + .context(StrContext::Label("time offset")), + ) + .parse_next(input) +} + +// date-fullyear = 4DIGIT +pub(crate) fn date_fullyear(input: &mut Input<'_>) -> PResult<u16> { + unsigned_digits::<4, 4> + .map(|s: &str| s.parse::<u16>().expect("4DIGIT should match u8")) + .parse_next(input) +} + +// date-month = 2DIGIT ; 01-12 +pub(crate) fn date_month(input: &mut Input<'_>) -> PResult<u8> { + unsigned_digits::<2, 2> + .try_map(|s: &str| { + let d = s.parse::<u8>().expect("2DIGIT should match u8"); + if (1..=12).contains(&d) { + Ok(d) + } else { + Err(CustomError::OutOfRange) + } + }) + .parse_next(input) +} + +// date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year +pub(crate) fn date_mday(input: &mut Input<'_>) -> PResult<u8> { + unsigned_digits::<2, 2> + .try_map(|s: &str| { + let d = s.parse::<u8>().expect("2DIGIT should match u8"); + if (1..=31).contains(&d) { + Ok(d) + } else { + Err(CustomError::OutOfRange) + } + }) + .parse_next(input) +} + +// time-delim = "T" / %x20 ; T, t, or space +pub(crate) fn time_delim(input: &mut Input<'_>) -> PResult<u8> { + one_of(TIME_DELIM).parse_next(input) +} + +const TIME_DELIM: (u8, u8, u8) = (b'T', b't', b' '); + +// time-hour = 2DIGIT ; 00-23 +pub(crate) fn time_hour(input: &mut Input<'_>) -> PResult<u8> { + unsigned_digits::<2, 2> + .try_map(|s: &str| { + let d = s.parse::<u8>().expect("2DIGIT should match u8"); + if (0..=23).contains(&d) { + Ok(d) + } else { + Err(CustomError::OutOfRange) + } + }) + .parse_next(input) +} + +// time-minute = 2DIGIT ; 00-59 +pub(crate) fn time_minute(input: &mut Input<'_>) -> PResult<u8> { + unsigned_digits::<2, 2> + .try_map(|s: &str| { + let d = s.parse::<u8>().expect("2DIGIT should match u8"); + if (0..=59).contains(&d) { + Ok(d) + } else { + Err(CustomError::OutOfRange) + } + }) + .parse_next(input) +} + +// time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules +pub(crate) fn time_second(input: &mut Input<'_>) -> PResult<u8> { + unsigned_digits::<2, 2> + .try_map(|s: &str| { + let d = s.parse::<u8>().expect("2DIGIT should match u8"); + if (0..=60).contains(&d) { + Ok(d) + } else { + Err(CustomError::OutOfRange) + } + }) + .parse_next(input) +} + +// time-secfrac = "." 1*DIGIT +pub(crate) fn time_secfrac(input: &mut Input<'_>) -> PResult<u32> { + static SCALE: [u32; 10] = [ + 0, + 100_000_000, + 10_000_000, + 1_000_000, + 100_000, + 10_000, + 1_000, + 100, + 10, + 1, + ]; + const INF: usize = usize::MAX; + preceded(b'.', unsigned_digits::<1, INF>) + .try_map(|mut repr: &str| -> Result<u32, CustomError> { + let max_digits = SCALE.len() - 1; + if max_digits < repr.len() { + // Millisecond precision is required. Further precision of fractional seconds is + // implementation-specific. If the value contains greater precision than the + // implementation can support, the additional precision must be truncated, not rounded. + repr = &repr[0..max_digits]; + } + + let v = repr.parse::<u32>().map_err(|_| CustomError::OutOfRange)?; + let num_digits = repr.len(); + + // scale the number accordingly. + let scale = SCALE.get(num_digits).ok_or(CustomError::OutOfRange)?; + let v = v.checked_mul(*scale).ok_or(CustomError::OutOfRange)?; + Ok(v) + }) + .parse_next(input) +} + +pub(crate) fn unsigned_digits<'i, const MIN: usize, const MAX: usize>( + input: &mut Input<'i>, +) -> PResult<&'i str> { + take_while(MIN..=MAX, DIGIT) + .map(|b: &[u8]| unsafe { from_utf8_unchecked(b, "`is_ascii_digit` filters out on-ASCII") }) + .parse_next(input) +} + +// DIGIT = %x30-39 ; 0-9 +const DIGIT: RangeInclusive<u8> = b'0'..=b'9'; + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn offset_date_time() { + let inputs = [ + ( + "1979-05-27T07:32:00Z", + Datetime { + date: Some(Date { + year: 1979, + month: 5, + day: 27, + }), + time: Some(Time { + hour: 7, + minute: 32, + second: 0, + nanosecond: 0, + }), + offset: Some(Offset::Z), + }, + ), + ( + "1979-05-27T00:32:00-07:00", + Datetime { + date: Some(Date { + year: 1979, + month: 5, + day: 27, + }), + time: Some(Time { + hour: 0, + minute: 32, + second: 0, + nanosecond: 0, + }), + offset: Some(Offset::Custom { minutes: -7 * 60 }), + }, + ), + ( + "1979-05-27T00:32:00-00:36", + Datetime { + date: Some(Date { + year: 1979, + month: 5, + day: 27, + }), + time: Some(Time { + hour: 0, + minute: 32, + second: 0, + nanosecond: 0, + }), + offset: Some(Offset::Custom { minutes: -36 }), + }, + ), + ( + "1979-05-27T00:32:00.999999", + Datetime { + date: Some(Date { + year: 1979, + month: 5, + day: 27, + }), + time: Some(Time { + hour: 0, + minute: 32, + second: 0, + nanosecond: 999999000, + }), + offset: None, + }, + ), + ]; + for (input, expected) in inputs { + dbg!(input); + let actual = date_time.parse(new_input(input)).unwrap(); + assert_eq!(expected, actual); + } + } + + #[test] + fn local_date_time() { + let inputs = [ + ( + "1979-05-27T07:32:00", + Datetime { + date: Some(Date { + year: 1979, + month: 5, + day: 27, + }), + time: Some(Time { + hour: 7, + minute: 32, + second: 0, + nanosecond: 0, + }), + offset: None, + }, + ), + ( + "1979-05-27T00:32:00.999999", + Datetime { + date: Some(Date { + year: 1979, + month: 5, + day: 27, + }), + time: Some(Time { + hour: 0, + minute: 32, + second: 0, + nanosecond: 999999000, + }), + offset: None, + }, + ), + ]; + for (input, expected) in inputs { + dbg!(input); + let actual = date_time.parse(new_input(input)).unwrap(); + assert_eq!(expected, actual); + } + } + + #[test] + fn local_date() { + let inputs = [ + ( + "1979-05-27", + Datetime { + date: Some(Date { + year: 1979, + month: 5, + day: 27, + }), + time: None, + offset: None, + }, + ), + ( + "2017-07-20", + Datetime { + date: Some(Date { + year: 2017, + month: 7, + day: 20, + }), + time: None, + offset: None, + }, + ), + ]; + for (input, expected) in inputs { + dbg!(input); + let actual = date_time.parse(new_input(input)).unwrap(); + assert_eq!(expected, actual); + } + } + + #[test] + fn local_time() { + let inputs = [ + ( + "07:32:00", + Datetime { + date: None, + time: Some(Time { + hour: 7, + minute: 32, + second: 0, + nanosecond: 0, + }), + offset: None, + }, + ), + ( + "00:32:00.999999", + Datetime { + date: None, + time: Some(Time { + hour: 0, + minute: 32, + second: 0, + nanosecond: 999999000, + }), + offset: None, + }, + ), + ]; + for (input, expected) in inputs { + dbg!(input); + let actual = date_time.parse(new_input(input)).unwrap(); + assert_eq!(expected, actual); + } + } + + #[test] + fn time_fraction_truncated() { + let input = "1987-07-05T17:45:00.123456789012345Z"; + date_time.parse(new_input(input)).unwrap(); + } +} |