Explorar o código

Fix crontab that use month of year and day of month

This patch fix the use of periodic tasks with
both month_of_year and day_of_month.

Before this patch, in July for a tasks that should
run in February, the periodic task run again and
again. Celery believes it is 5 months late when
it is 7 month in advance.
Guillaume Gauvrit %!s(int64=11) %!d(string=hai) anos
pai
achega
aed7888e30
Modificáronse 2 ficheiros con 58 adicións e 5 borrados
  1. 14 5
      celery/schedules.py
  2. 44 0
      celery/tests/tasks/test_tasks.py

+ 14 - 5
celery/schedules.py

@@ -379,7 +379,11 @@ class crontab(schedule):
                 flag = (datedata.dom == len(days_of_month) or
                         day_out_of_range(datedata.year,
                                          months_of_year[datedata.moy],
-                                         days_of_month[datedata.dom]))
+                                         days_of_month[datedata.dom]) or
+                        (self.maybe_make_aware(datetime(datedata.year,
+                         months_of_year[datedata.moy],
+                         days_of_month[datedata.dom])) < last_run_at))
+
                 if flag:
                     datedata.dom = 0
                     datedata.moy += 1
@@ -449,10 +453,11 @@ class crontab(schedule):
                                  self._orig_day_of_month,
                                  self._orig_month_of_year), None)
 
-    def remaining_estimate(self, last_run_at, tz=None):
+    def remaining_delta(self, last_run_at, tz=None):
         """Returns when the periodic task should run next as a timedelta."""
         tz = tz or self.tz
         last_run_at = self.maybe_make_aware(last_run_at)
+        now = self.maybe_make_aware(self.now())
         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
@@ -460,6 +465,9 @@ class crontab(schedule):
                              dow_num in self.day_of_week)
 
         execute_this_hour = (execute_this_date and
+                             last_run_at.day == now.day and
+                             last_run_at.month == now.month and
+                             last_run_at.year == now.year and
                              last_run_at.hour in self.hour and
                              last_run_at.minute < max(self.minute))
 
@@ -499,10 +507,11 @@ class crontab(schedule):
                 else:
                     delta = self._delta_to_next(last_run_at,
                                                 next_hour, next_minute)
+        return self.to_local(last_run_at), delta, self.to_local(now)
 
-        now = self.maybe_make_aware(self.now())
-        return remaining(self.to_local(last_run_at), delta,
-                         self.to_local(now))
+    def remaining_estimate(self, last_run_at):
+        """Returns when the periodic task should run next as a timedelta."""
+        return remaining(*self.remaining_delta(last_run_at))
 
     def is_due(self, last_run_at):
         """Returns tuple of two items `(is_due, next_time_to_run)`,

+ 44 - 0
celery/tests/tasks/test_tasks.py

@@ -1,6 +1,8 @@
 from __future__ import absolute_import
 from __future__ import with_statement
 
+import time
+
 from datetime import datetime, timedelta
 from functools import wraps
 from mock import patch
@@ -616,6 +618,14 @@ def monthly():
     pass
 
 
+@periodic_task(run_every=crontab(hour=22,
+                                 day_of_week='*',
+                                 month_of_year='2',
+                                 day_of_month='26,27,28'))
+def monthly_moy():
+    pass
+
+
 @periodic_task(run_every=crontab(hour=7, minute=30,
                                  day_of_week='thursday',
                                  day_of_month='8-14',
@@ -1212,6 +1222,40 @@ class test_crontab_is_due(Case):
         self.assertFalse(due)
         self.assertEqual(remaining, 4 * 24 * 60 * 60 - 3 * 60 * 60)
 
+    @patch_crontab_nowfun(monthly_moy, datetime(2014, 2, 26, 22, 0))
+    def test_monthly_moy_execution_is_due(self):
+        due, remaining = monthly_moy.run_every.is_due(
+            datetime(2013, 7, 4, 10, 0))
+        self.assertTrue(due)
+        self.assertEqual(remaining, 60.)
+
+    @patch_crontab_nowfun(monthly_moy, datetime(2013, 6, 28, 14, 30))
+    def test_monthly_moy_execution_is_not_due(self):
+        due, remaining = monthly_moy.run_every.is_due(
+            datetime(2013, 6, 28, 22, 14))
+        self.assertFalse(due)
+        attempt = (
+            time.mktime(datetime(2014, 2, 26, 22, 0).timetuple()) -
+            time.mktime(datetime(2013, 6, 28, 14, 30).timetuple()) -
+            60 * 60
+        )
+        self.assertEqual(remaining, attempt)
+
+    @patch_crontab_nowfun(monthly_moy, datetime(2014, 2, 26, 22, 0))
+    def test_monthly_moy_execution_is_due2(self):
+        due, remaining = monthly_moy.run_every.is_due(
+            datetime(2013, 2, 28, 10, 0))
+        self.assertTrue(due)
+        self.assertEqual(remaining, 60.)
+
+    @patch_crontab_nowfun(monthly_moy, datetime(2014, 2, 26, 21, 0))
+    def test_monthly_moy_execution_is_not_due2(self):
+        due, remaining = monthly_moy.run_every.is_due(
+            datetime(2013, 6, 28, 22, 14))
+        self.assertFalse(due)
+        attempt = 60 * 60
+        self.assertEqual(remaining, attempt)
+
     @patch_crontab_nowfun(yearly, datetime(2010, 3, 11, 7, 30))
     def test_yearly_execution_is_due(self):
         due, remaining = yearly.run_every.is_due(