Skip to content

Commit

Permalink
Replace pytz and tzlocal by zoneinfo
Browse files Browse the repository at this point in the history
Requires Python 3.9
  • Loading branch information
jspricke authored and tbabej committed Mar 15, 2022
1 parent e11e163 commit 0307266
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 49 deletions.
14 changes: 7 additions & 7 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -305,15 +305,15 @@ Dealing with dates and time
---------------------------

Any timestamp-like attributes of the tasks are converted to timezone-aware
datetime objects. To achieve this, Tasklib leverages ``pytz`` Python module,
datetime objects. To achieve this, Tasklib leverages ``zoneinfo`` Python module,
which brings the Olsen timezone database to Python.

This shields you from annoying details of Daylight Saving Time shifts
or conversion between different timezones. For example, to list all the
tasks which are due midnight if you're currently in Berlin:

>>> myzone = pytz.timezone('Europe/Berlin')
>>> midnight = myzone.localize(datetime(2015,2,2,0,0,0))
>>> myzone = zoneinfo.ZoneInfo('Europe/Berlin')
>>> midnight = datetime(2015,2,2,0,0,0,tzinfo=myzone)
>>> tw.tasks.filter(due__before=midnight)

However, this is still a little bit tedious. That's why TaskWarrior object
Expand Down Expand Up @@ -360,7 +360,7 @@ to localize the naive value first:

>>> from datetime import datetime
>>> from tasklib.task import local_zone
>>> now = local_zone.localize(datetime.now())
>>> now = datetime.now().replace(tzinfo=local_zone)
>>> t['due'] = now
>>> now
datetime.datetime(2015, 2, 1, 19, 44, 4, 770001, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>)
Expand All @@ -370,12 +370,12 @@ to localize the naive value first:
Also, note that it does not matter whether the timezone aware datetime objects
are set in the same timezone:

>>> import pytz
>>> import zoneinfo
>>> t['due']
datetime.datetime(2015, 2, 1, 19, 44, 4, 770001, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>)
>>> now.astimezone(pytz.utc)
>>> now.astimezone(zoneinfo.ZoneInfo('UTC'))
datetime.datetime(2015, 2, 1, 18, 44, 4, 770001, tzinfo=<UTC>)
>>> t['due'] == now.astimezone(pytz.utc)
>>> t['due'] == now.astimezone(zoneinfo.ZoneInfo('UTC'))
True

*Note*: Following behaviour is available only for TaskWarrior >= 2.4.0.
Expand Down
3 changes: 0 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from setuptools import setup, find_packages

install_requirements = ['pytz', 'tzlocal']

version = '2.4.3'

setup(
Expand All @@ -17,7 +15,6 @@
packages=find_packages(),
include_package_data=True,
test_suite='tasklib.tests',
install_requires=install_requirements,
classifiers=[
'Development Status :: 6 - Mature',
'Programming Language :: Python',
Expand Down
2 changes: 1 addition & 1 deletion tasklib/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def convert_datetime_string(self, value):
args = value.split()
result = self.execute_command(['calc'] + args)
naive = datetime.datetime.strptime(result[0], DATE_FORMAT_CALC)
localized = local_zone.localize(naive)
localized = naive.replace(tzinfo=local_zone)
return localized

@property
Expand Down
13 changes: 6 additions & 7 deletions tasklib/serializing.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import datetime
import importlib
import json
import pytz
import tzlocal
from zoneinfo import ZoneInfo


from .lazy import LazyUUIDTaskSet, LazyUUIDTask

DATE_FORMAT = '%Y%m%dT%H%M%SZ'
local_zone = pytz.timezone(str(tzlocal.get_localzone()))
local_zone = ZoneInfo('localtime')


class SerializingObject(object):
Expand Down Expand Up @@ -73,7 +72,7 @@ def timestamp_serializer(self, date):

# Any serialized timestamp should be localized, we need to
# convert to UTC before converting to string (DATE_FORMAT uses UTC)
date = date.astimezone(pytz.utc)
date = date.astimezone(ZoneInfo('UTC'))

return date.strftime(DATE_FORMAT)

Expand All @@ -83,7 +82,7 @@ def timestamp_deserializer(self, date_str):

# Return timestamp localized in the local zone
naive_timestamp = datetime.datetime.strptime(date_str, DATE_FORMAT)
localized_timestamp = pytz.utc.localize(naive_timestamp)
localized_timestamp = naive_timestamp.replace(tzinfo=ZoneInfo('UTC'))
return localized_timestamp.astimezone(local_zone)

def serialize_entry(self, value):
Expand Down Expand Up @@ -226,11 +225,11 @@ def datetime_normalizer(self, value):
):
# Convert to local midnight
value_full = datetime.datetime.combine(value, datetime.time.min)
localized = local_zone.localize(value_full)
localized = value_full.replace(tzinfo=local_zone)
elif isinstance(value, datetime.datetime):
if value.tzinfo is None:
# Convert to localized datetime object
localized = local_zone.localize(value)
localized = value.replace(tzinfo=local_zone)
else:
# If the value is already localized, there is no need to change
# time zone at this point. Also None is a valid value too.
Expand Down
2 changes: 1 addition & 1 deletion tasklib/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ def waiting(self):
if not self['wait']:
return False

return self['wait'] > local_zone.localize(datetime.datetime.now())
return self['wait'] > datetime.datetime.now().replace(tzinfo=local_zone)

@property
def pending(self):
Expand Down
59 changes: 29 additions & 30 deletions tasklib/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
import itertools
import json
import os
import pytz
import shutil
import sys
import tempfile
import unittest
from io import StringIO
from zoneinfo import ZoneInfo

from .backends import TaskWarrior
from .task import Task, ReadOnlyDictView
Expand Down Expand Up @@ -1110,8 +1110,7 @@ def test_export_data(self):
self.tw,
description='test task',
project='Home',
due=pytz.utc.localize(
datetime.datetime(2015, 1, 1, 23, 23, 23)),
due=datetime.datetime(2015, 1, 1, 23, 23, 23, tzinfo=ZoneInfo('UTC')),
)

# Check that the output is a permutation of:
Expand All @@ -1135,8 +1134,8 @@ def setUp(self):
self.zone = local_zone
self.localdate_naive = datetime.datetime(2015, 2, 2)
self.localtime_naive = datetime.datetime(2015, 2, 2, 0, 0, 0)
self.localtime_aware = self.zone.localize(self.localtime_naive)
self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
self.localtime_aware = self.localtime_naive.replace(tzinfo=self.zone)
self.utctime_aware = self.localtime_aware.astimezone(ZoneInfo('UTC'))

def test_timezone_naive_datetime_setitem(self):
t = Task(self.tw, description='test task')
Expand Down Expand Up @@ -1217,7 +1216,7 @@ def test_simple_now_conversion(self):
return

t = Task(self.tw, description='test task', due='now')
now = local_zone.localize(datetime.datetime.now())
now = datetime.datetime.now().replace(tzinfo=local_zone)

# Assert that both times are not more than 5 seconds apart
if sys.version_info < (2, 7):
Expand All @@ -1237,24 +1236,26 @@ def test_simple_eoy_conversion(self):
return

t = Task(self.tw, description='test task', due='eoy')
now = local_zone.localize(datetime.datetime.now())
eoy = local_zone.localize(datetime.datetime(
now = datetime.datetime.now().replace(tzinfo=local_zone)
eoy = datetime.datetime(
year=now.year,
month=12,
day=31,
hour=23,
minute=59,
second=59,
))
tzinfo=local_zone
)
if self.tw.version >= '2.5.2' and self.tw.version < '2.6.0':
eoy = local_zone.localize(datetime.datetime(
eoy = datetime.datetime(
year=now.year+1,
month=1,
day=1,
hour=0,
minute=0,
second=0,
))
tzinfo=local_zone
)
self.assertEqual(eoy, t['due'])

def test_complex_eoy_conversion(self):
Expand All @@ -1267,27 +1268,25 @@ def test_complex_eoy_conversion(self):
return

t = Task(self.tw, description='test task', due='eoy - 4 months')
now = local_zone.localize(datetime.datetime.now())
due_date = local_zone.localize(
datetime.datetime(
year=now.year,
month=12,
day=31,
hour=23,
minute=59,
second=59,
)
now = datetime.datetime.now().replace(tzinfo=local_zone)
due_date = datetime.datetime(
year=now.year,
month=12,
day=31,
hour=23,
minute=59,
second=59,
tzinfo=local_zone
) - datetime.timedelta(0, 4 * 30 * 86400)
if self.tw.version >= '2.5.2' and self.tw.version < '2.6.0':
due_date = local_zone.localize(
datetime.datetime(
year=now.year+1,
month=1,
day=1,
hour=0,
minute=0,
second=0,
)
due_date = datetime.datetime(
year=now.year+1,
month=1,
day=1,
hour=0,
minute=0,
second=0,
tzinfo=local_zone
) - datetime.timedelta(0, 4 * 30 * 86400)
self.assertEqual(due_date, t['due'])

Expand Down

0 comments on commit 0307266

Please sign in to comment.