Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bi-hourly CronTrigger runs into infinite loop at daylight savings time boundary #980

Open
3 tasks done
timon-k opened this issue Oct 27, 2024 · 3 comments · May be fixed by #981
Open
3 tasks done

Bi-hourly CronTrigger runs into infinite loop at daylight savings time boundary #980

timon-k opened this issue Oct 27, 2024 · 3 comments · May be fixed by #981
Labels

Comments

@timon-k
Copy link

timon-k commented Oct 27, 2024

Things to check first

  • I have checked that my issue does not already have a solution in the FAQ

  • I have searched the existing issues and didn't find my bug already reported there

  • I have checked that my bug is still present in the latest release

Version

4.0.0a5

What happened?

When using a bi-hourly CronTrigger as given below, starting at a point before the daylight savings time transition, the trigger fails to compute the sequence of next trigger times. It always returns the same static time+date.

The output of the example code given below is:

Trigger starts at 2024-10-27 01:00:00+01:00
Next trigger time is 2024-10-27 01:00:00+01:00
Next trigger time is 2024-10-27 01:00:00+00:00
Next trigger time is 2024-10-27 01:00:00+00:00
Next trigger time is 2024-10-27 01:00:00+00:00
Trigger is in future after daylight saving time transition: False

Together with the aync scheduler logic here, this leads to an infinite loop, since the computed next fire time will never become bigger than now:

                        while True:
                            try:
                                fire_time = calculate_next()
                            except Exception:
                                ...
                                break

                            # Stop if the calculated fire time is in the future
                            if fire_time is None or fire_time > now:
                                next_fire_time = fire_time
                                break

The root cause is that the local time 1:00 really exists twice in the given timezone: Once before (01:00:00+01:00) and once after the daylight savings time transition (01:00:00+00:00).

The trigger stays stuck on the occurrence after the transition.

How can we reproduce the bug?

Run this example

import datetime
import zoneinfo

from apscheduler.triggers.cron import CronTrigger

start_time = datetime.datetime(
    2024, 10, 27, 1, 0, 0, 0, tzinfo=zoneinfo.ZoneInfo(key="Europe/Lisbon")
)
print(f"Trigger starts at {start_time}")
trigger = CronTrigger(
    year="*",
    month="*",
    day="*",
    week="*",
    day_of_week="*",
    hour="1,3,5,7,9,11,13,15,17,19,21,23",
    minute="0",
    second="0",
    start_time=start_time,
    timezone="Europe/Lisbon",
)

print(f"Next trigger time is {trigger.next()}")
print(f"Next trigger time is {trigger.next()}")
print(f"Next trigger time is {trigger.next()}")
print(f"Next trigger time is {trigger.next()}")
now_time_after_dst_change = datetime.datetime(
    2024, 10, 27, 14, 0, 0, 0, tzinfo=zoneinfo.ZoneInfo(key="UTC")
)
print(f"Trigger is in future after daylight saving time transition: {trigger.next() > now_time_after_dst_change}")
@timon-k timon-k added the bug label Oct 27, 2024
@agronholm
Copy link
Owner

This is a persistent problem that has defied my attempts to fix it. A PR (that doesn't radically alter anything else) would be much appreciated!

@hlobit hlobit linked a pull request Oct 28, 2024 that will close this issue
@suola
Copy link

suola commented Nov 26, 2024

v3.11 seems to have very similar bug. I can create another issue if needed:

from zoneinfo import ZoneInfo
from datetime import datetime

from apscheduler.triggers.cron import CronTrigger

tz = ZoneInfo("Europe/Helsinki")
trigger = CronTrigger(minute=30, timezone=tz)
dt = datetime(2017, 10, 29, 3, 45, tzinfo=tz, fold=1)

dt = trigger.get_next_fire_time(dt, dt)
# dt = datetime.datetime(2017, 10, 29, 3, 30, fold=1, tzinfo=zoneinfo.ZoneInfo(key='Europe/Helsinki'))

dt = trigger.get_next_fire_time(dt, dt)
# dt = datetime.datetime(2017, 10, 29, 3, 30, fold=1, tzinfo=zoneinfo.ZoneInfo(key='Europe/Helsinki'))

@agronholm
Copy link
Owner

I tried applying your PR in the 3.x branch but kept failing – either the new test failed or the others broke. If you can figure it out, it would be fantastic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants