zopefoundation / DateTime

This package provides a DateTime data type, as known from Zope. Unless you need to communicate with Zope APIs, you're probably better off using Python's built-in datetime module.
Other
19 stars 25 forks source link

`pytz.exceptions.UnknownTimeZoneError` when unpickling `DateTime.DateTime('GMT+1').asdatetime()` #58

Closed perrinjerome closed 9 months ago

perrinjerome commented 9 months ago

BUG/PROBLEM REPORT / FEATURE REQUEST

What I did:

DateTime.asdatetime converts to datetime carrying the same date, including timezone, but such datetimes cannot be pickled:

>>> import DateTime, pickle; pickle.loads(pickle.dumps(DateTime.DateTime('GMT+1').asdatetime()))
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/srv/slapgrid/slappart30/srv/project/zope/venv/lib/python3.9/site-packages/pytz/__init__.py", line 307, in _p
    return unpickler(*args)
  File "/srv/slapgrid/slappart30/srv/project/zope/venv/lib/python3.9/site-packages/pytz/tzinfo.py", line 542, in unpickler
    tz = pytz.timezone(zone)
  File "/srv/slapgrid/slappart30/srv/project/zope/venv/lib/python3.9/site-packages/pytz/__init__.py", line 188, in timezone
    raise UnknownTimeZoneError(zone)
pytz.exceptions.UnknownTimeZoneError: 'GMT+1'

What I expect to happen:

the datetime instance can be unpickled

What actually happened:

an UnknownTimeZoneError is raised

What version of Python and Zope/Addons I am using:

pytz 2023.3.post1 DateTime current master branch ( 310488279d2cefe53f8f5f77020b72fbf2ddac0c )

More information:

DateTime constructs its own timezones with GMT+1 names that are defined here:

https://github.com/zopefoundation/DateTime/blob/310488279d2cefe53f8f5f77020b72fbf2ddac0c/src/DateTime/pytz_support.py#L27-L30

and used to create a registry with instances of a subclass of pytz.tzinfo.StaticTzInfo here:

https://github.com/zopefoundation/DateTime/blob/310488279d2cefe53f8f5f77020b72fbf2ddac0c/src/DateTime/pytz_support.py#L202-L212

unpickling such an instance is causing the UnknownTimeZoneError exception, because pytz.tzinfo.StaticTzInfo defines __reduce__ which calls _p, which is just a wrapper calling unpickler which basically does this:

def unpickler(zone):
    ...
    if zone not in _tzinfo_cache:
        if zone in all_timezones_set:  # noqa
            fp = open_resource(zone)
            try:
                _tzinfo_cache[zone] = build_tzinfo(zone, fp)
            finally:
                fp.close()
        else:
            raise UnknownTimeZoneError(zone)

where the UnknownTimeZoneError is raised.

I believe the fix can be to adjust _static_timezone_factory to create classes with a __reduce__ understanding the extra timezones from DateTime.pytz_support, I'm preparing a patch.