Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

and function run between time start and end #534

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 80 additions & 17 deletions schedule/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ class CancelJob(object):
pass


class DuringTime:

def __init__(self,
run_start: Optional[datetime.datetime] = None,
run_end: Optional[datetime.datetime] = None,
format: Optional[str] = None):
self.run_start = run_start
self.run_end = run_end
self.format = format


class Scheduler(object):
"""
Objects instantiated by the :class:`Scheduler <Scheduler>` are
Expand Down Expand Up @@ -174,7 +185,7 @@ def _run_job(self, job: "Job") -> None:
self.cancel_job(job)

def get_next_run(
self, tag: Optional[Hashable] = None
self, tag: Optional[Hashable] = None
) -> Optional[datetime.datetime]:
"""
Datetime when the next job should run.
Expand Down Expand Up @@ -252,6 +263,9 @@ def __init__(self, interval: int, scheduler: Scheduler = None):
# optional time of final run
self.cancel_after: Optional[datetime.datetime] = None

# optional time of start run and end run
self.run_during: Optional[DuringTime] = DuringTime

self.tags: Set[Hashable] = set() # unique set of tags for the job
self.scheduler: Optional[Scheduler] = scheduler # scheduler to register with

Expand Down Expand Up @@ -306,9 +320,9 @@ def is_repr(j):
)
else:
fmt = (
"Every %(interval)s "
+ ("to %(latest)s " if self.latest is not None else "")
+ "%(unit)s do %(call_repr)s %(timestats)s"
"Every %(interval)s "
+ ("to %(latest)s " if self.latest is not None else "")
+ "%(unit)s do %(call_repr)s %(timestats)s"
)

return fmt % dict(
Expand Down Expand Up @@ -572,8 +586,8 @@ def to(self, latest: int):
return self

def until(
self,
until_time: Union[datetime.datetime, datetime.timedelta, datetime.time, str],
self,
until_time: Union[datetime.datetime, datetime.timedelta, datetime.time, str],
):
"""
Schedule job to run until the specified moment.
Expand Down Expand Up @@ -609,7 +623,7 @@ def until(
datetime.datetime.now(), until_time
)
elif isinstance(until_time, str):
cancel_after = self._decode_datetimestr(
cancel_after, f = self._decode_datetimestr(
until_time,
[
"%Y-%m-%d %H:%M:%S",
Expand Down Expand Up @@ -639,6 +653,34 @@ def until(
)
return self

def time_between(self, start: str, end: str):
start_time, f1 = self._decode_datetimestr(
start,
[
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%d %H:%M",
"%Y-%m-%d",
"%H:%M:%S",
"%H:%M",
],
)
end_time, f2 = self._decode_datetimestr(
end,
[
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%d %H:%M",
"%Y-%m-%d",
"%H:%M:%S",
"%H:%M",
],
)

if f1 != f2:
raise RuntimeError('The format of the start time and end time must be same!')

self.run_during = DuringTime(start_time, end_time, f1)
return self

def do(self, job_func: Callable, *args, **kwargs):
"""
Specifies the job_func that should be called every time the
Expand Down Expand Up @@ -681,6 +723,10 @@ def run(self):
deadline is reached.

"""
if not self._is_during_time(datetime.datetime.now()):
logger.debug(f"Cancelling job {self} because of out-of the during time")
return CancelJob

if self._is_overdue(datetime.datetime.now()):
logger.debug("Cancelling job %s", self)
return CancelJob
Expand Down Expand Up @@ -760,17 +806,17 @@ def _schedule_next_run(self) -> None:
if not self.last_run or (self.next_run - self.last_run) > self.period:
now = datetime.datetime.now()
if (
self.unit == "days"
and self.at_time > now.time()
and self.interval == 1
self.unit == "days"
and self.at_time > now.time()
and self.interval == 1
):
self.next_run = self.next_run - datetime.timedelta(days=1)
elif self.unit == "hours" and (
self.at_time.minute > now.minute
or (
self.at_time.minute == now.minute
and self.at_time.second > now.second
)
self.at_time.minute > now.minute
or (
self.at_time.minute == now.minute
and self.at_time.second > now.second
)
):
self.next_run = self.next_run - datetime.timedelta(hours=1)
elif self.unit == "minutes" and self.at_time.second > now.second:
Expand All @@ -783,12 +829,29 @@ def _schedule_next_run(self) -> None:
def _is_overdue(self, when: datetime.datetime):
return self.cancel_after is not None and when > self.cancel_after

def _is_during_time(self, when: datetime.datetime):
during_bool = False
f = self.run_during.format
if f is not None:
now_time, f3 = self._decode_datetimestr(
when.strftime(f),
[
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%d %H:%M",
"%Y-%m-%d",
"%H:%M:%S",
"%H:%M",
],
)
during_bool = self.run_during.run_start < now_time < self.run_during.run_end
return during_bool

def _decode_datetimestr(
self, datetime_str: str, formats: List[str]
self, datetime_str: str, formats: List[str]
) -> Optional[datetime.datetime]:
for f in formats:
try:
return datetime.datetime.strptime(datetime_str, f)
return datetime.datetime.strptime(datetime_str, f), f
except ValueError:
pass
return None
Expand Down
101 changes: 57 additions & 44 deletions test_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,66 +95,66 @@ def test_time_units(self):
with self.assertRaises(IntervalError):
job_instance.week
with self.assertRaisesRegex(
IntervalError,
(
r"Scheduling \.monday\(\) jobs is only allowed for weekly jobs\. "
r"Using \.monday\(\) on a job scheduled to run every 2 or more "
r"weeks is not supported\."
),
IntervalError,
(
r"Scheduling \.monday\(\) jobs is only allowed for weekly jobs\. "
r"Using \.monday\(\) on a job scheduled to run every 2 or more "
r"weeks is not supported\."
),
):
job_instance.monday
with self.assertRaisesRegex(
IntervalError,
(
r"Scheduling \.tuesday\(\) jobs is only allowed for weekly jobs\. "
r"Using \.tuesday\(\) on a job scheduled to run every 2 or more "
r"weeks is not supported\."
),
IntervalError,
(
r"Scheduling \.tuesday\(\) jobs is only allowed for weekly jobs\. "
r"Using \.tuesday\(\) on a job scheduled to run every 2 or more "
r"weeks is not supported\."
),
):
job_instance.tuesday
with self.assertRaisesRegex(
IntervalError,
(
r"Scheduling \.wednesday\(\) jobs is only allowed for weekly jobs\. "
r"Using \.wednesday\(\) on a job scheduled to run every 2 or more "
r"weeks is not supported\."
),
IntervalError,
(
r"Scheduling \.wednesday\(\) jobs is only allowed for weekly jobs\. "
r"Using \.wednesday\(\) on a job scheduled to run every 2 or more "
r"weeks is not supported\."
),
):
job_instance.wednesday
with self.assertRaisesRegex(
IntervalError,
(
r"Scheduling \.thursday\(\) jobs is only allowed for weekly jobs\. "
r"Using \.thursday\(\) on a job scheduled to run every 2 or more "
r"weeks is not supported\."
),
IntervalError,
(
r"Scheduling \.thursday\(\) jobs is only allowed for weekly jobs\. "
r"Using \.thursday\(\) on a job scheduled to run every 2 or more "
r"weeks is not supported\."
),
):
job_instance.thursday
with self.assertRaisesRegex(
IntervalError,
(
r"Scheduling \.friday\(\) jobs is only allowed for weekly jobs\. "
r"Using \.friday\(\) on a job scheduled to run every 2 or more "
r"weeks is not supported\."
),
IntervalError,
(
r"Scheduling \.friday\(\) jobs is only allowed for weekly jobs\. "
r"Using \.friday\(\) on a job scheduled to run every 2 or more "
r"weeks is not supported\."
),
):
job_instance.friday
with self.assertRaisesRegex(
IntervalError,
(
r"Scheduling \.saturday\(\) jobs is only allowed for weekly jobs\. "
r"Using \.saturday\(\) on a job scheduled to run every 2 or more "
r"weeks is not supported\."
),
IntervalError,
(
r"Scheduling \.saturday\(\) jobs is only allowed for weekly jobs\. "
r"Using \.saturday\(\) on a job scheduled to run every 2 or more "
r"weeks is not supported\."
),
):
job_instance.saturday
with self.assertRaisesRegex(
IntervalError,
(
r"Scheduling \.sunday\(\) jobs is only allowed for weekly jobs\. "
r"Using \.sunday\(\) on a job scheduled to run every 2 or more "
r"weeks is not supported\."
),
IntervalError,
(
r"Scheduling \.sunday\(\) jobs is only allowed for weekly jobs\. "
r"Using \.sunday\(\) on a job scheduled to run every 2 or more "
r"weeks is not supported\."
),
):
job_instance.sunday

Expand Down Expand Up @@ -363,6 +363,19 @@ def test_until_time(self):
assert mock_job.call_count == 0
assert len(schedule.jobs) == 0

def test_between_time(self):
mock_job = make_mock_job()
with mock_datetime(2020, 1, 1, 11, 35, 10):
every(1).seconds.time_between("11:35:00", "11:36:00").do(mock_job)
with mock_datetime(2020, 1, 1, 11, 35, 15):
schedule.run_pending()
assert mock_job.call_count == 1
assert len(schedule.jobs) == 1
with mock_datetime(2020, 1, 1, 11, 36, 10):
schedule.run_all()
assert mock_job.call_count == 1
assert len(schedule.jobs) == 0

def test_weekday_at_todady(self):
mock_job = make_mock_job()

Expand Down Expand Up @@ -455,8 +468,8 @@ def test_next_run_time(self):
assert every().saturday.do(mock_job).next_run.day == 9
assert every().sunday.do(mock_job).next_run.day == 10
assert (
every().minute.until(datetime.time(12, 17)).do(mock_job).next_run.minute
== 16
every().minute.until(datetime.time(12, 17)).do(mock_job).next_run.minute
== 16
)

def test_next_run_time_day_end(self):
Expand Down