Skip to content

Commit 2acd6a2

Browse files
costelaauvipy
authored andcommitted
make last_run_at tz aware before passing to celery (#233)
celery always makes TZ aware and will wrongly assume UTC, even when the dates from the django model have an (implicit) TZ.
1 parent 8eac2ac commit 2acd6a2

File tree

3 files changed

+29
-8
lines changed

3 files changed

+29
-8
lines changed

django_celery_beat/schedulers.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,7 @@ def __init__(self, model, app=None):
9494
if not model.last_run_at:
9595
model.last_run_at = self._default_now()
9696

97-
last_run_at = model.last_run_at
98-
99-
if getattr(settings, 'DJANGO_CELERY_BEAT_TZ_AWARE', True):
100-
last_run_at = make_aware(last_run_at)
101-
102-
self.last_run_at = last_run_at
97+
self.last_run_at = model.last_run_at
10398

10499
def _disable(self, model):
105100
model.no_changes = True
@@ -134,7 +129,7 @@ def is_due(self):
134129
self.model.save()
135130
return schedules.schedstate(False, None) # Don't recheck
136131

137-
return self.schedule.is_due(self.last_run_at)
132+
return self.schedule.is_due(make_aware(self.last_run_at))
138133

139134
def _default_now(self):
140135
# The PyTZ datetime must be localised for the Django-Celery-Beat

django_celery_beat/utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ def make_aware(value):
2121
# then convert to the Django configured timezone.
2222
default_tz = timezone.get_default_timezone()
2323
value = timezone.localtime(value, default_tz)
24+
else:
25+
# naive datetimes are assumed to be in local timezone.
26+
if timezone.is_naive(value):
27+
value = timezone.make_aware(value, timezone.get_default_timezone())
2428
return value
2529

2630

t/unit/test_schedulers.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
from django.contrib.admin.sites import AdminSite
1111
from django.contrib.messages.storage.fallback import FallbackStorage
12-
from django.test import RequestFactory
12+
from django.test import RequestFactory, override_settings
13+
from django.utils import timezone
1314

1415
from celery.five import monotonic, text_t
1516
from celery.schedules import schedule, crontab, solar
@@ -137,6 +138,27 @@ def test_entry(self):
137138
assert e3.last_run_at > e2.last_run_at
138139
assert e3.total_run_count == 1
139140

141+
@override_settings(
142+
USE_TZ=False,
143+
DJANGO_CELERY_BEAT_TZ_AWARE=False
144+
)
145+
@timezone.override('Europe/Berlin')
146+
@pytest.mark.celery(timezone='Europe/Berlin')
147+
def test_entry_is_due__no_use_tz(self):
148+
assert self.app.timezone.zone == 'Europe/Berlin'
149+
150+
# simulate last_run_at from DB - not TZ aware but localtime
151+
right_now = timezone.now()
152+
153+
m = self.create_model_crontab(
154+
crontab(minute='*/10'),
155+
last_run_at=right_now,
156+
)
157+
e = self.Entry(m, app=self.app)
158+
159+
assert e.is_due().is_due is False
160+
assert e.is_due().next <= 600 # 10 minutes; see above
161+
140162
def test_task_with_start_time(self):
141163
interval = 10
142164
right_now = self.app.now()

0 commit comments

Comments
 (0)