Sebastian Thielrefactorfad821910 months agoHistory
116 lines · 4.7 KB · Rust
RawDownload
use crate::{SecondsSinceUnixEpoch, Time}; /// Strictly parse the raw commit header format like `1745582210 +0200`.////// Some strict rules include:////// - The timezone offset must be present./// - The timezone offset must have a sign; either `+` or `-`./// - The timezone offset hours must be less than or equal to 14./// - The timezone offset minutes must be exactly 0, 15, 30, or 45./// - The timezone offset seconds may be present, but 0 is the only valid value./// - Only whitespace may suffix the timezone offset.////// But this function isn't perfectly strict insofar as it allows arbitrary/// whitespace before and after the seconds and offset components.////// The goal is to only accept inputs that _unambiguously_ look like/// git's raw date format.pub fn parse_raw(input: &str) -> Option<Time> {    let mut split = input.split_whitespace();    let seconds = split.next()?.parse::<SecondsSinceUnixEpoch>().ok()?;    let offset_str = split.next()?;    if split.next().is_some() {        return None;    }    let offset_len = offset_str.len();    if offset_len != 5 && offset_len != 7 {        return None;    }    let sign: i32 = match offset_str.get(..1)? {        "-" => Some(-1),        "+" => Some(1),        _ => None,    }?;    let hours: u8 = offset_str.get(1..3)?.parse().ok()?;    let minutes: u8 = offset_str.get(3..5)?.parse().ok()?;    let offset_seconds: u8 = if offset_len == 7 {        offset_str.get(5..7)?.parse().ok()?    } else {        0    };    if hours > 14 || (minutes != 0 && minutes != 15 && minutes != 30 && minutes != 45) || offset_seconds != 0 {        return None;    }    let offset: i32 = sign * ((hours as i32) * 3600 + (minutes as i32) * 60);    Time { seconds, offset }.into()} #[cfg(test)]mod tests {    use super::*;     #[test]    fn parse_raw_valid() {        // These examples show how it's more loose than it has to be,        // merely as a side effect of the implementation.        for (valid, expected_seconds, expected_offset) in [            ("12345 +0000", 12345, 0),            ("-1234567 +0000", -1234567, 0),            ("+1234567 -000000", 1234567, 0),            ("   +0    -000000    ", 0, 0),            ("\t-0\t-0000\t", 0, 0),            ("\n-0\r\n-0000\n", 0, 0),        ] {            assert_eq!(                parse_raw(valid),                Some(Time {                    seconds: expected_seconds,                    offset: expected_offset                }),                "should succeed: '{valid}'"            );        }    }     #[test]    fn parse_raw_invalid() {        for (bad_date_str, message) in [            ("123456 !0600", "invalid sign - must be + or -"),            ("123456 0600", "missing offset sign"),            ("123456 +060", "positive offset too short"),            ("123456 -060", "negative offset too short"),            ("123456 +06000", "not enough offset seconds"),            ("123456 --060", "duplicate offset sign with correct offset length"),            ("123456 -+060", "multiple offset signs with correct offset length"),            ("123456 --0600", "multiple offset signs, but incorrect offset length"),            ("123456 +-06000", "multiple offset signs with correct offset length"),            ("123456 +-0600", "multiple offset signs with incorrect offset length"),            ("123456 +-060", "multiple offset signs with correct offset length"),            ("123456 +10030", "invalid offset length with one 'second' field"),            ("123456 06000", "invalid offset length, missing sign"),            ("123456 +0600 extra", "extra field past offset"),            ("123456 +0600 2005", "extra field past offset that looks like year"),            ("123456+0600", "missing space between unix timestamp and offset"),            (                "123456 + 600",                "extra spaces between sign and offset (which also is too short)",            ),            ("123456 -1500", "negative offset hours out of bounds"),            ("123456 +1500", "positive offset hours out of bounds"),            ("123456 +6600", "positive offset hours out of bounds"),            ("123456 +0660", "invalid offset minutes"),            ("123456 +060010", "positive offset seconds is allowed but only if zero"),            ("123456 -060010", "negative offset seconds is allowed but only if zero"),            ("123456 +0075", "positive offset minutes invalid"),            ("++123456 +0000", "duplicate timestamp sign"),            ("--123456 +0000", "duplicate timestamp sign"),            ("1234567 -+1+1+0", "unsigned offset parsing rejects '+'"),        ] {            assert!(                parse_raw(bad_date_str).is_none(),                "should fail: '{bad_date_str}': {message}"            );        }    }}