name = "chrono"
description = "Date and time library for Rust — ETNA workload."
language = "rust"
strategies = ["proptest", "quickcheck", "crabcheck", "hegel"]
crate = "chrono"
base_commit = "c6063e6f5a03a48c6feeac3eb5b51ab4cb902759"

[[tasks]]
mutations = ["from_num_days_from_ce_overflow_f659719_1"]

[tasks.source]
repo = "https://github.com/chronotope/chrono"
commits = ["f6597197cd8a0230291a478bee2b9b8c696ad80e"]
commit_subjects = ["Fix panic in from_num_days_from_ce_opt"]
summary = "`NaiveDate::from_num_days_from_ce_opt` added 365 to its i32 input with a plain `+`, which panicked in debug mode (and silently wrapped in release) for inputs near `i32::MAX` / `i32::MIN`. The fix uses `checked_add(365)?`, returning `None` for out-of-range inputs."

[tasks.injection]
kind = "patch"
files = ["src/naive/date/mod.rs"]
locations = [{ file = "src/naive/date/mod.rs", line = 379, symbol = "NaiveDate::from_num_days_from_ce_opt" }]
patch = "patches/from_num_days_from_ce_overflow_f659719_1.patch"

[tasks.bug]
short_name = "from_num_days_from_ce_overflow"
invariant = "For every `i32` argument `d`, `NaiveDate::from_num_days_from_ce_opt(d)` must complete without panicking. When the underlying day count `d + 365` would overflow `i32`, the function must return `None` rather than panic or wrap silently."
how_triggered = "The buggy body computes `let days = days + 365`. For `d = i32::MAX` the addition overflows; in debug builds Rust's overflow checks turn this into a panic, while release builds wrap to a tiny negative day count and return a nonsensical `Some(date)`. The witness calls the function with `i32::MAX` and asserts no panic occurs and any returned date round-trips through `num_days_from_ce`."

[[tasks.tasks]]
property = "FromNumDaysFromCeNoPanic"
witnesses = [
  { test_fn = "witness_from_num_days_from_ce_no_panic_case_i32_max" },
  { test_fn = "witness_from_num_days_from_ce_no_panic_case_i32_min" },
  { test_fn = "witness_from_num_days_from_ce_no_panic_case_zero" },
]

[[tasks]]
mutations = ["parse_rfc3339_utc_str_slicing_5a6b2b4_1"]

[tasks.source]
repo = "https://github.com/chronotope/chrono"
commits = ["5a6b2b40a781c19ad34a3593313468d922fceeea"]
commit_subjects = ["Fix arbitrary string slicing in `parse_rfc3339_relaxed`"]
summary = "`parse_rfc3339_relaxed` checked for the `UTC` literal with `&s[..3]`, which panics if byte 3 of the remaining input falls inside a multi-byte UTF-8 codepoint. The fix slices `s.as_bytes()` instead, performing the comparison on raw bytes and bypassing the UTF-8 boundary check."

[tasks.injection]
kind = "patch"
patch = "patches/parse_rfc3339_utc_str_slicing_5a6b2b4_1.patch"
files = ["src/format/parse.rs"]
locations = [{ file = "src/format/parse.rs", line = 639, symbol = "parse_rfc3339_relaxed" }]

[tasks.bug]
short_name = "parse_rfc3339_utc_str_slicing"
invariant = "For any `&str` `s`, `<DateTime<FixedOffset> as FromStr>::from_str(s)` must complete without panicking. Malformed input is fine to surface as `Err(ParseError)`, but a panic from a UTF-8 boundary check is a defect."
how_triggered = "After parsing the date and time portions, `parse_rfc3339_relaxed` checks for a literal `UTC` suffix via `&s[..3]`. When `s` begins with two two-byte UTF-8 characters (e.g. `ÄÄ` = `\\xC3\\x84\\xC3\\x84`), byte index 3 falls inside the second character; `&s[..3]` panics with `byte index 3 is not a char boundary`. The witness feeds `\"2024-01-01T00:00:00 ÄÄ\"` to `from_str`."

[[tasks.tasks]]
property = "ParseRfc3339NoPanic"
witnesses = [
  { test_fn = "witness_parse_rfc3339_no_panic_case_multibyte_offset" },
  { test_fn = "witness_parse_rfc3339_no_panic_case_valid_utc" },
]

[[tasks]]
mutations = ["duration_round_zero_panic_9a5f76c_1"]

[tasks.source]
repo = "https://github.com/chronotope/chrono"
commits = ["9a5f76c9bb70647d1ab4abd88872e395a7c06c79"]
commit_subjects = ["Fix issue #658 duration_round by zero panics (#659)"]
summary = "`DurationRound::duration_round` reached `stamp % span` with `span == 0` when called with `Duration::zero()`, panicking with attempt to calculate the remainder with a divisor of zero. The fix rejects non-positive spans up front with `RoundingError::DurationExceedsLimit`."

[tasks.injection]
kind = "patch"
patch = "patches/duration_round_zero_panic_9a5f76c_1.patch"
files = ["src/round.rs"]
locations = [{ file = "src/round.rs", line = 229, symbol = "duration_round" }]

[tasks.bug]
short_name = "duration_round_zero_panic"
invariant = "For any `DateTime<Utc>` `dt`, `dt.duration_round(TimeDelta::zero())` must terminate with `Err(RoundingError::DurationExceedsLimit)`; it must never panic."
how_triggered = "The buggy guard checks `if span < 0` instead of `if span <= 0`, allowing a zero span to flow into `let delta_down = stamp % span;` which panics with attempt to calculate the remainder with a divisor of zero. The witness rounds the UTC epoch to a zero `TimeDelta`."

[[tasks.tasks]]
property = "DurationRoundZeroNoPanic"
witnesses = [
  { test_fn = "witness_duration_round_zero_no_panic_case_simple" },
  { test_fn = "witness_duration_round_zero_no_panic_case_far_future" },
]

[[tasks]]
mutations = ["weekday_long_sunday_33516cc_1"]

[tasks.source]
repo = "https://github.com/chronotope/chrono"
commits = ["33516cc9f1ad1c78c214ca83c07d6c93785efb6c"]
commit_subjects = ["Fix parsing LongWeekday for Sunday"]
summary = "`short_or_long_weekday` indexed a per-weekday suffix table to consume the long-form name after the three-letter prefix. Sunday's entry was `\"sunday\"` rather than `\"day\"`, so parsing `\"Sunday\"` only consumed `\"Sun\"` and the literal `\"day\"` was treated as junk."

[tasks.injection]
kind = "patch"
files = ["src/format/scan.rs"]
locations = [{ file = "src/format/scan.rs", line = 150, symbol = "short_or_long_weekday::LONG_WEEKDAY_SUFFIXES" }]
patch = "patches/weekday_long_sunday_33516cc_1.patch"

[tasks.bug]
short_name = "weekday_long_sunday"
invariant = "Parsing any of the seven full English weekday names through the `%A` strftime directive must fully consume the input and identify the correct `Weekday`."
how_triggered = "With Sunday's suffix listed as `b\"sunday\"` instead of `b\"day\"`, the prefix-match check `s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(b\"sunday\")` against the leftover input `\"day\"` fails (the input is shorter than the suffix), so the suffix is never consumed. The trailing `\"day\"` then makes the overall `%A` parse fail with `ParseError(TooLong)`."

[[tasks.tasks]]
property = "LongWeekdayParsesFullName"
witnesses = [
  { test_fn = "witness_long_weekday_parses_full_name_case_sunday" },
  { test_fn = "witness_long_weekday_parses_full_name_case_monday" },
]

[[dropped]]
commit = "cb27ebf"
subject = "Fix out-of-range panic in NaiveWeek::last_day"
reason = "modern checked_first_day/checked_last_day already split first/last computation, and the refactor moved the boundary semantics so that injecting the historical bug also breaks otherwise-passing modern witnesses"

[[dropped]]
commit = "f9f3c78"
subject = "Fix panic in DateTime::checked_add_days"
reason = "modern checked_add_days takes Days = u64 and converts via add_days(i32); the historical Duration::days(i64) overflow path no longer exists in the API"

[[dropped]]
commit = "86391ac"
subject = "Fix potential panic due to overflow in format_inner offset arithmetic"
reason = "format_inner has been rewritten to compute timestamp - offset.unwrap_or(0); the buggy NaiveDateTime - FixedOffset construction site is gone"

[[dropped]]
commit = "49c4bad"
subject = "Fix Negative UNIX timestamps accepted by from_timestamp_millis"
reason = "from_timestamp_millis now delegates to DateTime::from_timestamp_millis; the old buggy direct (millis % 1000) * 1_000_000 path no longer exists in NaiveDateTime"
