Calculating the next run date of a Celery periodic task
I just wanted to get the next run date of a Celery periodic task. Turned into a mini rabbit hole.
The problem
You have a periodic task in Celery defined with a crontab(...)
schedule, and you want to calculate the next time it's supposed to run.
Example: you want to find out when crontab(hour=12, minute=0)
will trigger next after now.
Simple, right? There’s croniter library, which seems to be designed to solve this exact problem. Just use it, right?
Well.
First mistake: trying croniter with crontab
So my first instinct was to use croniter
like this:
from celery.schedules import crontab
from croniter import croniter
from datetime import datetime
schedule = crontab(hour=12, minute=0)
cron = croniter(schedule, datetime.now())
next_run = cron.get_next(datetime)
Boom 💥 doesn’t work. Because Celery’s crontab
is not a string and croniter
expects a string like "0 12 * * *"
:
AttributeError: 'crontab' object has no attribute 'lower'
And no, crontab()
does not have a nice .as_cron_string()
method either.
So now you’re stuck parsing crontab
's internal fields (._orig_minute
, ._orig_hour
, etc) just to reconstruct a string - and it starts to smell like overengineering for something that should be simple.
The right way (which I learned too late)
Turns out Celery’s crontab
(and all schedules derived from celery.schedules.BaseSchedule
) already has a method for this:
from datetime import datetime
from celery.schedules import crontab
schedule = crontab(hour=12, minute=0)
now = datetime.now()
# `now` is datetime.datetime(2025, 6, 11, 0, 16, 58, 484085)
next_run = now + schedule.remaining_delta(now)[1]
# `next_run` is datetime.datetime(2025, 6, 11, 12, 0)
That’s it. You don’t need croniter
at all. Celery knows how to calculate the delta to the next run. It just doesn’t shout about it in the docs.
Summary
don’t reinvent Celery’s scheduling logic - it already has what you need;
crontab
is not a cron string, don’t try to treat it like one;use
.remaining_delta(last_run)
to calculate when the next run will happen.
Hope this saves someone the 2 hours I wasted trying to do it the wrong way.