apscheduler: AndTrigger does not work with IntervalTrigger

Recently, I learn how to use apscheduler, and I find something interesting. From the latest doc’s example, AndTrigger can be used as:

from apscheduler.triggers.combining import AndTrigger
from apscheduler.triggers.interval import IntervalTrigger
from apscheduler.triggers.cron import CronTrigger


trigger = AndTrigger([IntervalTrigger(hours=2),
                      CronTrigger(day_of_week='sat,sun')])
scheduler.add_job(job_function, trigger)

Actually, it doesn’t work. I have read https://github.com/agronholm/apscheduler/issues/281 and https://github.com/agronholm/apscheduler/issues/309, and know that the time generated by the two triggers never coincide.

So, if I sepcify a start_date like

import datetime
from tzlocal import get_localzone

from apscheduler.triggers.combining import AndTrigger
from apscheduler.triggers.interval import IntervalTrigger

tz = get_localzone()
now = datetime.datetime.now(tz=tz)

trigger = AndTrigger([IntervalTrigger(hours=2, start_date=now),
                      IntervalTrigger(hours=3, start_date=now)])
next_run_time = trigger.get_next_fire_time(None, now + datetime.timedelta(seconds=10))

print(now)
print(next_run_time)

It works as expected.

2019-02-24 22:23:26.052845+09:00
2019-02-25 04:23:26.052845+09:00

But when schedule a job, it does work.

import datetime
from tzlocal import get_localzone

from apscheduler.triggers.combining import AndTrigger
from apscheduler.triggers.interval import IntervalTrigger
from apscheduler.schedulers.blocking import BlockingScheduler

tz = get_localzone()
now = datetime.datetime.now(tz=tz)


def tick():
    print('tick')


scheduler = BlockingScheduler()
trigger = AndTrigger([IntervalTrigger(seconds=2, start_date=now),
                      IntervalTrigger(seconds=3, start_date=now)])
scheduler.add_job(tick, trigger)
scheduler.start()

The real problem it that AndTrigger may not support IntervalTrigger

In _process_jobs, it will calculate run times .

https://github.com/agronholm/apscheduler/blob/ab991eb29eedb0943356c4d7ddca8320e7845965/apscheduler/schedulers/base.py#L970-L972

https://github.com/agronholm/apscheduler/blob/ab991eb29eedb0943356c4d7ddca8320e7845965/apscheduler/job.py#L123-L137

https://github.com/agronholm/apscheduler/blob/ab991eb29eedb0943356c4d7ddca8320e7845965/apscheduler/triggers/combining.py#L53-L62

https://github.com/agronholm/apscheduler/blob/ab991eb29eedb0943356c4d7ddca8320e7845965/apscheduler/triggers/interval.py#L52-L66

In the loop of calculating next_run_time in AndTrigger, we only change the value of now. But in IntervalTrigger, if we pass previous_fire_time which is not None, it will just add the interval. So it caused a dead loop.

Similarly, When we combine IntervalTrigger and CronTrigger, if previous_fire_time is not None, only CronTrigger will walk.

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 1
  • Comments: 24 (15 by maintainers)

Most upvoted comments

Something like this is coming to APScheduler 4.0.

I’ve posted issue #465 to track v4.0 development so I will stop posting updates here.

Update: work on AnyIO v2.0 is virtually complete. There are a couple blocker issues I need to take care of before moving on with the release.

Sure - but as far as I’m concerned, just noting this limitation in documentation would be enough. There are ways around it after all.