{"record":{"id":"cc0b883f7220fa0c","repo":"pola-rs/polars","slug":"unexpected-time-zone-offset-offset-r","errorCode":null,"errorMessage":"unexpected time zone offset: {offset!r}","messagePattern":"unexpected time zone offset: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"py-polars/src/polars/_utils/convert.py","lineNumber":192,"sourceCode":"        tz = _parse_fixed_tz_offset(time_zone)\n\n    return dt.astimezone(tz)\n\n\n# cache here as we have a single tz per column\n# and this function will be called on every conversion\n@lru_cache(16)\ndef _parse_fixed_tz_offset(offset: str) -> tzinfo:\n    try:\n        # use fromisoformat to parse the offset\n        dt_offset = datetime.fromisoformat(\"2000-01-01T00:00:00\" + offset)\n\n        # alternatively, we parse the offset ourselves extracting hours and\n        # minutes, then we can construct:\n        # tzinfo=timezone(timedelta(hours=..., minutes=...))\n    except ValueError:\n        msg = f\"unexpected time zone offset: {offset!r}\"\n        raise ValueError(msg) from None\n\n    return dt_offset.tzinfo  # type: ignore[return-value]\n\n\ndef to_py_timedelta(value: int | float, time_unit: TimeUnit) -> timedelta:\n    \"\"\"Convert an integer or float to a Python timedelta object.\"\"\"\n    if time_unit == \"us\":\n        return timedelta(microseconds=value)\n    elif time_unit == \"ns\":\n        return timedelta(microseconds=value // 1_000)\n    elif time_unit == \"ms\":\n        return timedelta(milliseconds=value)\n    else:\n        _raise_invalid_time_unit(time_unit)\n\n\ndef to_py_decimal(prec: int, value: str) -> Decimal:\n    \"\"\"Convert decimal components to a Python Decimal object.\"\"\"","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/pola-rs/polars/blob/df599052daf96e7a9cc30a3b0c6bd25d6947e3c0/py-polars/src/polars/_utils/convert.py#L174-L210","documentation":"_parse_fixed_tz_offset (lru_cached) parses fixed UTC offset strings by appending them to an ISO datetime and calling datetime.fromisoformat. If the offset is not in the strict ±HH:MM form that fromisoformat accepts (e.g. \"UTC+02:00\", \"+0200\", \"+2\", or trailing garbage), the ValueError from fromisoformat is re-raised as this error with the offending offset.","triggerScenarios":"Datetime strings with non-canonical fixed offsets (\"2024-01-01T00:00:00UTC+02:00\", \"+0200\" on Python < 3.11); offsets like \"+0530\" without a colon on older interpreters; stray characters appended to the offset field.","commonSituations":"Parsing logs or exports whose tz field uses \"GMT+2\"-style or colon-less formats; running on Python 3.10 or older where fromisoformat only accepts ±HH:MM[:SS[.ffffff]]; pandas→polars conversion of tz-aware data with unusual fixed offsets.","solutions":["Normalize the offset to ±HH:MM before parsing (strip 'UTC'/'GMT', insert the colon).","Upgrade to Python ≥ 3.11, where fromisoformat accepts \"+0200\" and more variants.","Convert the offset yourself: timezone(timedelta(hours=h, minutes=m)) and pass tzinfo directly instead of the string."],"exampleFix":"// before\npl.Series([\"2024-01-01T00:00:00+0200\"]).str.to_datetime()\n\n// after\nimport re\ns = pl.Series([\"2024-01-01T00:00:00+0200\"])\nfixed = re.sub(r\"([+-]\\d{2})(\\d{2})$\", r\"\\1:\\2\", s[0])\npl.Series([fixed]).str.to_datetime()","handlingStrategy":"validation","validationCode":"import re\n\nVALID_FIXED_OFFSET = re.compile(r\"^[+-]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?$\")\n\ndef normalize_offset(off: str) -> str:\n    off = re.sub(r\"^(?:UTC|GMT)\", \"\", off.strip())\n    m = re.fullmatch(r\"([+-]\\d{2})(\\d{2})\", off)\n    if m:\n        off = f\"{m.group(1)}:{m.group(2)}\"\n    if not VALID_FIXED_OFFSET.fullmatch(off):\n        raise ValueError(f\"unexpected time zone offset: {off!r}\")\n    return off","typeGuard":"def is_iso_fixed_offset(off: str) -> bool:\n    import re\n    return bool(re.fullmatch(r\"[+-]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?\", off))","tryCatchPattern":"try:\n    s = pl.Series([dt_string]).str.to_datetime()\nexcept ValueError as e:\n    if \"unexpected time zone offset\" in str(e):\n        s = pl.Series([normalize_offset_string(dt_string)]).str.to_datetime()\n    else:\n        raise","preventionTips":["Normalize tz fields to ±HH:MM before parsing on any Python version.","Run datetime parsing on Python ≥ 3.11 for lenient fromisoformat.","Prefer zoneinfo/typed tzinfo over free-form offset strings in your data model."],"tags":["timezone","datetime","parsing","python-version"],"backgroundTag":null,"analyzedSha":"df599052daf96e7a9cc30a3b0c6bd25d6947e3c0","analyzedAt":"2026-08-16T12:10:03.978Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}