Pickling and copying

Every whenever value pickles, on the Rust extension and on the pure-Python backend alike, and a pickle written by one backend loads on the other. The one exception is a ZonedDateTime whose system time zone has no time zone ID: pickling it raises ValueError (see The system time zone). copy.copy() and copy.deepcopy() return the value itself, because values are immutable.

Zoned datetimes across time zone data updates

A pickled ZonedDateTime stores its local fields, observed offset, and time zone ID. It does not embed a snapshot of the time zone database. Unpickling loads the rules available in the new environment, which may differ after a time zone data update or on another machine.

When the rules are unchanged, the round trip preserves the instant, local representation, offset, and time zone ID without a warning:

>>> import pickle
>>> original = ZonedDateTime(2024, 7, 1, 12, tz="Europe/Paris")
>>> restored = pickle.loads(pickle.dumps(original))
>>> restored.strict_eq(original)
True

If a political decision changes the applicable offset, Whenever preserves the original instant and rebuilds the local representation using the current rules. A hypothetical example: a pickle of 2030-06-01 12:00+02:00[Europe/Paris] written today would load as 2030-06-01 11:00+01:00[Europe/Paris] on a machine whose time zone data records the EU abolishing summer time. It emits PickleOffsetMismatchWarning describing the stored offset and local datetime, their replacements, and that the instant was preserved.

This is an offset mismatch resolved as keep_instant, the same resolution parse_iso() offers through offset_mismatch=. It warns because pickle.loads() has no argument to pass the policy to.

Applications that require the exact time zone rules used when writing the pickle should pin their time zone data version. They can reject a mismatch with the standard warning machinery:

import warnings
from whenever import PickleOffsetMismatchWarning

warnings.filterwarnings("error", category=PickleOffsetMismatchWarning)
restored = pickle.loads(payload)

This behavior prevents unpickling from creating a zoned datetime whose offset contradicts its time zone rules. Unknown time zone IDs and malformed or out-of-range payloads still raise exceptions.

Compatibility

Every pickle written by 0.8.0 or later loads, as long as its type still exists. 0.11 also loads Instant pickles written before 0.8.0; 1.0 won’t. DateDelta and DateTimeDelta pickles stopped loading in 0.11 with their types; SystemDateTime pickles in 0.9.0. The payload stores a time zone ID, never the rules, so what a pickle loads as depends on the time zone data of the machine that loads it.