Browse Source

celery.schedules now works with timezone aware datetime's

Ask Solem 12 years ago
parent
commit
e8f3b09c14
2 changed files with 29 additions and 4 deletions
  1. 9 3
      celery/schedules.py
  2. 20 1
      celery/utils/timeutils.py

+ 9 - 3
celery/schedules.py

@@ -17,7 +17,8 @@ from dateutil.relativedelta import relativedelta
 from . import current_app
 from .utils import is_iterable
 from .utils.timeutils import (timedelta_seconds, weekday, maybe_timedelta,
-                              remaining, humanize_seconds)
+                              remaining, humanize_seconds, is_naive, to_utc,
+                              timezone)
 from .datastructures import AttributeDict
 
 
@@ -38,8 +39,11 @@ class schedule(object):
 
     def remaining_estimate(self, last_run_at):
         """Returns when the periodic task should run next as a timedelta."""
-        return remaining(last_run_at, self.run_every, relative=self.relative,
-                         now=self.now())
+        now = self.now()
+        if not is_naive(last_run_at):
+            now = to_utc(now)
+        return remaining(last_run_at, self.run_every,
+                         relative=self.relative, now=now)
 
     def is_due(self, last_run_at):
         """Returns tuple of two items `(is_due, next_time_to_run)`,
@@ -405,6 +409,8 @@ class crontab(schedule):
 
     def remaining_estimate(self, last_run_at):
         """Returns when the periodic task should run next as a timedelta."""
+        if not is_naive(last_run_at):
+            last_run_at = last_run_at.astimezone(timezone.utc)
         dow_num = last_run_at.isoweekday() % 7  # Sunday is day 0, not day 7
 
         execute_this_date = (last_run_at.month in self.month_of_year and

+ 20 - 1
celery/utils/timeutils.py

@@ -48,7 +48,7 @@ class _Zone(object):
         return self.get_timezone(tzinfo)
 
     def to_local(self, dt, local=None, orig=None):
-        return dt.replace(tzinfo=orig or self.utc).astimezone(
+        return to_tz(dt, orig or self.utc).astimezone(
                     self.tz_or_local(local))
 
     def get_timezone(self, zone):
@@ -192,3 +192,22 @@ def maybe_iso8601(dt):
     if isinstance(dt, datetime):
         return dt
     return parse_iso8601(dt)
+
+
+def is_naive(dt):
+    """Returns :const:`True` if the datetime is naive
+    (does not have timezone information)."""
+    return dt.tzinfo is None or dt.tzinfo.utcoffset(dt) is None
+
+
+def set_tz(dt, tz):
+    """Sets the timezone for a datetime object."""
+    if hasattr(tz, 'localize'):
+        # works on pytz timezones
+        return tz.localize(dt, is_dst=None)
+    return dt.replace(tzinfo=tz)
+
+
+def to_utc(dt):
+    """Converts naive datetime to UTC"""
+    return set_tz(dt, timezone.utc)