use std::time::SystemTime; use gix_date::Time;use gix_error::Exn; #[test]fn time_without_offset_defaults_to_utc() { // Git parses datetime without offset and defaults to UTC (+0000) let result = gix_date::parse("1979-02-26 18:30:00", Some(SystemTime::now())); assert!(result.is_ok(), "Git parses datetime without offset, defaulting to UTC"); let time = result.unwrap(); assert_eq!(time.offset, 0, "Offset should default to UTC (+0000)");} #[test]fn parse_header_is_not_too_lenient() { for not_a_header_str in ["2005-04-07T22:13:09", "2005-04-07 22:13:09"] { assert!( gix_date::parse_header(not_a_header_str).is_none(), "parse_header only accepts raw format (timestamp +offset), not ISO8601" ); // Note: gix_date::parse() DOES accept these formats, matching Git's behavior // Git parses them with default UTC offset }} #[test]fn short() { assert_eq!( gix_date::parse("1979-02-26", Some(SystemTime::now())).unwrap(), Time { seconds: 288835200, offset: 0, }, "could not parse with SHORT format" );} #[test]fn rfc2822() { assert_eq!( gix_date::parse("Thu, 18 Aug 2022 12:45:06 +0800", None).unwrap(), Time { seconds: 1660797906, offset: 28800, }, );} #[test]fn git_rfc2822() { let expected = Time { seconds: 1659329106, offset: 28800, }; assert_eq!( gix_date::parse("Thu, 1 Aug 2022 12:45:06 +0800", None).unwrap(), expected, ); assert_eq!( gix_date::parse("Thu, 1 Aug 2022 12:45:06 +0800", None).unwrap(), expected, );} #[test]fn raw() -> Result<(), Exn<gix_date::Error>> { assert_eq!( gix_date::parse("1660874655 +0800", None)?, Time { seconds: 1660874655, offset: 28800, }, ); assert_eq!( gix_date::parse("1112911993 +0100", None)?, Time { seconds: 1112911993, offset: 3600, }, ); assert_eq!( gix_date::parse("1313584730 +051500", None)?, Time { seconds: 1313584730, offset: 18900, }, "seconds for time-offsets work as well" ); assert_eq!( gix_date::parse("1313584730 -0230", None)?, Time { seconds: 1313584730, offset: -150 * 60, }, ); assert!(gix_date::parse("1313584730 +1500", None).is_err()); assert!(gix_date::parse("1313584730 +000001", None).is_err()); assert!(gix_date::parse("1313584730 +0001", None).is_err()); assert!(gix_date::parse("1313584730 +000100", None).is_err()); let expected = Time { seconds: 1660874655, offset: -28800, }; for date_str in [ "1660874655 -0800", "1660874655 -0800 ", " 1660874655 -0800", " 1660874655 -0800 ", " 1660874655 -0800 ", "1660874655\t-0800", "1660874655\t-080000", ] { assert_eq!(gix_date::parse_header(date_str), Some(expected)); } Ok(())} #[test]fn bad_raw() { for bad_date_str in [ "123456 !0600", "123456 +060", "123456 -060", "123456 +06000", "123456 +10030", "123456 06000", "123456 0600", "123456 +0600 extra", "123456+0600", "123456 + 600", ] { assert_eq!( gix_date::parse_header(bad_date_str), Some(Time { seconds: 123456, offset: 0 }), "{bad_date_str}: invalid offsets default to zero (like in git2), and Git ignores them mostly" ); }} #[test]fn double_negation_in_offset() { let actual = gix_date::parse_header("1288373970 --700").unwrap(); assert_eq!( actual, gix_date::Time { seconds: 1288373970, offset: 0, }, "double-negation isn't special, it's considered malformed" ); assert_eq!( actual.to_string(), "1288373970 +0000", "serialization is lossy as offset couldn't be parsed" );} #[test]fn git_default() { assert_eq!( gix_date::parse("Thu Aug 8 12:45:06 2022 +0800", None).unwrap(), Time { seconds: 1659933906, offset: 28800, }, );} #[test]fn invalid_dates_can_be_produced_without_current_time() { assert_eq!( gix_date::parse("foobar", None).unwrap_err().to_string(), "Unknown date format: \"foobar\"" );} /// Tests for compact ISO8601 formats (YYYYMMDDTHHMMSS variants)mod compact_iso8601;mod relative; /// Tests for ISO8601 with dots format (YYYY.MM.DD HH:MM:SS offset)mod iso8601_dots { use gix_date::Time; #[test] fn basic() { assert_eq!( gix_date::parse("2008.02.14 20:30:45 -0500", None).unwrap(), Time { seconds: 1203039045, offset: -18000, }, "2008.02.14 20:30:45 -0500" ); }} /// Tests for flexible timezone offset formatsmod flexible_offset; /// Tests for subsecond precision in ISO8601 formats (ignored like Git)mod subsecond_precision { use gix_date::Time; #[test] fn iso8601_with_subseconds() { assert_eq!( gix_date::parse("2008-02-14 20:30:45.019-04:00", None).unwrap(), Time { seconds: 1203035445, offset: -14400, }, "2008-02-14 20:30:45.019-04:00 - subseconds ignored" ); }} /// Various cases the fuzzer foundmod fuzz { use std::path::PathBuf; fn fuzz_artifact_paths(target: &str) -> Vec<PathBuf> { let mut paths = std::fs::read_dir( PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("fuzz/artifacts") .join(target), ) .expect("artifact directory exists") .filter_map(|entry| entry.ok().map(|entry| entry.path())) .collect::<Vec<_>>(); paths.sort(); paths } #[test] fn reproduce_1979() { gix_date::parse("fRi ", None).ok(); } #[test] fn artifact_inputs_can_be_parsed_without_panicking() { for path in fuzz_artifact_paths("parse") { let input = std::fs::read(path).expect("artifact is readable"); if let Ok(input) = std::str::from_utf8(&input) { gix_date::parse(input, None).ok(); } } } #[test] fn invalid_but_does_not_cause_panic() { for input in ["-9999-1-1", "7 -𬞋", "5 ڜ-09", "-4 week ago Z", "8960609 day ago"] { gix_date::parse(input, Some(std::time::UNIX_EPOCH)).ok(); } }}
use std::time::SystemTime;
use gix_date::Time;
use gix_error::Exn;
#[test]
fn time_without_offset_defaults_to_utc() {
// Git parses datetime without offset and defaults to UTC (+0000)
let result = gix_date::parse("1979-02-26 18:30:00", Some(SystemTime::now()));
assert!(result.is_ok(), "Git parses datetime without offset, defaulting to UTC");
let time = result.unwrap();
assert_eq!(time.offset, 0, "Offset should default to UTC (+0000)");
}
#[test]
fn parse_header_is_not_too_lenient() {
for not_a_header_str in ["2005-04-07T22:13:09", "2005-04-07 22:13:09"] {
assert!(
gix_date::parse_header(not_a_header_str).is_none(),
"parse_header only accepts raw format (timestamp +offset), not ISO8601"
);
// Note: gix_date::parse() DOES accept these formats, matching Git's behavior
// Git parses them with default UTC offset
}
}
#[test]
fn short() {
assert_eq!(
gix_date::parse("1979-02-26", Some(SystemTime::now())).unwrap(),
Time {
seconds: 288835200,
offset: 0,
},
"could not parse with SHORT format"
);
}
#[test]
fn rfc2822() {
assert_eq!(
gix_date::parse("Thu, 18 Aug 2022 12:45:06 +0800", None).unwrap(),
Time {
seconds: 1660797906,
offset: 28800,
},
);
}
#[test]
fn git_rfc2822() {
let expected = Time {
seconds: 1659329106,
offset: 28800,
};
assert_eq!(
gix_date::parse("Thu, 1 Aug 2022 12:45:06 +0800", None).unwrap(),
expected,
);
assert_eq!(
gix_date::parse("Thu, 1 Aug 2022 12:45:06 +0800", None).unwrap(),
expected,
);
}
#[test]
fn raw() -> Result<(), Exn<gix_date::Error>> {
assert_eq!(
gix_date::parse("1660874655 +0800", None)?,
Time {
seconds: 1660874655,
offset: 28800,
},
);
assert_eq!(
gix_date::parse("1112911993 +0100", None)?,
Time {
seconds: 1112911993,
offset: 3600,
},
);
assert_eq!(
gix_date::parse("1313584730 +051500", None)?,
Time {
seconds: 1313584730,
offset: 18900,
},
"seconds for time-offsets work as well"
);
assert_eq!(
gix_date::parse("1313584730 -0230", None)?,
Time {
seconds: 1313584730,
offset: -150 * 60,
},
);
assert!(gix_date::parse("1313584730 +1500", None).is_err());
assert!(gix_date::parse("1313584730 +000001", None).is_err());
assert!(gix_date::parse("1313584730 +0001", None).is_err());
assert!(gix_date::parse("1313584730 +000100", None).is_err());
let expected = Time {
seconds: 1660874655,
offset: -28800,
};
for date_str in [
"1660874655 -0800",
"1660874655 -0800 ",
" 1660874655 -0800",
" 1660874655 -0800 ",
" 1660874655 -0800 ",
"1660874655\t-0800",
"1660874655\t-080000",
] {
assert_eq!(gix_date::parse_header(date_str), Some(expected));
}
Ok(())
}
#[test]
fn bad_raw() {
for bad_date_str in [
"123456 !0600",
"123456 +060",
"123456 -060",
"123456 +06000",
"123456 +10030",
"123456 06000",
"123456 0600",
"123456 +0600 extra",
"123456+0600",
"123456 + 600",
] {
assert_eq!(
gix_date::parse_header(bad_date_str),
Some(Time {
seconds: 123456,
offset: 0
}),
"{bad_date_str}: invalid offsets default to zero (like in git2), and Git ignores them mostly"
);
}
}
#[test]
fn double_negation_in_offset() {
let actual = gix_date::parse_header("1288373970 --700").unwrap();
assert_eq!(
actual,
gix_date::Time {
seconds: 1288373970,
offset: 0,
},
"double-negation isn't special, it's considered malformed"
);
assert_eq!(
actual.to_string(),
"1288373970 +0000",
"serialization is lossy as offset couldn't be parsed"
);
}
#[test]
fn git_default() {
assert_eq!(
gix_date::parse("Thu Aug 8 12:45:06 2022 +0800", None).unwrap(),
Time {
seconds: 1659933906,
offset: 28800,
},
);
}
#[test]
fn invalid_dates_can_be_produced_without_current_time() {
assert_eq!(
gix_date::parse("foobar", None).unwrap_err().to_string(),
"Unknown date format: \"foobar\""
);
}
/// Tests for compact ISO8601 formats (YYYYMMDDTHHMMSS variants)
mod compact_iso8601;
mod relative;
/// Tests for ISO8601 with dots format (YYYY.MM.DD HH:MM:SS offset)
mod iso8601_dots {
use gix_date::Time;
#[test]
fn basic() {
assert_eq!(
gix_date::parse("2008.02.14 20:30:45 -0500", None).unwrap(),
Time {
seconds: 1203039045,
offset: -18000,
},
"2008.02.14 20:30:45 -0500"
);
}
}
/// Tests for flexible timezone offset formats
mod flexible_offset;
/// Tests for subsecond precision in ISO8601 formats (ignored like Git)
mod subsecond_precision {
use gix_date::Time;
#[test]
fn iso8601_with_subseconds() {
assert_eq!(
gix_date::parse("2008-02-14 20:30:45.019-04:00", None).unwrap(),
Time {
seconds: 1203035445,
offset: -14400,
},
"2008-02-14 20:30:45.019-04:00 - subseconds ignored"
);
}
}
/// Various cases the fuzzer found
mod fuzz {
use std::path::PathBuf;
fn fuzz_artifact_paths(target: &str) -> Vec<PathBuf> {
let mut paths = std::fs::read_dir(
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("fuzz/artifacts")
.join(target),
)
.expect("artifact directory exists")
.filter_map(|entry| entry.ok().map(|entry| entry.path()))
.collect::<Vec<_>>();
paths.sort();
paths
}
#[test]
fn reproduce_1979() {
gix_date::parse("fRi ", None).ok();
}
#[test]
fn artifact_inputs_can_be_parsed_without_panicking() {
for path in fuzz_artifact_paths("parse") {
let input = std::fs::read(path).expect("artifact is readable");
if let Ok(input) = std::str::from_utf8(&input) {
gix_date::parse(input, None).ok();
}
}
}
#[test]
fn invalid_but_does_not_cause_panic() {
for input in ["-9999-1-1", "7 -𬞋", "5 ڜ-09", "-4 week ago Z", "8960609 day ago"] {
gix_date::parse(input, Some(std::time::UNIX_EPOCH)).ok();
}
}
}