pypa/pip · error · ValueError
Cannot serialize {obj!r} where tzinfo=None
Error message
Cannot serialize {obj!r} where tzinfo=None What it means
Raised by msgpack's pure-Python Packer._pack when datetime support is enabled (datetime=True, the default in newer versions) but the datetime object has tzinfo=None — a naive datetime. The Timestamp extension type computes UTC seconds from dt.timestamp(), which for naive datetimes is ambiguous (interpreted as local time), so msgpack explicitly refuses to serialize it to prevent silent data corruption.
Source
Thrown at src/pip/_vendor/msgpack/fallback.py:802
self._pack_array_header(n)
for i in range(n):
self._pack(obj[i], nest_limit - 1)
return
if check(obj, dict):
return self._pack_map_pairs(len(obj), obj.items(), nest_limit - 1)
if self._datetime and check(obj, _DateTime) and obj.tzinfo is not None:
obj = Timestamp.from_datetime(obj)
default_used = 1
continue
if not default_used and self._default is not None:
obj = self._default(obj)
default_used = 1
continue
if self._datetime and check(obj, _DateTime):
raise ValueError(f"Cannot serialize {obj!r} where tzinfo=None")
raise TypeError(f"Cannot serialize {obj!r}")
def pack(self, obj):
try:
self._pack(obj)
except:
self._buffer = BytesIO() # force reset
raise
if self._autoreset:
ret = self._buffer.getvalue()
self._buffer = BytesIO()
return ret
def pack_map_pairs(self, pairs):
self._pack_map_pairs(len(pairs), pairs)
if self._autoreset:
ret = self._buffer.getvalue()View on GitHub (pinned to f399c37189)
Solutions
- Make the datetime timezone-aware before packing: dt.replace(tzinfo=timezone.utc) or dt.astimezone(timezone.utc).
- Use datetime.now(timezone.utc) instead of datetime.now() at creation time.
- If the original timezone is unknown but you know it is UTC, attach tzinfo=datetime.timezone.utc explicitly.
- Disable datetime support (datetime=False) and serialize the datetime yourself if you must handle naive datetimes.
Example fix
# before import datetime packer = msgpack.Packer(datetime=True) packer.pack(datetime.datetime(2024, 1, 1)) # naive — raises # after dt = datetime.datetime(2024, 1, 1, tzinfo=datetime.timezone.utc) packer.pack(dt)
Defensive patterns
Strategy: validation
Validate before calling
import datetime
def is_aware_datetime(dt) -> bool:
return isinstance(dt, datetime.datetime) and dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None Type guard
import datetime
def is_timezone_aware(dt) -> bool:
return isinstance(dt, datetime.datetime) and dt.tzinfo is not None Try / catch
import datetime
for dt in datetimes:
if dt.tzinfo is None:
dt = dt.replace(tzinfo=datetime.timezone.utc)
packer.pack(dt) Prevention
- Always use datetime.now(timezone.utc) instead of datetime.now().
- Apply .replace(tzinfo=timezone.utc) to naive datetimes from external sources.
- Use pydantic or attrs validators to enforce timezone-awareness.
- Set a global lint rule or pre-commit check to flag naive datetime usage.
When it happens
Trigger: Packing a datetime.datetime or datetime.time without tzinfo set while the Packer is configured with datetime=True (or the default enables it). The code path at line 791 checks obj.tzinfo is not None before converting; line 802 catches the fallback where tzinfo is still None.
Common situations: Application creates datetime.now() (naive, local) instead of datetime.now(timezone.utc); data from a database or CSV parsed as naive datetimes; legacy code predating timezone-aware datetimes.
Related errors
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/680d5c3272457a26.
Report an issue: GitHub.