Time issue
I think there's something wrong with the time of model.date_changed in line 96 of schedulers.py.
https://github.com/celery/django-celery-beat/blob/f1f239c5900bd2b1d24c9f368da7b344627ec500/django_celery_beat/schedulers.py#L96
Maybe it needs to be converted according to the configuration of DJANGO_CELERY_BEAT_TZ_AWARE?
can you help to reproduce the problem with a failing test case as PR?
I'm a beginner and I'm not quite sure if the issue is with model.date_changed. Here are my reproduction steps:
- settings:
- USE_TZ = False
- DJANGO_CELERY_BEAT_TZ_AWARE = False
ModelEntry.is_due
last_run_at_in_tz = maybe_make_aware(self.last_run_at).astimezone(tz)
info(f"self.model.date_changed={self.model.date_changed} last_run_at={self.last_run_at} last_run_at_in_tz={last_run_at_in_tz}") # add this
return self.schedule.is_due(last_run_at_in_tz)
-
Step 1:
- Start the beat using v2.7.0
- log: [2025-07-15 10:18:08,193: INFO/MainProcess] self.model.date_changed=2025-07-15 10:07:30.892475 last_run_at=2025-07-15 02:17:58.134552 last_run_at_in_tz=2025-07-15 10:17:58.134552+08:00
- The task is triggered normally.
-
Step 2:
- The task needs to be re-enabled. Otherwise, the task can run normally.
-
Step 3:
- Start the beat using v2.8.1
- log: [2025-07-15 10:19:17,501: INFO/MainProcess] self.model.date_changed=2025-07-15 10:19:13.690588 last_run_at=2025-07-15 10:19:13.690588 last_run_at_in_tz=2025-07-15 18:19:13.690588+08:00
- The task will no longer be triggered.
According to the logs, the format of last_run_at differs between v2.7.0 and v2.8.1. So, I think there should be a minor issue here.
did you try from main branch? there are some new commits after 2.8.1 release https://github.com/celery/django-celery-beat/commits/main/
I tried, but it didn't work.
I think it's still caused by the fact that last_run_at uses model.date_changed.
I modified the code from model.last_run_at = model.date_changed or self._default_now() to model.last_run_at = self._default_now(), and the task was triggered normally. It may still be necessary to convert model.date_changed.
you can fork this repo, then create a new branch and share a PR on what change you made.
I also ran into this problem and as mentioned in previous comments, it seems to be related to this line:
model.last_run_at = model.date_changed or self._default_now()
The problem in my environment is that _default_now() is in UTC and model.date_changed is a naive time in the server's timezone. Passing model.date_changed to maybe_make_aware() will result in the actual time +/- the tz offset. In my case it's +2 right now, which ends up being in the future.
The following change could fix it, while maintaining the intended behaviour of date_changed. Unfortunately I'm not very experienced with timezones and Celery, so maybe this is not the right way. I also don't know how to run the tests and create a test case for it.
--- a/django_celery_beat/schedulers.py
+++ b/django_celery_beat/schedulers.py
@@ -93,7 +93,10 @@ class ModelEntry(ScheduleEntry):
self.model = model
if not model.last_run_at:
- model.last_run_at = model.date_changed or self._default_now()
+ date_changed = model.date_changed
+ if date_changed and not getattr(settings, 'DJANGO_CELERY_BEAT_TZ_AWARE', True):
+ date_changed = date_changed.replace(tzinfo=self.app.timezone)
+ model.last_run_at = date_changed or self._default_now()
# if last_run_at is not set and
# model.start_time last_run_at should be in way past.
# This will trigger the job to run at start_time
same problem
settings.py
TIME_ZONE = "Asia/Shanghai"
USE_TZ = False
DJANGO_CELERY_BEAT_TZ_AWARE = False
CELERY_TIMEZONE = TIME_ZONE
It will lead to the following result:
my current time: 2025-11-13 16:34:13+08:00
model.date_changed: 2025-11-13 16:34:14.117205
self.last_run_at: 2025-11-13 16:34:13.977566
maybe_make_aware(self.last_run_at): 2025-11-13 16:34:13.977566+00:00
last_run_at_in_tz: 2025-11-14 00:34:13.977566+08:00
if not model.last_run_at:
model.last_run_at = model.date_changed or self._default_now()
self.last_run_at = model.last_run_at
tz = self.app.timezone
last_run_at_in_tz = maybe_make_aware(self.last_run_at).astimezone(tz)
This issue was introduced at https://github.com/celery/django-celery-beat/pull/717, and downgrading to version 2.7.0 resolves the problem.