11#!/usr/bin/env python
22
33import unittest
4+ import zoneinfo
45from datetime import datetime , timedelta
56from functools import partial
67from 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 )
0 commit comments