diff --git a/crates/doc/src/vars.rs b/crates/doc/src/vars.rs
--- a/crates/doc/src/vars.rs
+++ b/crates/doc/src/vars.rs
@@ -195,8 +195,12 @@ pub fn parse_iso_date(s: &str) -> Option<i64> {
         None | Some('Z') => Some(secs),
         Some(sign @ ('+' | '-')) => {
             let z = zone.get(1..)?;
-            let zh: i64 = z.get(0..2)?.parse().ok()?;
-            let zm: i64 = z.get(2..).map(|r| r.trim_start_matches(':')).filter(|r| !r.is_empty()).map_or(Some(0), |r| r.parse().ok())?;
+            let digits = |t: &str| -> Option<i64> { t.bytes().all(|c| c.is_ascii_digit()).then(|| t.parse().ok()).flatten() };
+            let zh = digits(z.get(0..2)?)?;
+            let zm = z.get(2..).map(|r| r.trim_start_matches(':')).filter(|r| !r.is_empty()).map_or(Some(0), digits)?;
+            if zh > 23 || zm > 59 {
+                return None;
+            }
             let off = zh * 3600 + zm * 60;
             Some(if sign == '+' { secs - off } else { secs + off })
         }
@@ -336,6 +340,13 @@ mod tests {
         }
     }
 
+    #[test]
+    fn iso_date_rejects_malformed_zone() {
+        for bad in ["2019-12-16T09:48:02+-5:00", "2019-12-16T09:48:02+05:+30", "2019-12-16T09:48:02+24:00", "2019-12-16T09:48:02+05:60"] {
+            assert_eq!(parse_iso_date(bad), None, "{bad}");
+        }
+    }
+
     #[test]
     fn chars() {
         assert_eq!(var_index(var_char(3).unwrap()), Some(3));
