Repository: celery/celery

Issue:
Possible DST issue?
I just noticed a crontab task setup to run at 12:00 today for some reason ran at at 
both 11:00 and then again at 12:00. I've checked the server date and time and it does seem to have made the switch to winter time last night so i'm not sure if this was a fluke or really related to the DST but i don't see any other reason for it to run the task twice...

My crontab setup, for this particular task, looks like this:

```
from celery.schedules import crontab

CELERYBEAT_SCHEDULE = {
    'event_suggestions': {
        'task': 'events.event_suggestions',
        'schedule': crontab(hour=12, minute=0, day_of_week=1)
    }
}
```

Versions are:
celery==3.0.24
django-celery==3.0.23



Discussion:
We've experienced the same issue last night. Our task scheduled at 01:01 running both at 00:01 and 01:01.

```
'daily_notifications': {
    'task': 'notifications.tasks.daily_notifications',
    'schedule': crontab(minute=1, hour=1),
},
```

Versions:
celery==3.0.23
django-celery==3.0.11

We've django's TIME_ZONE setting set to 'Europe/London', as well as the system's /etc/timezone file. USE_TZ to False.

My TIME_ZONE setting is 'Europe/Amsterdam' as is /etc/timezone. USE_TZ is True in my case. 

Do you have pytz installed?  Celery 3.0.x doesn't depend on it, so could be you're running without it.  I'm not sure if python does account for dst changes when comparing, but you probably would need the timezone information present (which pytz provides)

@ask `pytz==2013.7` i think it's actually installed as part of the requirements of django-celery-with-redis?

@tijs Yeah, django-celery installs it, and celery 3.1 will depend on pytz (replacing the python-dateutil dependency).

Thanks for the info though, important to know it doesn't work even if pytz is installed.

Yes, also pytz installed here. In case it helps: pytz==2013b

Hi, we saw this problem today as well.  We are on heroku...  
celery==3.0.23  
django-celery==3.0.23
pytz==2013.7
amqp==1.0.13

We also saw this issue this week.

celery==3.0.21
django-celery==3.0.21
pytz==2013b
amqp==1.0.13

I will look into this soon, probably there is some way to handle this in pytz.

Yep, just experienced the issue live !!! I got affected slightly different, here is a description: http://stackoverflow.com/questions/26568990/celery-django-rabbitmq-dst-bug

Is this a django specific issue?

We also ran into this issue during the last DST changeover. We have a daily task that executes at 5:00pm, however it was triggered at both 4:00pm and 5:00 pm. I'm wondering if we'll see similar behavior when DST starts in March. 

Using celery version 3.1.18

Django settings roughly look like the following:

``` py
USE_TZ = True

TIME_ZONE = 'America/New_York'

CELERY_TIMEZONE = 'America/New_York'

# probably not relevant?
CELERY_ENABLE_UTC = True

CELERYBEAT_SCHEDULE = {
    'send_reminder_emails': {
        'task': 'app.tasks.send_reminder_emails',
        'schedule': crontab(hour=17, minute=0, day_of_week=[1, 2, 3, 4, 5]),
    },
    # snip
    # ...
}
```

@auvipy 
Maybe, but I would doubt it. In our case, there isn't any Django integration beyond loading settings/discovering tasks from the Django project. django-celery isn't being used, so it's not an issue w/ the database-based scheduler.

Closing this, as we don't have the resources to complete this task.

I just hit this issue too. Caused significant confusion and disruption. How can this issue just be closed?!

so another DST change, bitten again.

in my opinion this is a showstopper bug and if (sadly) the project does not have the resources
at the moment to fix it, it should own up to that **openly** so users have the chance to factor
this in in their workflow/risk management.

what is wrong is not _not fixing_ this bug for lack of resources, but _burrying_ it in a closed bug report.

> what is wrong is not not fixing this bug for lack of resources, but burrying it in a closed bug report.

I kind of have to agree with this. I understand (although disagree with) the idea of closing issues that cannot be tackled. However, by closing the issue and removing bug labels, these issues cannot be tracked. There is no way for potential users to audit the health of a package, understand its caveats etc... 

A few ideas:
- Create a new label to indicate the lack of resources such as "help wanted"
- Create a separate issue to track these issues. 

I'm going to reopen this for now. But if it won't be resolved by 5.0 it won't be resolved at all.


Resolving patch:
diff --git a/celery/utils/time.py b/celery/utils/time.py
--- a/celery/utils/time.py
+++ b/celery/utils/time.py
@@ -209,6 +209,9 @@ def remaining(start, ends_in, now=None, relative=False):
         ~datetime.timedelta: Remaining time.
     """
     now = now or datetime.utcnow()
+    if now.utcoffset() != start.utcoffset():
+        # Timezone has changed, or DST started/ended
+        start = start.replace(tzinfo=now.tzinfo)
     end_date = start + ends_in
     if relative:
         end_date = delta_resolution(end_date, ends_in)


Test patch:
diff --git a/t/unit/app/test_schedules.py b/t/unit/app/test_schedules.py
--- a/t/unit/app/test_schedules.py
+++ b/t/unit/app/test_schedules.py
@@ -1,6 +1,7 @@
 from __future__ import absolute_import, unicode_literals
 
 import time
+import pytz
 from contextlib import contextmanager
 from datetime import datetime, timedelta
 from pickle import dumps, loads
@@ -439,6 +440,40 @@ def test_leapyear(self):
         )
         assert next == datetime(2016, 2, 29, 14, 30)
 
+    def test_day_after_dst_end(self):
+        # Test for #1604 issue with region configuration using DST
+        tzname = "Europe/Paris"
+        self.app.timezone = tzname
+        tz = pytz.timezone(tzname)
+        crontab = self.crontab(minute=0, hour=9)
+
+        # Set last_run_at Before DST end
+        last_run_at = tz.localize(datetime(2017, 10, 28, 9, 0))
+        # Set now after DST end
+        now = tz.localize(datetime(2017, 10, 29, 7, 0))
+        crontab.nowfun = lambda: now
+        next = now + crontab.remaining_estimate(last_run_at)
+
+        assert next.utcoffset().seconds == 3600
+        assert next == tz.localize(datetime(2017, 10, 29, 9, 0))
+
+    def test_day_after_dst_start(self):
+        # Test for #1604 issue with region configuration using DST
+        tzname = "Europe/Paris"
+        self.app.timezone = tzname
+        tz = pytz.timezone(tzname)
+        crontab = self.crontab(minute=0, hour=9)
+
+        # Set last_run_at Before DST start
+        last_run_at = tz.localize(datetime(2017, 3, 25, 9, 0))
+        # Set now after DST start
+        now = tz.localize(datetime(2017, 3, 26, 7, 0))
+        crontab.nowfun = lambda: now
+        next = now + crontab.remaining_estimate(last_run_at)
+
+        assert next.utcoffset().seconds == 7200
+        assert next == tz.localize(datetime(2017, 3, 26, 9, 0))
+
 
 class test_crontab_is_due: