{"record":{"id":"28dcb51b57aefc05","repo":"GitoxideLabs/gitoxide","slug":"couldn-t-parse-span-from-period-count","errorCode":null,"errorMessage":"Couldn't parse span from '{period} {count}'","messagePattern":"Couldn't parse span from '(.+?) (.+?)'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-date/src/parse/relative.rs","lineNumber":184,"sourceCode":"        /// Turn the fields back into a point in time: a day beyond the end of the month rolls over into the\n        /// following month. One month before May 31st is thus May 1st, a day after April 30th.\n        fn normalize(&self) -> Result<Zoned, Exn<Error>> {\n            let first_of_month = civil::Date::new(self.year, self.month, 1)\n                .or_raise(|| Error::new(format!(\"Date lies out of range: {}-{:02}\", self.year, self.month)))?;\n            let days_beyond_first = SignedDuration::from_secs((i64::from(self.day) - 1) * 24 * 60 * 60);\n            first_of_month\n                .checked_add(days_beyond_first)\n                .or_raise(|| Error::new(format!(\"Day {} lies out of range\", self.day)))?\n                .to_datetime(self.time)\n                .to_zoned(self.timezone.clone())\n                .or_raise(|| Error::new(\"Could not convert date to a point in time\"))\n        }\n    }\n\n    let now = now.ok_or(ValidationError::new(\"Missing current time\"))?;\n    let mut fields = Fields::from(now);\n    for Pair { period, count, unit } in pairs {\n        let err = || Error::new(format!(\"Couldn't parse span from '{period} {count}'\"));\n        match unit {\n            Unit::Seconds(factor) => {\n                let seconds = count\n                    .checked_mul(*factor)\n                    .map(SignedDuration::from_secs)\n                    .ok_or_else(err)?;\n                let ts = fields.normalize()?.timestamp().checked_sub(seconds).or_raise(err)?;\n                fields = ts.to_zoned(fields.timezone.clone()).into();\n            }\n            Unit::Months(factor) => {\n                let months = count.checked_mul(*factor).ok_or_else(err)?;\n                fields = fields.normalize()?.into();\n                let total = (i64::from(fields.year) * 12 + i64::from(fields.month) - 1)\n                    .checked_sub(months)\n                    .ok_or_else(err)?;\n                fields.year = i16::try_from(total.div_euclid(12)).ok().ok_or_else(err)?;\n                fields.month = i8::try_from(total.rem_euclid(12) + 1).expect(\"a value in 1..=12\");\n            }","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-date/src/parse/relative.rs#L166-L202","documentation":"In gix-date's relative-date `subtract_pairs`, each (count, unit) pair must be convertible into a jiff `Span`. The closure `err` builds Error 'Couldn't parse span from \\'{period} {count}\\'' raised when `count.checked_mul(factor)` overflows for `Unit::Seconds(factor)` (and analogous failures building spans for other units), i.e. the numeric count times its factor does not fit.","triggerScenarios":"Calling relative-date `parse` (directly or via `gix_date::parse`) with a pair whose count is huge — e.g. '18446744073709551615 seconds ago' — where `checked_mul` on the second-factor overflows i64/u64.","commonSituations":"Users passing absurdly large numbers to relative date expressions (`--since=999999999999999999999 seconds ago`) in tooling built on gix-date.","solutions":["Reduce the count to a value that fits when multiplied by the unit factor (keep counts within i64-safe bounds)","Pre-validate the numeric count before handing the pair list to gix-date","Catch the Error and report the offending 'period count' text to the user"],"exampleFix":"// before\nparse(now, \"18446744073709551615 seconds ago\")\n// after\nlet count = count.min(i64::MAX as u64 / factor);\nparse(now, &format!(\"{count} seconds ago\"))","handlingStrategy":"validation","validationCode":"fn count_safe(count: u64, factor: u64) -> bool {\n    count.checked_mul(factor).is_some()\n}","typeGuard":null,"tryCatchPattern":"match parse_relative(input) {\n    Ok(t) => t,\n    Err(e) => { warn!(\"bad relative date: {e}\"); now() },\n}","preventionTips":["Validate numeric magnitude of relative-date components before parsing","Clamp counts to i64-safe bounds per unit factor","Reject nonsensical inputs early with clear user messages"],"tags":["date","parsing","overflow","duration"],"backgroundTag":"invalid-duration-format","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}