{"record":{"id":"607fbb6549f65633","repo":"python/cpython","slug":"malformed-time-zone-string","errorCode":null,"errorMessage":"Malformed time zone string","messagePattern":"Malformed time zone string","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":485,"sourceCode":"            error_from_components = True\n\n    tzi = None\n    if tz_pos == len_str and tstr[-1] == 'Z':\n        tzi = timezone.utc\n    elif tz_pos > 0:\n        tzstr = tstr[tz_pos:]\n\n        # Valid time zone strings are:\n        # HH                  len: 2\n        # HHMM                len: 4\n        # HH:MM               len: 5\n        # HHMMSS              len: 6\n        # HHMMSS.f+           len: 7+\n        # HH:MM:SS            len: 8\n        # HH:MM:SS.f+         len: 10+\n\n        if len(tzstr) in (0, 1, 3) or tstr[tz_pos-1] == 'Z':\n            raise ValueError(\"Malformed time zone string\")\n\n        tz_comps = _parse_hh_mm_ss_ff(tzstr)\n\n        if all(x == 0 for x in tz_comps):\n            tzi = timezone.utc\n        else:\n            tzsign = -1 if tstr[tz_pos - 1] == '-' else 1\n\n            try:\n                # This function is intended to validate datetimes, but because\n                # we restrict time zones to ±24h, it serves here as well.\n                _check_time_fields(hour=tz_comps[0], minute=tz_comps[1],\n                                   second=tz_comps[2], microsecond=tz_comps[3],\n                                   fold=0)\n            except ValueError as e:\n                error_from_tz = e\n            else:\n                td = timedelta(hours=tz_comps[0], minutes=tz_comps[1],","sourceCodeStart":467,"sourceCodeEnd":503,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L467-L503","documentation":"After a timezone sign ('+'/'-') or 'Z' is found, the remaining tz string must match one of the documented lengths: 2 (HH), 4 (HHMM), 5 (HH:MM), 6 (HHMMSS), 7+ (HHMMSS.f), 8 (HH:MM:SS), 10+ (HH:MM:SS.f). Lengths 0, 1, 3 — or a 'Z' followed by more characters — raise ValueError('Malformed time zone string').","triggerScenarios":"datetime.fromisoformat('2021-01-01T12:30:00+0'); '+01:0' (length 5 is fine but '+01' length 3 is not); '...Z01:00' (Z plus trailing offset); '...+01:0' vs '+010' (length 4 needs compact HHMM).","commonSituations":"Offsets hand-built as f'+{tz}' without zero-padding hours/minutes; slicing offsets to fixed width and cutting a digit; supporting 'Z' plus offset simultaneously in serialized data; JSON payloads where the offset lost a leading zero ('+1:00' length 5 parses as malformed content elsewhere, '+1' length 2 with bad digits errors later).","solutions":["Emit offsets with strftime('%z') or isoformat(), producing '+HHMM'/'±HH:MM[:SS[.f]]'","Zero-pad when building manually: f'{sign}{hh:02d}:{mm:02d}'","Treat a trailing 'Z' as terminal: strip everything after it or reject 'Z+offset' combinations at validation"],"exampleFix":"// before\ntz = f'+{hours}:{minutes}'          # '+1:30' shape issues / '+1' length errors\ndt = datetime.fromisoformat(f'{s}{tz}')\n\n# after\ntz = f'+{hours:02d}:{minutes:02d}'  # '+01:30'\ndt = datetime.fromisoformat(f'{s}{tz}')","handlingStrategy":"validation","validationCode":"def build_offset(sign: int, hh: int, mm: int, ss: int = 0) -> str:\n    return f\"{'+' if sign >= 0 else '-'}{hh:02d}:{mm:02d}\" + (f':{ss:02d}' if ss else '')","typeGuard":"def offset_shape_ok(tz: str) -> bool:\n    import re\n    return re.fullmatch(r'(Z|[+-]\\d{2}(:?\\d{2}(:?\\d{2}([.,]\\d+)?)?)?)', tz) is not None","tryCatchPattern":null,"preventionTips":["Always produce offsets with strftime('%z') or isoformat()","Zero-pad offset hours and minutes","Never append characters after a 'Z' marker"],"tags":["datetime","fromisoformat","timezone","valueerror","stdlib"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}