diff --git a/src/task_processor/models.py b/src/task_processor/models.py index e7782fe..6d198a5 100644 --- a/src/task_processor/models.py +++ b/src/task_processor/models.py @@ -180,7 +180,23 @@ def should_execute(self) -> bool: # If we have never run this task, then we should execute it only if # the time has passed after which we want to ensure this task runs. # This allows us to control when intensive tasks should be run. - return not (self.first_run_time and self.first_run_time > now.time()) + if not self.first_run_time: + return True + first_run_today = now.replace( + hour=self.first_run_time.hour, + minute=self.first_run_time.minute, + second=self.first_run_time.second, + microsecond=self.first_run_time.microsecond, + ) + # Handle midnight boundary using 12-hour window heuristic. + time_difference = (now - first_run_today).total_seconds() + if time_difference > 12 * 3600: + # first_run_today appears far in the past; it refers to tomorrow. + return False + if time_difference < -12 * 3600: + # first_run_today appears far in the future; it refers to yesterday. + return True + return now >= first_run_today # if the last run was at t- run_every, then we should execute it if (timezone.now() - last_task_run.started_at) >= self.run_every: diff --git a/tests/unit/task_processor/test_unit_task_processor_models.py b/tests/unit/task_processor/test_unit_task_processor_models.py index 0adc96b..0fb6a0c 100644 --- a/tests/unit/task_processor/test_unit_task_processor_models.py +++ b/tests/unit/task_processor/test_unit_task_processor_models.py @@ -3,6 +3,7 @@ import pytest from django.utils import timezone +from freezegun import freeze_time from pytest_mock import MockerFixture from task_processor.decorators import register_task_handler @@ -80,3 +81,31 @@ def test_recurring_task_run_should_execute_first_run_at( ).should_execute == expected ) + + +@freeze_time("2026-01-15 23:05:23") +def test_recurring_task_should_execute__first_run_time_after_midnight__returns_false() -> ( + None +): + # Given + task = RecurringTask( + first_run_time=time(0, 5, 23), + run_every=timedelta(days=1), + ) + + # When & Then + assert task.should_execute is False + + +@freeze_time("2026-01-16 00:30:00") +def test_recurring_task_should_execute__first_run_time_before_midnight__returns_true() -> ( + None +): + # Given + task = RecurringTask( + first_run_time=time(23, 0, 0), + run_every=timedelta(days=1), + ) + + # When & Then + assert task.should_execute is True