Python Everyday Routine Scheduler

Introduction

Python built-in event scheduler library sched is a low level library for scheduling events. However, it is not straightforward to use to schedule daily routines.

In this blog post, I would like to discuss about how to use Python sched, time, and datetime for scheduling daily routines.

Python Everyday Routine Scheduler

Everyday Routine

Everyday routine is something that the user will always do everyday, including workdays and weekends, at certain fixed time. For example, turning on TV to watch CNN morning news briefing at 8:00 AM and opening browser to check Emails at 10:00 AM are all everyday routines. Manually doing those could be tedious in some scenarios. Therefore, we could employ computer programs to automate the everyday routines.

Scheduler Python Implementation

In the following implementation, two major scheduler functions, run_every_one_rounded_hour_everyday and run_at_fixed_time_everyday, have been created.

run_every_one_rounded_hour_everyday runs given action every one rounded hour, i.e., 0:00 AM, 1:00 AM, …, 23:00 PM, everyday.

run_at_fixed_time_everyday run given action at specified time everyday. Of course, run_at_fixed_time_everyday can do what run_every_one_rounded_hour_everyday does.

scheduler.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
from typing import List, NamedTuple
import sched
import time
import datetime
import random


def round_date_time_to_hour(date_time: datetime.datetime) -> datetime.datetime:
"""Round date_time to hour.

For example, round "2021-12-04 19:58:21.453642" to "2021-12-04 19:00:00".

Args:
date_time (datetime.datetime): datetime to be rounded.

Returns:
datetime.datetime: datetime rounded.
"""

# Not entirely sure if this can always do the rounding correctly.
# rounded_date_time = datetime.datetime(
# year=date_time.year,
# month=date_time.month,
# day=date_time.day,
# hour=date_time.hour,
# minute=0,
# second=0,
# microsecond=0,
# tzinfo=date_time.tzinfo,
# )

# However, this can.
time_tuple = date_time.timetuple()
rounded_time_tuple = time.struct_time((
time_tuple.tm_year,
time_tuple.tm_mon,
time_tuple.tm_mday,
time_tuple.tm_hour,
0, # tm_min
0, # tm_sec
time_tuple.tm_wday,
time_tuple.tm_yday,
time_tuple.tm_isdst,
))

rounded_date_time = datetime.datetime.fromtimestamp(
time.mktime(rounded_time_tuple))

return rounded_date_time


def create_date_time_after_n_hours(date_time: datetime.datetime,
n: int) -> datetime.datetime:
"""Create the datetime after n hours comparing to the input datetime.

For example, round "2021-12-04 23:00:00" to "2021-12-05 00:00:00".

Args:
date_time (datetime.datetime): The input datetime.
n (int): The number of hours.

Returns:
datetime.datetime: The datetime n hours after the the input datetime.
"""

date_time_delta = datetime.timedelta(hours=n)
date_time_n_hours_later = date_time + date_time_delta

return date_time_n_hours_later


def run_every_one_rounded_hour_everyday() -> None:
"""Schedule to run action every one rounded hour for everyday.

For example, now the time is 14:32, the actions are scheduled to run
on everyday at 15:00, 16:00, 17:00, ...
"""

action_period = 1 # hour
fruits = ["apple", "banana", "cherry"]

scheduler = sched.scheduler(time.time, time.sleep)

now_date_time = datetime.datetime.now()
rounded_date_time = round_date_time_to_hour(date_time=now_date_time)
# The first action might not run at the rounded time.
# scheduled_date_time = rounded_date_time
# If we want to run the first action at the rounded time,
# the following date_time has to be used for the first scheduled time.
scheduled_date_time = create_date_time_after_n_hours(
date_time=rounded_date_time, n=action_period)

# Loop forever.
while True:
t = time.mktime(scheduled_date_time.timetuple())
scheduler.enterabs(time=t,
priority=1,
action=eat_fruit,
argument=(),
kwargs={
"fruit": random.choice(fruits),
})
print(f"Scheduled action to run at {scheduled_date_time}.")
scheduler.run()
print(f"The action to run at {scheduled_date_time} was completed at "
f"{datetime.datetime.now()}.")
# Prepare the time for the next action.
scheduled_date_time = create_date_time_after_n_hours(
date_time=scheduled_date_time, n=action_period)


class ScheduledTime(NamedTuple):

hour: int
minute: int
second: int


def run_at_fixed_time_everyday(scheduled_times: List[ScheduledTime]) -> None:
"""Schedule to run action at fixed time everyday.

For example, the actions are scheduled to run on everyday
at 07:00, 12:00, 15:00.

Args:
scheduled_times (List[ScheduledTime]): A list of the scheduled time.
"""

fruits = ["apple", "banana", "cherry"]

scheduler = sched.scheduler(time.time, time.sleep)

def create_scheduled_date_times(
year: int, month: int, day: int,
scheduled_times: List[ScheduledTime]) -> List[datetime.datetime]:

scheduled_date_times = []
for scheduled_time in scheduled_times:
date_time = datetime.datetime(
year=year,
month=month,
day=day,
hour=scheduled_time.hour,
minute=scheduled_time.minute,
second=scheduled_time.second,
microsecond=0,
)
scheduled_date_times.append(date_time)

return scheduled_date_times

now_date_time = datetime.datetime.now()
scheduled_date_times = create_scheduled_date_times(
year=now_date_time.year,
month=now_date_time.month,
day=now_date_time.day,
scheduled_times=scheduled_times)

# Loop forever.
while True:
for scheduled_date_time in scheduled_date_times:
if scheduled_date_time > datetime.datetime.now():
t = time.mktime(scheduled_date_time.timetuple())
scheduler.enterabs(time=t,
priority=1,
action=eat_fruit,
argument=(),
kwargs={
"fruit": random.choice(fruits),
})
print(f"Scheduled action to run at {scheduled_date_time}.")

scheduler.run()
print(f"Scheduled actions completed.")

tomorrow_scheduled_date_times = [
create_date_time_after_n_hours(date_time=scheduled_date_time, n=24)
for scheduled_date_time in scheduled_date_times
]
scheduled_date_times = tomorrow_scheduled_date_times


def eat_fruit(fruit: str) -> None:

print(f"Eating {fruit} at {datetime.datetime.now()}.")


if __name__ == "__main__":

# The functionality of run_every_one_rounded_hour_everyday
# could be also be achieved by run_at_fixed_time_everyday.
# run_every_one_rounded_hour_everyday()

now_date_time = datetime.datetime.now()
planned_date_time = now_date_time + datetime.timedelta(minutes=1)

scheduled_time_1 = ScheduledTime(hour=planned_date_time.hour,
minute=planned_date_time.minute,
second=0)
scheduled_time_2 = ScheduledTime(hour=planned_date_time.hour,
minute=planned_date_time.minute,
second=10)
scheduled_time_3 = ScheduledTime(hour=planned_date_time.hour,
minute=planned_date_time.minute,
second=20)

scheduled_times = [scheduled_time_1, scheduled_time_2, scheduled_time_3]

run_at_fixed_time_everyday(scheduled_times=scheduled_times)

We could see the scheduler program runs as expected.

1
2
3
4
5
6
7
8
9
10
11
$ python3 scheduler.py 
Scheduled action to run at 2021-12-05 18:37:00.
Scheduled action to run at 2021-12-05 18:37:10.
Scheduled action to run at 2021-12-05 18:37:20.
Eating apple at 2021-12-05 18:37:00.002135.
Eating banana at 2021-12-05 18:37:10.010048.
Eating cherry at 2021-12-05 18:37:20.010034.
Scheduled actions completed.
Scheduled action to run at 2021-12-06 18:37:00.
Scheduled action to run at 2021-12-06 18:37:10.
Scheduled action to run at 2021-12-06 18:37:20.

Final Remarks

Enjoy freeing your hands.

References

Author

Lei Mao

Posted on

03-27-2022

Updated on

03-27-2022

Licensed under


Comments