@@ -58,15 +58,18 @@ def today(cls):
58
58
return cls (self .year , self .month , self .day )
59
59
60
60
@classmethod
61
- def now (cls ):
62
- return cls (
61
+ def now (cls , tz = None ):
62
+ mock_date = cls (
63
63
self .year ,
64
64
self .month ,
65
65
self .day ,
66
66
self .hour ,
67
67
self .minute ,
68
68
self .second ,
69
69
)
70
+ if tz :
71
+ return mock_date .astimezone (tz )
72
+ return mock_date
70
73
71
74
self .original_datetime = datetime .datetime
72
75
datetime .datetime = MockDate
@@ -500,26 +503,75 @@ def test_next_run_time_day_end(self):
500
503
assert job .next_run .hour == 23
501
504
502
505
def test_next_run_time_hour_end (self ):
506
+ try :
507
+ import pytz
508
+ except ModuleNotFoundError :
509
+ self .skipTest ("pytz unavailable" )
510
+
511
+ self .tst_next_run_time_hour_end (None , 0 )
512
+
513
+ def test_next_run_time_hour_end_london (self ):
514
+ try :
515
+ import pytz
516
+ except ModuleNotFoundError :
517
+ self .skipTest ("pytz unavailable" )
518
+
519
+ self .tst_next_run_time_hour_end ("Europe/London" , 0 )
520
+
521
+ def test_next_run_time_hour_end_katmandu (self ):
522
+ try :
523
+ import pytz
524
+ except ModuleNotFoundError :
525
+ self .skipTest ("pytz unavailable" )
526
+
527
+ # 12:00 in Berlin is 15:45 in Kathmandu
528
+ # this test schedules runs at :10 minutes, so job runs at
529
+ # 16:10 in Kathmandu, which is 13:25 in Berlin
530
+ # in local time we don't run at :10, but at :25, offset of 15 minutes
531
+ self .tst_next_run_time_hour_end ("Asia/Kathmandu" , 15 )
532
+
533
+ def tst_next_run_time_hour_end (self , tz , offsetMinutes ):
503
534
mock_job = make_mock_job ()
535
+
536
+ # So a job scheduled to run at :10 in Kathmandu, runs always 25 minutes
504
537
with mock_datetime (2010 , 10 , 10 , 12 , 0 , 0 ):
505
- job = every ().hour .at (":10" ).do (mock_job )
538
+ job = every ().hour .at (":10" , tz ).do (mock_job )
506
539
assert job .next_run .hour == 12
507
- assert job .next_run .minute == 10
540
+ assert job .next_run .minute == 10 + offsetMinutes
508
541
509
542
with mock_datetime (2010 , 10 , 10 , 13 , 0 , 0 ):
510
543
job .run ()
511
544
assert job .next_run .hour == 13
512
- assert job .next_run .minute == 10
545
+ assert job .next_run .minute == 10 + offsetMinutes
513
546
514
- with mock_datetime (2010 , 10 , 10 , 13 , 15 , 0 ):
547
+ with mock_datetime (2010 , 10 , 10 , 13 , 30 , 0 ):
515
548
job .run ()
516
549
assert job .next_run .hour == 14
517
- assert job .next_run .minute == 10
550
+ assert job .next_run .minute == 10 + offsetMinutes
518
551
519
552
def test_next_run_time_minute_end (self ):
553
+ self .tst_next_run_time_minute_end (None )
554
+
555
+ def test_next_run_time_minute_end_london (self ):
556
+ try :
557
+ import pytz
558
+ except ModuleNotFoundError :
559
+ self .skipTest ("pytz unavailable" )
560
+
561
+ self .tst_next_run_time_minute_end ("Europe/London" )
562
+
563
+ def test_next_run_time_minute_end_katmhandu (self ):
564
+ try :
565
+ import pytz
566
+ except ModuleNotFoundError :
567
+ self .skipTest ("pytz unavailable" )
568
+
569
+ self .tst_next_run_time_minute_end ("Asia/Kathmandu" )
570
+
571
+ def tst_next_run_time_minute_end (self , tz ):
520
572
mock_job = make_mock_job ()
521
573
with mock_datetime (2010 , 10 , 10 , 10 , 10 , 0 ):
522
- job = every ().minute .at (":15" ).do (mock_job )
574
+ job = every ().minute .at (":15" , tz ).do (mock_job )
523
575
assert job .next_run .minute == 10
524
576
assert job .next_run .second == 15
525
577
@@ -585,6 +637,40 @@ def test_at_timezone(self):
585
637
assert next .hour == 15
586
638
assert next .minute == 30
587
639
640
+ # Test the DST-case that is described in the documentation
641
+ with mock_datetime (2023 , 3 , 26 , 1 , 30 ):
642
+ # Current Berlin time: 01:30 (NOT during daylight saving)
643
+ # Expected to run: 02:30 - this time doesn't exist
644
+ # because clock moves from 02:00 to 03:00
645
+ # Next run: 03:30
646
+ job = every ().day .at ("02:30" , "Europe/Berlin" ).do (mock_job )
647
+ assert job .next_run .day == 26
648
+ assert job .next_run .hour == 3
649
+ assert job .next_run .minute == 30
650
+ with mock_datetime (2023 , 3 , 27 , 1 , 30 ):
651
+ # the next day the job shall again run at 02:30
652
+ job .run ()
653
+ assert job .next_run .day == 27
654
+ assert job .next_run .hour == 2
655
+ assert job .next_run .minute == 30
656
+
657
+ # Test the DST-case that is described in the documentation
658
+ with mock_datetime (2023 , 10 , 29 , 1 , 30 ):
659
+ # Current Berlin time: 01:30 (during daylight saving)
660
+ # Expected to run: 02:30 - this time exists twice
661
+ # because clock moves from 03:00 to 02:00
662
+ # Next run should be at the first occurrence of 02:30
663
+ job = every ().day .at ("02:30" , "Europe/Berlin" ).do (mock_job )
664
+ assert job .next_run .day == 29
665
+ assert job .next_run .hour == 2
666
+ assert job .next_run .minute == 30
667
+ with mock_datetime (2023 , 10 , 29 , 2 , 35 ):
668
+ # After the job runs, the next run should be scheduled on the next day at 02:30
669
+ job .run ()
670
+ assert job .next_run .day == 30
671
+ assert job .next_run .hour == 2
672
+ assert job .next_run .minute == 30
673
+
588
674
with mock_datetime (2022 , 3 , 20 , 10 , 0 ):
589
675
# Current Berlin time: 10:00 (local) (NOT during daylight saving)
590
676
# Current Krasnoyarsk time: 16:00
@@ -628,6 +714,109 @@ def test_at_timezone(self):
628
714
assert next .hour == 13
629
715
assert next .minute == 45
630
716
717
+ with mock_datetime (2023 , 10 , 19 , 15 , 0 , 0 , TZ_UTC ):
718
+ # Testing issue #603
719
+ # Current UTC: oktober-19 15:00
720
+ # Current Amsterdam: oktober-19 17:00 (daylight saving active)
721
+ # Expected run Amsterdam: oktober-20 00:00:20 (daylight saving active)
722
+ # Next run UTC time: oktober-19 22:00:20
723
+ schedule .clear ()
724
+ next = every ().day .at ("00:00:20" , "Europe/Amsterdam" ).do (mock_job ).next_run
725
+ assert next .day == 19
726
+ assert next .hour == 22
727
+ assert next .minute == 00
728
+ assert next .second == 20
729
+
730
+ with mock_datetime (2023 , 10 , 22 , 23 , 0 , 0 , TZ_UTC ):
731
+ # Current UTC: sunday 22-okt 23:00
732
+ # Current Amsterdam: monday 23-okt 01:00 (daylight saving active)
733
+ # Expected run Amsterdam: sunday 29 oktober 23:00 (daylight saving NOT active)
734
+ # Next run UTC time: oktober-29 22:00
735
+ schedule .clear ()
736
+ next = every ().sunday .at ("23:00" , "Europe/Amsterdam" ).do (mock_job ).next_run
737
+ assert next .day == 29
738
+ assert next .hour == 22
739
+ assert next .minute == 00
740
+
741
+ with mock_datetime (2023 , 12 , 31 , 23 , 0 , 0 ):
742
+ # Current Berlin time: dec-31 23:00 (local)
743
+ # Current Sydney time: jan-1 09:00 (next day)
744
+ # Expected to run Sydney time: jan-1 12:00
745
+ # Next run Berlin time: jan-1 02:00
746
+ next = every ().day .at ("12:00" , "Australia/Sydney" ).do (mock_job ).next_run
747
+ assert next .day == 1
748
+ assert next .hour == 2
749
+ assert next .minute == 0
750
+
751
+ with mock_datetime (2023 , 3 , 26 , 1 , 30 ):
752
+ # Daylight Saving Time starts in Berlin
753
+ # Current Berlin time: march-26 01:30 (30 mintues before moving to 03:00 due to DST)
754
+ # Current London time: march-26 00:30 (30 mintues before moving to 02:00 due to DST)
755
+ # Expected to run London time: march-26 02:00 (which is equal to 01:00 due to DST)
756
+ # Next run Berlin time: march-26 03:00
757
+ next = every ().day .at ("01:00" , "Europe/London" ).do (mock_job ).next_run
758
+ assert next .day == 26
759
+ assert next .hour == 3
760
+ assert next .minute == 0
761
+
762
+ with mock_datetime (2023 , 10 , 29 , 2 , 30 ):
763
+ # Daylight Saving Time ends in Berlin
764
+ # Current Berlin time: oct-29 02:30 (after moving back to 02:00 due to DST end)
765
+ # Current Istanbul time: oct-29 04:30
766
+ # Expected to run Istanbul time: oct-29 06:00
767
+ # Next run Berlin time: oct-29 04:00
768
+ next = every ().day .at ("06:00" , "Europe/Istanbul" ).do (mock_job ).next_run
769
+ assert next .hour == 4
770
+ assert next .minute == 0
771
+
772
+ with mock_datetime (2023 , 12 , 31 , 23 , 50 ):
773
+ # End of the year in Berlin
774
+ # Current Berlin time: dec-31 23:50
775
+ # Current Tokyo time: jan-1 07:50 (next day)
776
+ # Expected to run Tokyo time: jan-1 09:00
777
+ # Next run Berlin time: jan-1 01:00
778
+ next = every ().day .at ("09:00" , "Asia/Tokyo" ).do (mock_job ).next_run
779
+ assert next .day == 1
780
+ assert next .hour == 1
781
+ assert next .minute == 0
782
+
783
+ with mock_datetime (2023 , 2 , 28 , 23 , 50 ):
784
+ # End of the month (non-leap year) in Berlin
785
+ # Current Berlin time: feb-28 23:50
786
+ # Current Sydney time: mar-1 09:50 (next day)
787
+ # Expected to run Sydney time: mar-1 10:00
788
+ # Next run Berlin time: mar-1 00:00
789
+ next = every ().day .at ("10:00" , "Australia/Sydney" ).do (mock_job ).next_run
790
+ assert next .day == 1
791
+ assert next .hour == 0
792
+ assert next .minute == 0
793
+
794
+ with mock_datetime (2024 , 2 , 28 , 23 , 50 ):
795
+ # End of the month (leap year) in Berlin
796
+ # Current Berlin time: feb-28 23:50
797
+ # Current Dubai time: feb-29 02:50
798
+ # Expected to run Dubai time: feb-29 04:00
799
+ # Next run Berlin time: feb-29 01:00
800
+ next = every ().day .at ("04:00" , "Asia/Dubai" ).do (mock_job ).next_run
801
+ assert next .month == 2
802
+ assert next .day == 29
803
+ assert next .hour == 1
804
+ assert next .minute == 0
805
+
806
+ with mock_datetime (2023 , 9 , 18 , 10 , 00 , 0 , TZ_AUCKLAND ):
807
+ schedule .clear ()
808
+ # Testing issue #605
809
+ # Current time: Monday 18 September 10:00 NZST
810
+ # Current time UTC: Sunday 17 September 22:00
811
+ # We expect the job to run at 23:00 on Sunday 17 September NZST
812
+ # That is an expected idle time of 1 hour
813
+ # Expected next run in NZST: 2023-09-18 11:00:00
814
+ next = schedule .every ().day .at ("23:00" , "UTC" ).do (mock_job ).next_run
815
+ assert round (schedule .idle_seconds () / 3600 ) == 1
816
+ assert next .day == 18
817
+ assert next .hour == 11
818
+ assert next .minute == 0
819
+
631
820
with self .assertRaises (pytz .exceptions .UnknownTimeZoneError ):
632
821
every ().day .at ("10:30" , "FakeZone" ).do (mock_job )
633
822
0 commit comments