Skip to content

Commit 39b32a3

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 abeaa39 commit 39b32a3

4 files changed

Lines changed: 261 additions & 9 deletions

File tree

README.rst

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

112+
Example using zoneinfo::
113+
114+
>>> import zoneinfo
115+
>>> tz = zoneinfo.ZoneInfo("Europe/Berlin")
116+
>>> local_date = datetime(2017, 3, 26, tzinfo=tz)
117+
>>> val = croniter('0 0 * * *', local_date).get_next(datetime)
118+
112119
Example using pytz::
113120

114121
>>> import pytz
@@ -394,4 +401,3 @@ If you have contributed and your name is not listed below please let us know.
394401
- shazow
395402
- yuzawa-san
396403
- zed2015
397-

src/croniter/tests/test_croniter.py

Lines changed: 232 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python
22

33
import unittest
4+
import zoneinfo
45
from datetime import datetime, timedelta
56
from functools import partial
67
from time import sleep
@@ -727,7 +728,15 @@ def test_timezone(self):
727728
n1 = itr.get_next(datetime)
728729
self.assertEqual(n1.tzinfo, None)
729730

731+
tokyo = zoneinfo.ZoneInfo("Asia/Tokyo")
732+
start = datetime(2013, 3, 4, 12, 15, tzinfo=tokyo)
733+
itr2 = croniter("* * * * *", start)
734+
n2 = itr2.get_next(datetime)
735+
self.assertEqual(n2.tzinfo.key, "Asia/Tokyo")
736+
737+
def test_timezone_pytz(self):
730738
tokyo = pytz.timezone("Asia/Tokyo")
739+
base = datetime(2013, 3, 4, 12, 15)
731740
itr2 = croniter("* * * * *", tokyo.localize(base))
732741
n2 = itr2.get_next(datetime)
733742
self.assertEqual(n2.tzinfo.zone, "Asia/Tokyo")
@@ -747,6 +756,30 @@ def test_init_no_start_time(self):
747756
self.assertTrue(itr2.cur > itr.cur)
748757

749758
def test_timezone_winter_time(self):
759+
"""Test Athens jumps backwards: 2013-10-27 04:00 -> 03:00 (UTC+3 -> UTC+2)."""
760+
tz = zoneinfo.ZoneInfo("Europe/Athens")
761+
762+
expected_schedule = [
763+
"2013-10-27T02:30:00+03:00",
764+
"2013-10-27T03:00:00+03:00",
765+
"2013-10-27T03:30:00+03:00",
766+
"2013-10-27T03:00:00+02:00",
767+
"2013-10-27T03:30:00+02:00",
768+
"2013-10-27T04:00:00+02:00",
769+
"2013-10-27T04:30:00+02:00",
770+
]
771+
772+
start = datetime(2013, 10, 27, 2, 0, 0, tzinfo=tz)
773+
ct = croniter("*/30 * * * *", start)
774+
schedule = [ct.get_next(datetime).isoformat() for _ in range(7)]
775+
self.assertEqual(schedule, expected_schedule)
776+
777+
start = datetime(2013, 10, 27, 5, 0, 0, tzinfo=tz)
778+
ct = croniter("*/30 * * * *", start)
779+
schedule = [ct.get_prev(datetime).isoformat() for _ in range(7)]
780+
self.assertEqual(schedule, list(reversed(expected_schedule)))
781+
782+
def test_timezone_winter_time_pytz(self):
750783
"""Test Athens jumps backwards: 2013-10-27 04:00 -> 03:00 (UTC+3 -> UTC+2)."""
751784
tz = pytz.timezone("Europe/Athens")
752785

@@ -771,6 +804,28 @@ def test_timezone_winter_time(self):
771804
self.assertEqual(schedule, list(reversed(expected_schedule)))
772805

773806
def test_timezone_summer_time(self):
807+
"""Test Athens jumps forward: 2013-03-31 03:00 -> 04:00 (UTC+2 -> UTC+3)."""
808+
tz = zoneinfo.ZoneInfo("Europe/Athens")
809+
810+
expected_schedule = [
811+
"2013-03-31T01:30:00+02:00",
812+
"2013-03-31T02:00:00+02:00",
813+
"2013-03-31T02:30:00+02:00",
814+
"2013-03-31T04:00:00+03:00",
815+
"2013-03-31T04:30:00+03:00",
816+
]
817+
818+
start = datetime(2013, 3, 31, 1, 0, 0, tzinfo=tz)
819+
ct = croniter("*/30 * * * *", start)
820+
schedule = [ct.get_next(datetime).isoformat() for _ in range(5)]
821+
self.assertEqual(schedule, expected_schedule)
822+
823+
start = datetime(2013, 3, 31, 5, 0, 0, tzinfo=tz)
824+
ct = croniter("*/30 * * * *", start)
825+
schedule = [ct.get_prev(datetime).isoformat() for _ in range(5)]
826+
self.assertEqual(schedule, list(reversed(expected_schedule)))
827+
828+
def test_timezone_summer_time_pytz(self):
774829
"""Test Athens jumps forward: 2013-03-31 03:00 -> 04:00 (UTC+2 -> UTC+3)."""
775830
tz = pytz.timezone("Europe/Athens")
776831

@@ -798,6 +853,41 @@ def test_std_dst(self):
798853
799854
This fixes https://github.com/taichino/croniter/issues/82
800855
856+
"""
857+
tz = zoneinfo.ZoneInfo("Europe/Warsaw")
858+
# -> 2017-03-26 01:59+1:00 -> 03:00+2:00
859+
local_date = datetime(2017, 3, 26, tzinfo=tz)
860+
val = croniter("0 0 * * *", local_date).get_next(datetime)
861+
self.assertEqual(val.isoformat(), "2017-03-27T00:00:00+02:00")
862+
#
863+
local_date = datetime(2017, 3, 26, 1, tzinfo=tz)
864+
cr = croniter("0 * * * *", local_date)
865+
val = cr.get_next(datetime)
866+
self.assertEqual(val.isoformat(), "2017-03-26T03:00:00+02:00")
867+
val = cr.get_current(datetime)
868+
self.assertEqual(val.isoformat(), "2017-03-26T03:00:00+02:00")
869+
870+
# -> 2017-10-29 02:59+2:00 -> 02:00+1:00
871+
local_date = datetime(2017, 10, 29, tzinfo=tz)
872+
val = croniter("0 0 * * *", local_date).get_next(datetime)
873+
self.assertEqual(val.isoformat(), "2017-10-30T00:00:00+01:00")
874+
local_date = datetime(2017, 10, 29, 1, 59, tzinfo=tz)
875+
cr = croniter("0 * * * *", local_date)
876+
schedule = [cr.get_next(datetime).isoformat() for _ in range(4)]
877+
expected_schedule = [
878+
"2017-10-29T02:00:00+02:00",
879+
"2017-10-29T02:00:00+01:00",
880+
"2017-10-29T03:00:00+01:00",
881+
"2017-10-29T04:00:00+01:00",
882+
]
883+
self.assertEqual(schedule, expected_schedule)
884+
885+
def test_std_dst_pytz(self):
886+
"""
887+
DST tests
888+
889+
This fixes https://github.com/taichino/croniter/issues/82
890+
801891
"""
802892
tz = pytz.timezone("Europe/Warsaw")
803893
# -> 2017-03-26 01:59+1:00 -> 03:00+2:00
@@ -835,6 +925,39 @@ def test_std_dst2(self):
835925
836926
São Paulo, Brazil: 18/02/2018 00:00 -> 17/02/2018 23:00
837927
928+
"""
929+
tz = zoneinfo.ZoneInfo("America/Sao_Paulo")
930+
local_dates = [
931+
# 17-22: 00 -> 18-00:00
932+
(datetime(2018, 2, 17, 21, 0, 0, tzinfo=tz), "2018-02-18 00:00:00-03:00"),
933+
# 17-23: 00 -> 18-00:00
934+
(datetime(2018, 2, 17, 22, 0, 0, tzinfo=tz), "2018-02-18 00:00:00-03:00"),
935+
# 17-23: 00 -> 18-00:00
936+
(datetime(2018, 2, 17, 23, 0, 0, tzinfo=tz), "2018-02-18 00:00:00-03:00"),
937+
# 18-00: 00 -> 19-00:00
938+
(datetime(2018, 2, 18, 0, 0, 0, tzinfo=tz), "2018-02-19 00:00:00-03:00"),
939+
# 17-22: 00 -> 18-00:00
940+
(datetime(2018, 2, 17, 21, 5, 0, tzinfo=tz), "2018-02-18 00:00:00-03:00"),
941+
# 17-23: 00 -> 18-00:00
942+
(datetime(2018, 2, 17, 22, 5, 0, tzinfo=tz), "2018-02-18 00:00:00-03:00"),
943+
# 17-23: 00 -> 18-00:00
944+
(datetime(2018, 2, 17, 23, 5, 0, tzinfo=tz), "2018-02-18 00:00:00-03:00"),
945+
# 18-00: 00 -> 19-00:00
946+
(datetime(2018, 2, 18, 0, 5, 0, tzinfo=tz), "2018-02-19 00:00:00-03:00"),
947+
]
948+
ret1 = [croniter("0 0 * * *", d[0]).get_next(datetime) for d in local_dates]
949+
sret1 = [str(d) for d in ret1]
950+
lret1 = [str(d[1]) for d in local_dates]
951+
self.assertEqual(sret1, lret1)
952+
953+
def test_std_dst2_pytz(self):
954+
"""
955+
DST tests
956+
957+
This fixes https://github.com/taichino/croniter/issues/87
958+
959+
São Paulo, Brazil: 18/02/2018 00:00 -> 17/02/2018 23:00
960+
838961
"""
839962
tz = pytz.timezone("America/Sao_Paulo")
840963
local_dates = [
@@ -870,6 +993,24 @@ def test_std_dst3(self):
870993
871994
"""
872995

996+
tz = zoneinfo.ZoneInfo("Australia/Adelaide")
997+
998+
schedule = croniter("0 0 24 * *", datetime(2020, 4, 15, tzinfo=tz))
999+
val1 = schedule.get_prev(datetime)
1000+
self.assertEqual(val1.isoformat(), "2020-03-24T00:00:00+10:30")
1001+
1002+
val2 = schedule.get_next(datetime)
1003+
self.assertEqual(val2.isoformat(), "2020-04-24T00:00:00+09:30")
1004+
1005+
def test_std_dst3_pytz(self):
1006+
"""
1007+
DST tests
1008+
1009+
This fixes https://github.com/taichino/croniter/issues/90
1010+
1011+
Adelaide, Australia: 15/04/2020 00:00 -> 15/03/2020
1012+
1013+
"""
8731014
tz = pytz.timezone("Australia/Adelaide")
8741015

8751016
schedule = croniter("0 0 24 * *", tz.localize(datetime(2020, 4, 15)))
@@ -1169,6 +1310,75 @@ def test_dst_issue90_st31ny(self):
11691310
Paris jumps forward: 2020-03-29 02:00 -> 03:00 (UTC+1 -> UTC+2).
11701311
So 2020-03-29 02:01 does not exist in local time.
11711312
1313+
This fixes https://github.com/taichino/croniter/issues/90#issuecomment-605615205
1314+
"""
1315+
tz = zoneinfo.ZoneInfo("Europe/Paris")
1316+
now = datetime(2020, 3, 29, 1, 59, 55, tzinfo=tz)
1317+
it = croniter("1 2 * * *", now)
1318+
ret = [
1319+
it.get_next(datetime).isoformat(),
1320+
it.get_prev(datetime).isoformat(),
1321+
it.get_prev(datetime).isoformat(),
1322+
it.get_next(datetime).isoformat(),
1323+
it.get_next(datetime).isoformat(),
1324+
]
1325+
self.assertEqual(
1326+
ret,
1327+
[
1328+
"2020-03-30T02:01:00+02:00",
1329+
"2020-03-29T01:01:00+01:00",
1330+
"2020-03-28T03:01:00+01:00",
1331+
"2020-03-29T03:01:00+02:00",
1332+
"2020-03-30T02:01:00+02:00",
1333+
],
1334+
)
1335+
#
1336+
nowp = datetime(2020, 3, 28, 1, 58, 55, tzinfo=tz)
1337+
itp = croniter("1 2 * * *", nowp)
1338+
retp = [
1339+
itp.get_next(datetime).isoformat(),
1340+
itp.get_prev(datetime).isoformat(),
1341+
itp.get_prev(datetime).isoformat(),
1342+
itp.get_next(datetime).isoformat(),
1343+
itp.get_next(datetime).isoformat(),
1344+
]
1345+
self.assertEqual(
1346+
retp,
1347+
[
1348+
"2020-03-29T03:01:00+02:00",
1349+
"2020-03-29T01:01:00+01:00",
1350+
"2020-03-28T03:01:00+01:00",
1351+
"2020-03-29T03:01:00+02:00",
1352+
"2020-03-30T02:01:00+02:00",
1353+
],
1354+
)
1355+
#
1356+
nowt = datetime(2020, 3, 29, 2, 0, 0, tzinfo=tz)
1357+
itt = croniter("1 2 * * *", nowt)
1358+
rett = [
1359+
itt.get_next(datetime).isoformat(),
1360+
itt.get_prev(datetime).isoformat(),
1361+
itt.get_prev(datetime).isoformat(),
1362+
itt.get_next(datetime).isoformat(),
1363+
itt.get_next(datetime).isoformat(),
1364+
]
1365+
self.assertEqual(
1366+
rett,
1367+
[
1368+
"2020-03-30T02:01:00+02:00",
1369+
"2020-03-29T01:01:00+01:00",
1370+
"2020-03-28T03:01:00+01:00",
1371+
"2020-03-29T03:01:00+02:00",
1372+
"2020-03-30T02:01:00+02:00",
1373+
],
1374+
)
1375+
1376+
def test_dst_issue90_st31ny_pytz(self):
1377+
"""Test DST gap with cron job every day at 02:01.
1378+
1379+
Paris jumps forward: 2020-03-29 02:00 -> 03:00 (UTC+1 -> UTC+2).
1380+
So 2020-03-29 02:01 does not exist in local time.
1381+
11721382
This fixes https://github.com/taichino/croniter/issues/90#issuecomment-605615205
11731383
"""
11741384
tz = pytz.timezone("Europe/Paris")
@@ -1233,6 +1443,25 @@ def test_dst_issue90_st31ny(self):
12331443
)
12341444

12351445
def test_dst_iter(self):
1446+
"""Test Hebron jumps one hour forward on 2022-03-27 00:00 (UTC+2 -> UTC+3)."""
1447+
tz = zoneinfo.ZoneInfo("Asia/Hebron")
1448+
now = datetime(2022, 3, 26, 0, 0, 0, tzinfo=tz)
1449+
it = croniter("0 0 * * *", now)
1450+
ret = [
1451+
it.get_next(datetime).isoformat(),
1452+
it.get_next(datetime).isoformat(),
1453+
it.get_next(datetime).isoformat(),
1454+
]
1455+
self.assertEqual(
1456+
ret,
1457+
[
1458+
"2022-03-26T00:00:00+02:00",
1459+
"2022-03-27T01:00:00+03:00",
1460+
"2022-03-28T00:00:00+03:00",
1461+
],
1462+
)
1463+
1464+
def test_dst_iter_pytz(self):
12361465
"""Test Hebron jumps one hour forward on 2022-03-27 00:00 (UTC+2 -> UTC+3)."""
12371466
tz = pytz.timezone("Asia/Hebron")
12381467
now = datetime(2022, 3, 26, 0, 0, 0, tzinfo=tz)
@@ -1761,15 +1990,15 @@ def test_issue_k6(self):
17611990
self.assertRaises(CroniterBadCronError, croniter, "0 0 0 1 0")
17621991

17631992
def test_issue_k11(self):
1764-
now = pytz.timezone("America/New_York").localize(datetime(2019, 1, 14, 11, 0, 59))
1993+
now = datetime(2019, 1, 14, 11, 0, 59, tzinfo=zoneinfo.ZoneInfo("America/New_York"))
17651994
nextnow = croniter("* * * * * ").next(datetime, start_time=now)
17661995
nextnow2 = croniter("* * * * * ", now).next(datetime)
17671996
for nt in nextnow, nextnow2:
1768-
self.assertEqual(nt.tzinfo.zone, "America/New_York")
1997+
self.assertEqual(nt.tzinfo.key, "America/New_York")
17691998
self.assertEqual(int(croniter._datetime_to_timestamp(nt)), 1547481660)
17701999

17712000
def test_issue_k12(self):
1772-
tz = pytz.timezone("Europe/Athens")
2001+
tz = zoneinfo.ZoneInfo("Europe/Athens")
17732002
base = datetime(2010, 1, 23, 12, 18, tzinfo=tz)
17742003
itr = croniter("* * * * *")
17752004
itr.set_current(start_time=base)

src/croniter/tests/test_croniter_range.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python
22

33
import unittest
4+
import zoneinfo
45
from datetime import datetime
56

67
import pytz
@@ -79,8 +80,8 @@ def test_input_type_exceptions(self):
7980
with self.assertRaises(TypeError):
8081
list(croniter_range(f_start1, dt_stop1, "0 * * * *"))
8182

82-
def test_timezone_dst(self):
83-
"""Test across DST transition, which technically is a timzone change."""
83+
def test_timezone_dst_pytz(self):
84+
"""Test across DST transition, which technically is a timzone change in pytz."""
8485
tz = pytz.timezone("America/New_York")
8586
start = tz.localize(datetime(2020, 10, 30))
8687
stop = tz.localize(datetime(2020, 11, 10))
@@ -90,6 +91,23 @@ def test_timezone_dst(self):
9091

9192
def test_extra_hour_day_prio(self):
9293
"""Test New York jumps forward: 2020-03-08 02:00 -> 03:00 (UTC-5 -> UTC-4)."""
94+
tz = zoneinfo.ZoneInfo("America/New_York")
95+
cron = "0 3 * * *"
96+
start = datetime(2020, 3, 7, tzinfo=tz)
97+
end = datetime(2020, 3, 11, tzinfo=tz)
98+
ret = [i.isoformat() for i in croniter_range(start, end, cron)]
99+
self.assertEqual(
100+
ret,
101+
[
102+
"2020-03-07T03:00:00-05:00",
103+
"2020-03-08T03:00:00-04:00",
104+
"2020-03-09T03:00:00-04:00",
105+
"2020-03-10T03:00:00-04:00",
106+
],
107+
)
108+
109+
def test_extra_hour_day_prio_pytz(self):
110+
"""Test New York jumps forward: 2020-03-08 02:00 -> 03:00 (UTC-5 -> UTC-4)."""
93111

94112
def datetime_tz(*args, **kw):
95113
"""Defined this in another branch. single-use-version"""

src/croniter/tests/test_croniter_speed.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
import os
44
import sys
55
import unittest
6+
import zoneinfo
67
from datetime import datetime
78
from timeit import Timer
89

9-
import pytz
10-
1110
from croniter import croniter
1211
from croniter.tests import base
1312

@@ -85,7 +84,7 @@ def run_long_test(self, iterations=1):
8584
itr.get_prev(datetime)
8685

8786
# dst regression test
88-
tz = pytz.timezone("Europe/Bucharest")
87+
tz = zoneinfo.ZoneInfo("Europe/Bucharest")
8988
offsets = set()
9089
dst_cron = "15 0,3 * 3 *"
9190
dst_iters = int(2 * 31 * (iterations / 40))

0 commit comments

Comments
 (0)