Skip to content

Make Timezone instances built via from_file() picklable - #1000

Open
afonsojanu wants to merge 1 commit into
python-pendulum:masterfrom
afonsojanu:fix/pickle-keyless-local-timezone-899
Open

Make Timezone instances built via from_file() picklable#1000
afonsojanu wants to merge 1 commit into
python-pendulum:masterfrom
afonsojanu:fix/pickle-keyless-local-timezone-899

Conversation

@afonsojanu

Copy link
Copy Markdown

Closes #899.

get_local_timezone()'s last-resort fallback reads the raw zoneinfo file at /etc/localtime (or the file the TZ environment variable points at) directly, since the system's timezone name couldn't be derived any other way. That goes through Timezone.from_file(), and the underlying zoneinfo.ZoneInfo.from_file() refuses to pickle any instance built that way, key or no key, since it has no record of which file it came from to reconstruct on unpickling.

import pickle, pendulum
dt = pendulum.now()
pickle.dumps(dt)
# _pickle.PicklingError: Cannot pickle a ZoneInfo file from a file stream.

This reproduces whenever the local timezone name can't be identified (as reported in the issue), so the local tzinfo ends up being one of these keyless, file-based instances.

Timezone.from_file() now keeps the raw TZif bytes it read around on the instance, and __reduce__ uses them to rebuild an equivalent instance on unpickling instead of delegating to the version inherited from zoneinfo.ZoneInfo, which unconditionally rejects this. Instances built via the regular Timezone(key) constructor still pickle exactly as before, by key.

Added two tests covering both cases (keyless from_file(), and from_file() with a key), using the raw TZif bytes from the tzdata package (already a dependency) so the test works the same on every platform, including Windows, which has no /usr/share/zoneinfo at all.

get_local_timezone()'s last-resort fallback reads the raw zoneinfo
file at /etc/localtime (or the value of TZ, when it points at a
file) directly, since the system's timezone name couldn't be derived
any other way. That goes through Timezone.from_file(), and the
underlying zoneinfo.ZoneInfo.from_file() refuses to pickle any
instance built that way, key or no key, since it has no record of
which file it came from to reconstruct on unpickling. Attempting to
pickle a DateTime carrying that tzinfo raises PicklingError: Cannot
pickle a ZoneInfo file from a file stream.

Timezone.from_file() now keeps the raw TZif bytes it read around on
the instance, and __reduce__ uses them to rebuild an equivalent
instance on unpickling instead of delegating to the version inherited
from zoneinfo.ZoneInfo, which unconditionally rejects this. Instances
built via the regular Timezone(key) constructor still pickle exactly
as before.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DateTime of version 3.0+ cannot be pickled without timezone

1 participant