{"record":{"id":"9d3965876df67dda","repo":"python/cpython","slug":"fromutc-dt-tzinfo-is-not-self","errorCode":null,"errorMessage":"fromutc: dt.tzinfo is not self","messagePattern":"fromutc: dt\\.tzinfo is not self","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pydatetime.py","lineNumber":2537,"sourceCode":"\n    def tzname(self, dt):\n        if isinstance(dt, datetime) or dt is None:\n            if self._name is None:\n                return self._name_from_offset(self._offset)\n            return self._name\n        raise TypeError(\"tzname() argument must be a datetime instance\"\n                        \" or None\")\n\n    def dst(self, dt):\n        if isinstance(dt, datetime) or dt is None:\n            return None\n        raise TypeError(\"dst() argument must be a datetime instance\"\n                        \" or None\")\n\n    def fromutc(self, dt):\n        if isinstance(dt, datetime):\n            if dt.tzinfo is not self:\n                raise ValueError(\"fromutc: dt.tzinfo \"\n                                 \"is not self\")\n            return dt + self._offset\n        raise TypeError(\"fromutc() argument must be a datetime instance\"\n                        \" or None\")\n\n    _maxoffset = timedelta(hours=24, microseconds=-1)\n    _minoffset = -_maxoffset\n\n    @staticmethod\n    def _name_from_offset(delta):\n        if not delta:\n            return 'UTC'\n        if delta < timedelta(0):\n            sign = '-'\n            delta = -delta\n        else:\n            sign = '+'\n        hours, rest = divmod(delta, timedelta(hours=1))","sourceCodeStart":2519,"sourceCodeEnd":2555,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pydatetime.py#L2519-L2555","documentation":"timezone.fromutc(dt) converts a UTC-wall-clock datetime into local time by adding the fixed offset — but only when dt.tzinfo is exactly this timezone instance. Passing a datetime attached to a different tzinfo object (even an equal-offset one) raises ValueError, because fromutc would otherwise produce a datetime labeled with the wrong zone.","triggerScenarios":"utc_tz.fromutc(dt) where dt.tzinfo is timezone.utc or another timezone instance; datetime.astimezone internally relies on fromutc with self-attached datetimes, so direct misuse is the usual trigger; reusing datetimes across zone objects.","commonSituations":"Manual UTC→local conversions that forget dt.replace(tzinfo=tz); caching converted datetimes and re-running fromutc on them; helper functions receiving datetimes already bound to another zone.","solutions":["Attach the zone first: tz.fromutc(dt.replace(tzinfo=tz))","Prefer the public API: dt.astimezone(tz) handles attachment and fromutc correctly","Ensure helper functions rebind tzinfo rather than assuming it is already set"],"exampleFix":"// before\nlocal = my_tz.fromutc(utc_dt)  # utc_dt.tzinfo is timezone.utc\n\n# after\nlocal = utc_dt.astimezone(my_tz)","handlingStrategy":"validation","validationCode":"from datetime import datetime\n\ndef from_utc(tz, dt: datetime) -> datetime:\n    if dt.tzinfo is not tz:\n        dt = dt.replace(tzinfo=tz)\n    return tz.fromutc(dt)","typeGuard":"def is_bound_to(dt, tz) -> bool:\n    return dt.tzinfo is tz","tryCatchPattern":"try:\n    local = tz.fromutc(dt)\nexcept ValueError:\n    local = dt.astimezone(tz)  # public API handles binding","preventionTips":["Prefer dt.astimezone(tz) over direct fromutc calls","Rebind tzinfo with replace() before manual fromutc","Check dt.tzinfo is tz before protocol-level conversion"],"tags":["python","datetime","timezone","value-error","utc"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}