Skip to content

Commit c28afef

Browse files
committed
Drop support for pytz
Replace pytz with zoneinfo which is part of the Python standard library since Python 3.9. Bug-Ubuntu: https://launchpad.net/bugs/2101044
1 parent 6a10b3f commit c28afef

6 files changed

Lines changed: 71 additions & 84 deletions

File tree

README.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,11 @@ About DST
109109
=========
110110
Be sure to init your croniter instance with a TZ aware datetime for this to work!
111111

112-
Example using pytz::
112+
Example using zoneinfo::
113113

114-
>>> import pytz
115-
>>> tz = pytz.timezone("Europe/Paris")
116-
>>> local_date = tz.localize(datetime(2017, 3, 26))
114+
>>> import zoneinfo
115+
>>> tz = zoneinfo.ZoneInfo("Europe/Paris")
116+
>>> local_date = datetime(2017, 3, 26, tzinfo=tz)
117117
>>> val = croniter('0 0 * * *', local_date).get_next(datetime)
118118

119119
Example using python_dateutil::

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ classifiers = [
3030
]
3131
dependencies = [
3232
"python_dateutil",
33-
"pytz>2021.1",
3433
]
3534
dynamic = ["readme"]
3635

src/croniter/croniter.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212
import traceback as _traceback
1313
from time import time
1414

15-
# as pytz is optional in thirdparty libs but we need it for good support under
16-
# python2, just test that it's well installed
17-
import pytz # noqa
1815
from dateutil.relativedelta import relativedelta
1916
from dateutil.tz import tzutc
2017

@@ -63,11 +60,7 @@ def is_32bit():
6360
OrderedDict = dict # py26 degraded mode, expanders order will not be immutable
6461

6562

66-
try:
67-
# py3 recent
68-
UTC_DT = datetime.timezone.utc
69-
except AttributeError:
70-
UTC_DT = pytz.utc
63+
UTC_DT = datetime.timezone.utc
7164
EPOCH = datetime.datetime.fromtimestamp(0, UTC_DT)
7265

7366
# fmt: off

src/croniter/tests/test_croniter.py

Lines changed: 53 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
except ImportError:
66
import unittest
77

8+
import zoneinfo
89
from datetime import datetime, timedelta
910
from functools import partial
1011
from time import sleep
1112

1213
import dateutil.tz
13-
import pytz
1414

1515
from croniter import (
1616
CroniterBadCronError,
@@ -731,10 +731,11 @@ def testTimezone(self):
731731
n1 = itr.get_next(datetime)
732732
self.assertEqual(n1.tzinfo, None)
733733

734-
tokyo = pytz.timezone("Asia/Tokyo")
735-
itr2 = croniter("* * * * *", tokyo.localize(base))
734+
tokyo = zoneinfo.ZoneInfo("Asia/Tokyo")
735+
start = datetime(2013, 3, 4, 12, 15, tzinfo=tokyo)
736+
itr2 = croniter("* * * * *", start)
736737
n2 = itr2.get_next(datetime)
737-
self.assertEqual(n2.tzinfo.zone, "Asia/Tokyo")
738+
self.assertEqual(n2.tzinfo.key, "Asia/Tokyo")
738739

739740
def testTimezoneDateutil(self):
740741
tokyo = dateutil.tz.gettz("Asia/Tokyo")
@@ -757,7 +758,7 @@ def assertScheduleTimezone(self, callback, expected_schedule):
757758
self.assertEqual(expected_offset, d.utcoffset().total_seconds())
758759

759760
def testTimezoneWinterTime(self):
760-
tz = pytz.timezone("Europe/Athens")
761+
tz = zoneinfo.ZoneInfo("Europe/Athens")
761762

762763
expected_schedule = [
763764
(datetime(2013, 10, 27, 2, 30, 0), 10800),
@@ -769,16 +770,16 @@ def testTimezoneWinterTime(self):
769770
(datetime(2013, 10, 27, 4, 30, 0), 7200),
770771
]
771772

772-
start = datetime(2013, 10, 27, 2, 0, 0)
773-
ct = croniter("*/30 * * * *", tz.localize(start))
773+
start = datetime(2013, 10, 27, 2, 0, 0, tzinfo=tz)
774+
ct = croniter("*/30 * * * *", start)
774775
self.assertScheduleTimezone(lambda: ct.get_next(datetime), expected_schedule)
775776

776-
start = datetime(2013, 10, 27, 5, 0, 0)
777-
ct = croniter("*/30 * * * *", tz.localize(start))
777+
start = datetime(2013, 10, 27, 5, 0, 0, tzinfo=tz)
778+
ct = croniter("*/30 * * * *", start)
778779
self.assertScheduleTimezone(lambda: ct.get_prev(datetime), reversed(expected_schedule))
779780

780781
def testTimezoneSummerTime(self):
781-
tz = pytz.timezone("Europe/Athens")
782+
tz = zoneinfo.ZoneInfo("Europe/Athens")
782783

783784
expected_schedule = [
784785
(datetime(2013, 3, 31, 1, 30, 0), 7200),
@@ -788,12 +789,12 @@ def testTimezoneSummerTime(self):
788789
(datetime(2013, 3, 31, 4, 30, 0), 10800),
789790
]
790791

791-
start = datetime(2013, 3, 31, 1, 0, 0)
792-
ct = croniter("*/30 * * * *", tz.localize(start))
792+
start = datetime(2013, 3, 31, 1, 0, 0, tzinfo=tz)
793+
ct = croniter("*/30 * * * *", start)
793794
self.assertScheduleTimezone(lambda: ct.get_next(datetime), expected_schedule)
794795

795-
start = datetime(2013, 3, 31, 5, 0, 0)
796-
ct = croniter("*/30 * * * *", tz.localize(start))
796+
start = datetime(2013, 3, 31, 5, 0, 0, tzinfo=tz)
797+
ct = croniter("*/30 * * * *", start)
797798
self.assertScheduleTimezone(lambda: ct.get_prev(datetime), reversed(expected_schedule))
798799

799800
def test_std_dst(self):
@@ -803,41 +804,41 @@ def test_std_dst(self):
803804
This fixes https://github.com/taichino/croniter/issues/82
804805
805806
"""
806-
tz = pytz.timezone("Europe/Warsaw")
807+
tz = zoneinfo.ZoneInfo("Europe/Warsaw")
807808
# -> 2017-03-26 01:59+1:00 -> 03:00+2:00
808-
local_date = tz.localize(datetime(2017, 3, 26))
809+
local_date = datetime(2017, 3, 26, tzinfo=tz)
809810
val = croniter("0 0 * * *", local_date).get_next(datetime)
810-
self.assertEqual(val, tz.localize(datetime(2017, 3, 27)))
811+
self.assertEqual(val, datetime(2017, 3, 27, tzinfo=tz))
811812
#
812-
local_date = tz.localize(datetime(2017, 3, 26, 1))
813+
local_date = datetime(2017, 3, 26, 1, tzinfo=tz)
813814
cr = croniter("0 * * * *", local_date)
814815
val = cr.get_next(datetime)
815-
self.assertEqual(val, tz.localize(datetime(2017, 3, 26, 3)))
816+
self.assertEqual(val, datetime(2017, 3, 26, 3, tzinfo=tz))
816817
val = cr.get_current(datetime)
817-
self.assertEqual(val, tz.localize(datetime(2017, 3, 26, 3)))
818+
self.assertEqual(val, datetime(2017, 3, 26, 3, tzinfo=tz))
818819

819820
# -> 2017-10-29 02:59+2:00 -> 02:00+1:00
820-
local_date = tz.localize(datetime(2017, 10, 29))
821+
local_date = datetime(2017, 10, 29, tzinfo=tz)
821822
val = croniter("0 0 * * *", local_date).get_next(datetime)
822-
self.assertEqual(val, tz.localize(datetime(2017, 10, 30)))
823-
local_date = tz.localize(datetime(2017, 10, 29, 1, 59))
823+
self.assertEqual(val, datetime(2017, 10, 30, tzinfo=tz))
824+
local_date = datetime(2017, 10, 29, 1, 59, tzinfo=tz)
824825
val = croniter("0 * * * *", local_date).get_next(datetime)
825826
self.assertEqual(
826827
val.replace(tzinfo=None),
827-
tz.localize(datetime(2017, 10, 29, 2)).replace(tzinfo=None),
828+
datetime(2017, 10, 29, 2, tzinfo=tz).replace(tzinfo=None),
828829
)
829-
local_date = tz.localize(datetime(2017, 10, 29, 2))
830+
local_date = datetime(2017, 10, 29, 2, tzinfo=tz)
830831
val = croniter("0 * * * *", local_date).get_next(datetime)
831-
self.assertEqual(val, tz.localize(datetime(2017, 10, 29, 3)))
832-
local_date = tz.localize(datetime(2017, 10, 29, 3))
832+
self.assertEqual(val, datetime(2017, 10, 29, 3, tzinfo=tz))
833+
local_date = datetime(2017, 10, 29, 3, tzinfo=tz)
833834
val = croniter("0 * * * *", local_date).get_next(datetime)
834-
self.assertEqual(val, tz.localize(datetime(2017, 10, 29, 4)))
835-
local_date = tz.localize(datetime(2017, 10, 29, 4))
835+
self.assertEqual(val, datetime(2017, 10, 29, 4, tzinfo=tz))
836+
local_date = datetime(2017, 10, 29, 4, tzinfo=tz)
836837
val = croniter("0 * * * *", local_date).get_next(datetime)
837-
self.assertEqual(val, tz.localize(datetime(2017, 10, 29, 5)))
838-
local_date = tz.localize(datetime(2017, 10, 29, 5))
838+
self.assertEqual(val, datetime(2017, 10, 29, 5, tzinfo=tz))
839+
local_date = datetime(2017, 10, 29, 5, tzinfo=tz)
839840
val = croniter("0 * * * *", local_date).get_next(datetime)
840-
self.assertEqual(val, tz.localize(datetime(2017, 10, 29, 6)))
841+
self.assertEqual(val, datetime(2017, 10, 29, 6, tzinfo=tz))
841842

842843
def test_std_dst2(self):
843844
"""
@@ -848,24 +849,24 @@ def test_std_dst2(self):
848849
São Paulo, Brazil: 18/02/2018 00:00 -> 17/02/2018 23:00
849850
850851
"""
851-
tz = pytz.timezone("America/Sao_Paulo")
852+
tz = zoneinfo.ZoneInfo("America/Sao_Paulo")
852853
local_dates = [
853854
# 17-22: 00 -> 18-00:00
854-
(tz.localize(datetime(2018, 2, 17, 21, 0, 0)), "2018-02-18 00:00:00-03:00"),
855+
(datetime(2018, 2, 17, 21, 0, 0, tzinfo=tz), "2018-02-18 00:00:00-03:00"),
855856
# 17-23: 00 -> 18-00:00
856-
(tz.localize(datetime(2018, 2, 17, 22, 0, 0)), "2018-02-18 00:00:00-03:00"),
857+
(datetime(2018, 2, 17, 22, 0, 0, tzinfo=tz), "2018-02-18 00:00:00-03:00"),
857858
# 17-23: 00 -> 18-00:00
858-
(tz.localize(datetime(2018, 2, 17, 23, 0, 0)), "2018-02-18 00:00:00-03:00"),
859+
(datetime(2018, 2, 17, 23, 0, 0, tzinfo=tz), "2018-02-18 00:00:00-03:00"),
859860
# 18-00: 00 -> 19-00:00
860-
(tz.localize(datetime(2018, 2, 18, 0, 0, 0)), "2018-02-19 00:00:00-03:00"),
861+
(datetime(2018, 2, 18, 0, 0, 0, tzinfo=tz), "2018-02-19 00:00:00-03:00"),
861862
# 17-22: 00 -> 18-00:00
862-
(tz.localize(datetime(2018, 2, 17, 21, 5, 0)), "2018-02-18 00:00:00-03:00"),
863+
(datetime(2018, 2, 17, 21, 5, 0, tzinfo=tz), "2018-02-18 00:00:00-03:00"),
863864
# 17-23: 00 -> 18-00:00
864-
(tz.localize(datetime(2018, 2, 17, 22, 5, 0)), "2018-02-18 00:00:00-03:00"),
865+
(datetime(2018, 2, 17, 22, 5, 0, tzinfo=tz), "2018-02-18 00:00:00-03:00"),
865866
# 17-23: 00 -> 18-00:00
866-
(tz.localize(datetime(2018, 2, 17, 23, 5, 0)), "2018-02-18 00:00:00-03:00"),
867+
(datetime(2018, 2, 17, 23, 5, 0, tzinfo=tz), "2018-02-18 00:00:00-03:00"),
867868
# 18-00: 00 -> 19-00:00
868-
(tz.localize(datetime(2018, 2, 18, 0, 5, 0)), "2018-02-19 00:00:00-03:00"),
869+
(datetime(2018, 2, 18, 0, 5, 0, tzinfo=tz), "2018-02-19 00:00:00-03:00"),
869870
]
870871
ret1 = [croniter("0 0 * * *", d[0]).get_next(datetime) for d in local_dates]
871872
sret1 = ["{0}".format(d) for d in ret1]
@@ -882,15 +883,15 @@ def test_std_dst3(self):
882883
883884
"""
884885

885-
tz = pytz.timezone("Australia/Adelaide")
886+
tz = zoneinfo.ZoneInfo("Australia/Adelaide")
886887

887-
schedule = croniter("0 0 24 * *", tz.localize(datetime(2020, 4, 15)))
888+
schedule = croniter("0 0 24 * *", datetime(2020, 4, 15, tzinfo=tz))
888889
val1 = schedule.get_prev(datetime)
889-
dt1 = tz.localize(datetime(2020, 3, 24))
890+
dt1 = datetime(2020, 3, 24, tzinfo=tz)
890891
self.assertEqual(val1, dt1)
891892

892893
val2 = schedule.get_next(datetime)
893-
dt2 = tz.localize(datetime(2020, 4, 24))
894+
dt2 = datetime(2020, 4, 24, tzinfo=tz)
894895
self.assertEqual(val2, dt2)
895896

896897
def test_error_alpha_cron(self):
@@ -1178,7 +1179,7 @@ def test_match_range(self):
11781179
)
11791180

11801181
def test_dst_issue90_st31ny(self):
1181-
tz = pytz.timezone("Europe/Paris")
1182+
tz = zoneinfo.ZoneInfo("Europe/Paris")
11821183
now = datetime(2020, 3, 29, 1, 59, 55, tzinfo=tz)
11831184
it = croniter("1 2 * * *", now)
11841185
#
@@ -1243,8 +1244,8 @@ def test_dst_issue90_st31ny(self):
12431244
)
12441245

12451246
def test_dst_iter(self):
1246-
tz = pytz.timezone("Asia/Hebron")
1247-
now = datetime(2022, 3, 26, 0, 0, 0, tzinfo=tz)
1247+
tz = zoneinfo.ZoneInfo("Asia/Hebron")
1248+
now = datetime(2022, 3, 25, 12, 0, 0, tzinfo=tz)
12481249
it = croniter("0 0 * * *", now)
12491250
ret = [
12501251
it.get_next(datetime).isoformat(),
@@ -1255,7 +1256,7 @@ def test_dst_iter(self):
12551256
ret,
12561257
[
12571258
"2022-03-26T00:00:00+02:00",
1258-
"2022-03-27T01:00:00+03:00",
1259+
"2022-03-27T00:00:00+02:00",
12591260
"2022-03-28T00:00:00+03:00",
12601261
],
12611262
)
@@ -1770,15 +1771,15 @@ def test_issue_k6(self):
17701771
self.assertRaises(CroniterBadCronError, croniter, "0 0 0 1 0")
17711772

17721773
def test_issue_k11(self):
1773-
now = pytz.timezone("America/New_York").localize(datetime(2019, 1, 14, 11, 0, 59))
1774+
now = datetime(2019, 1, 14, 11, 0, 59, tzinfo=zoneinfo.ZoneInfo("America/New_York"))
17741775
nextnow = croniter("* * * * * ").next(datetime, start_time=now)
17751776
nextnow2 = croniter("* * * * * ", now).next(datetime)
17761777
for nt in nextnow, nextnow2:
1777-
self.assertEqual(nt.tzinfo.zone, "America/New_York")
1778+
self.assertEqual(nt.tzinfo.str, "America/New_York")
17781779
self.assertEqual(int(croniter._datetime_to_timestamp(nt)), 1547481660)
17791780

17801781
def test_issue_k12(self):
1781-
tz = pytz.timezone("Europe/Athens")
1782+
tz = zoneinfo.ZoneInfo("Europe/Athens")
17821783
base = datetime(2010, 1, 23, 12, 18, tzinfo=tz)
17831784
itr = croniter("* * * * *")
17841785
itr.set_current(start_time=base)

src/croniter/tests/test_croniter_range.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
# -*- coding: utf-8 -*-
33

44
import unittest
5+
import zoneinfo
56
from datetime import datetime, timedelta
67

7-
import pytz
8-
98
from croniter import CroniterBadCronError, CroniterBadDateError, CroniterBadTypeRangeError, croniter, croniter_range
109
from croniter.tests import base
1110

@@ -81,30 +80,26 @@ def test_input_type_exceptions(self):
8180
list(croniter_range(f_start1, dt_stop1, "0 * * * *"))
8281

8382
def test_timezone_dst(self):
84-
"""Test across DST transition, which technically is a timzone change."""
85-
tz = pytz.timezone("America/New_York")
86-
start = tz.localize(datetime(2020, 10, 30))
87-
stop = tz.localize(datetime(2020, 11, 10))
83+
"""Test across DST transition."""
84+
tz = zoneinfo.ZoneInfo("America/New_York")
85+
start = datetime(2020, 10, 30, tzinfo=tz)
86+
stop = datetime(2020, 11, 10, tzinfo=tz)
8887
res = list(croniter_range(start, stop, "0 0 * * *"))
89-
self.assertNotEqual(res[0].tzinfo, res[-1].tzinfo)
88+
self.assertNotEqual(res[0].dst(), res[-1].dst())
9089
self.assertEqual(len(res), 12)
9190

9291
def test_extra_hour_day_prio(self):
93-
def datetime_tz(*args, **kw):
94-
"""Defined this in another branch. single-use-version"""
95-
tzinfo = kw.pop("tzinfo")
96-
return tzinfo.localize(datetime(*args))
97-
98-
tz = pytz.timezone("America/New_York")
92+
"""America/New_York sprang forward from 2 am to 3 am on 2020-03-08."""
93+
tz = zoneinfo.ZoneInfo("America/New_York")
9994
cron = "0 3 * * *"
100-
start = datetime_tz(2020, 3, 7, tzinfo=tz)
101-
end = datetime_tz(2020, 3, 11, tzinfo=tz)
95+
start = datetime(2020, 3, 7, tzinfo=tz)
96+
end = datetime(2020, 3, 11, tzinfo=tz)
10297
ret = [i.isoformat() for i in croniter_range(start, end, cron)]
10398
self.assertEqual(
10499
ret,
105100
[
106101
"2020-03-07T03:00:00-05:00",
107-
"2020-03-08T03:00:00-04:00",
102+
"2020-03-08T02:00:00-05:00", # == 2020-03-08T03:00:00-04:00
108103
"2020-03-09T03:00:00-04:00",
109104
"2020-03-10T03:00:00-04:00",
110105
],

src/croniter/tests/test_croniter_speed.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99

1010
import os
1111
import sys
12+
import zoneinfo
1213
from datetime import datetime
1314
from timeit import Timer
1415

15-
import pytz
16-
1716
from croniter import cron_m, croniter
1817
from croniter.tests import base
1918

@@ -91,7 +90,7 @@ def run_long_test(self, iterations=1):
9190
itr.get_prev(datetime)
9291

9392
# dst regression test
94-
tz = pytz.timezone("Europe/Bucharest")
93+
tz = zoneinfo.ZoneInfo("Europe/Bucharest")
9594
offsets = set()
9695
dst_cron = "15 0,3 * 3 *"
9796
dst_iters = int(2 * 31 * (iterations / 40))

0 commit comments

Comments
 (0)