-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgenerate.py
executable file
·168 lines (149 loc) · 5.65 KB
/
generate.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
#!/usr/bin/env python3
#
# Generates ical files for Dunedin City Council recycling collection days.
# Data comes from the "Let's Sort It Out" calendar
# https://www.dunedin.govt.nz/services/rubbish-and-recycling/collection-days
#
# Ian Rees 2021-2024
# python3 -m pip install icalendar
from icalendar import Calendar, Event
from datetime import date, timedelta
from itertools import count
# If a pickup would normally be `key`, do it `value` instead
exceptions = {
date(2021, 4, 2): date(2021, 4, 3),
date(2022, 1, 31): None,
date(2022, 4, 15): date(2022, 4, 16),
date(2023, 4, 7): date(2023, 4, 8),
date(2023, 12, 25): date(2023, 12, 30),
date(2024, 1, 1): date(2024, 1, 6),
date(2024, 3, 29): date(2024, 3, 30),
date(2024, 12, 25): date(2024, 12, 28),
}
# For whatever reason, the collection calendar doesn't start on Jan 1, which
# makes iterating awkward. I'd do this differently if starting now, but this
# list is the date that "week one" starts with yellow bin pickup. Years
# unlike 2021 (when the year started with a complete week) need an exception
# above, so that pickup days have the right phase.
first_day_of_week = {
2021: {
"Monday": date(2021, 2, 1),
"Tuesday": date(2021, 2, 2),
"Wednesday": date(2021, 2, 3),
"Thursday": date(2021, 2, 4),
"Friday": date(2021, 2, 5),
},
2022: {
"Monday": date(2022, 1, 31),
"Tuesday": date(2022, 2, 1),
"Wednesday": date(2022, 2, 2),
"Thursday": date(2022, 2, 3),
"Friday": date(2022, 2, 4),
},
2023: {
"Monday": date(2023, 1, 2),
"Tuesday": date(2023, 2, 3),
"Wednesday": date(2023, 2, 4),
"Thursday": date(2023, 2, 5),
"Friday": date(2023, 2, 6),
},
2024: {
"Monday": date(2024, 1, 1),
"Tuesday": date(2024, 1, 2),
"Wednesday": date(2024, 1, 3),
"Thursday": date(2024, 1, 4),
"Friday": date(2024, 1, 5),
},
# Only define years after the exceptions are known for that year
}
# Stop generation for a particular year after this date
# TODO might be nice to stop generating when the subsequent year starts too -
# currently get duplicates where the two calendars overlap
last_day_of_year = {
2021: date(2022, 1, 31),
2022: date(2023, 1, 31),
2023: date(2023, 12, 30),
2024: date(2024, 12, 31),
}
# After this day, we use red bins for waste every second week
red_bins_start = date(2024, 7, 1)
# DCC talks about "Week One" and "Week Two", instead let's talk about the colour
# bin collected on the first Monday/Tuesday/etc. since they cycle.
colours = ["Yellow", "Blue"]
day_of_week = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
]
def make_event(colour):
event = Event()
if colour == "Yellow":
event.add("summary", "📦 Yellow bin")
event.add("description",
"""YELLOW WEEK: Rinsed rigid plastics
1, 2 and 5 only, tins, cans, and clean
paper and cardboard. No caps, lids,
pumps or trigger sprays.
Place all recycling and DCC black bags
kerbside by 7am on your collection day.""")
elif colour == "Blue":
event.add("summary", "🍾 Blue bin")
event.add("description",
"""BLUE WEEK: Unbroken glass
bottles and jars, with NO lids.
No mirror glass.
Place all recycling and DCC black bags
kerbside by 7am on your collection day.""")
return event
def make_new_event(colour):
event = Event()
if colour == "Yellow":
event.add("summary", "🍂📦 Green and Yellow")
event.add("description",
"""Yellow bin: Rinsed rigid plastics
1, 2 and 5 only, tins, cans, and clean
paper and cardboard. No caps, lids,
pumps or trigger sprays.
Place bins kerbside by 7am on your collection day.""")
elif colour == "Blue":
event.add("summary", "🍂🗑️🍾 Green, Red, and Blue")
event.add("description",
"""Blue Bin: Unbroken glass
bottles and jars, with NO lids.
No mirror glass.
Place bins kerbside by 7am on your collection day.""")
return event
for colour_index in range(len(colours)):
for weekday in day_of_week:
filename = "week-{}-{}.ics".format(colour_index+1, weekday.lower())
with open(filename.encode("UTF-8"), "wb") as file:
cal = Calendar()
cal.add("prodid", "-//Dunedin City Council//Rubbish Collection week {}, {} pickup//EN".format(
colour_index + 1, weekday))
cal.add("version", "2.0")
cal.add("X-WR-TIMEZONE", "Pacific/Auckland")
for year in first_day_of_week:
first = first_day_of_week[year][weekday]
for week in count(): # Not always 52 weeks in a DCC year...
start = first + timedelta(days = 7 * week)
if start in exceptions:
start = exceptions[start]
if start is None:
# This can happen at the start of the year
continue
if start > last_day_of_year[year]:
break # I miss Rust already!
colour = colours[(colour_index + week) % len(colours)]
if start >= red_bins_start:
event = make_new_event(colour)
else:
event = make_event(colour)
event.add("dtstart", start)
event.add("dtend", start + timedelta(days=1))
# The UID needs to be reproducible for updates
uid = "recycling-week{}-{}".format(colour_index+1, start)
event.add("uid", uid)
cal.add_component(event)
file.write(cal.to_ical())