File: netherlands.py

package info (click to toggle)
python-calendra 7.11.1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,600 kB
  • sloc: python: 16,840; makefile: 6
file content (301 lines) | stat: -rw-r--r-- 9,058 bytes parent folder | download
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
from datetime import date, timedelta
from ..core import WesternCalendar, SUN, ISO_SAT
from ..registry_tools import iso_register


@iso_register('NL')
class Netherlands(WesternCalendar):
    'Netherlands'

    include_good_friday = True
    include_easter_sunday = True
    include_easter_monday = True
    include_ascension = True
    include_whit_sunday = True
    include_whit_monday = True
    include_boxing_day = True

    observance_shift = None

    FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + (
        (5, 5, "Liberation Day"),
    )

    def __init__(self, include_carnival=False):
        self.include_carnival = include_carnival
        super().__init__()

    def get_king_queen_day(self, year):
        """27 April unless this is a Sunday in which case it is the 26th

        Before 2013 it was called Queensday, falling on
        30 April, unless this is a Sunday in which case it is the 29th.
        """
        if year > 2013:
            king_day = date(year, 4, 27)
            if king_day.weekday() != SUN:
                return king_day, "King's day"
            return king_day - timedelta(days=1), "King's day"
        else:
            queen_day = date(year, 4, 30)
            if queen_day.weekday() != SUN:
                return queen_day, "Queen's day"
            return queen_day - timedelta(days=1), "Queen's day"

    def get_carnival_days(self, year):
        """Carnival starts 7 weeks before Easter Sunday and lasts 3 days."""
        n_days = 3
        start = self.get_easter_sunday(year) - timedelta(weeks=7)

        return [
            (
                start + timedelta(days=i), "Carnival"
            ) for i in range(n_days)
        ]

    def get_variable_days(self, year):
        days = super().get_variable_days(year)
        days.append(self.get_king_queen_day(year))
        if self.include_carnival:
            days.extend(self.get_carnival_days(year))
        return days


FALL_HOLIDAYS_EARLY_REGIONS = {
    2025: ["south"],
    2024: ["south"],
    2023: ["middle", "south"],
    2022: ["north"],
    2021: ["north", "middle"],
    2020: ["north"],
    2019: ["south"],
    2018: ["south"],
    2017: ["middle", "south"],
    2016: ["north", "middle"],
}

SPRING_HOLIDAYS_EARLY_REGIONS = {
    2025: ["north"],
    2024: ["south"],
    2023: ["south"],
    2022: ["north"],
    2021: ["south"],
    2020: ["north"],
    2019: ["north"],
    2018: ["south"],
    2017: ["north"],
    2016: ["middle", "south"],
}

SUMMER_HOLIDAYS_EARLY_REGIONS = {
    2025: ["south"],
    2024: ["south"],
    2023: ["middle"],
    2022: ["middle"],
    2021: ["north"],
    2020: ["north"],
    2019: ["south"],
    2018: ["south"],
    2017: ["middle"],
    2016: ["middle"],
}

SUMMER_HOLIDAYS_LATE_REGIONS = {
    2025: ["middle"],
    2024: ["north"],
    2023: ["north"],
    2022: ["south"],
    2021: ["south"],
    2020: ["middle"],
    2019: ["middle"],
    2018: ["north"],
    2017: ["north"],
    2016: ["south"],
}


class NetherlandsWithSchoolHolidays(Netherlands):
    """Netherlands with school holidays (2016 to 2025).

    Data source and regulating body:
    https://www.rijksoverheid.nl/onderwerpen/schoolvakanties/overzicht-schoolvakanties-per-schooljaar
    """

    def __init__(self, region, carnival_instead_of_spring=False, **kwargs):
        """ Set up a calendar incl. school holidays for a specific region

        :param region: either "north", "middle" or "south"
        """
        if region not in ("north", "middle", "south"):
            raise ValueError("Set region to 'north', 'middle' or 'south'.")
        self.region = region
        self.carnival_instead_of_spring = carnival_instead_of_spring
        if carnival_instead_of_spring and "include_carnival" not in kwargs:
            kwargs["include_carnival"] = True
        super().__init__(**kwargs)

    def get_fall_holidays(self, year):
        """
        Return Fall holidays.

        They start at week 43 or 44 and last for 9 days
        """
        if year not in FALL_HOLIDAYS_EARLY_REGIONS:
            raise NotImplementedError(f"Unknown fall holidays for {year}.")

        n_days = 9
        week = 43

        # Exceptional years
        if year == 2024:
            week = 44

        # Holiday starts on the preceding Saturday
        start = self.get_iso_week_date(year, week - 1, ISO_SAT)

        # Some regions have their fall holiday 1 week earlier
        if self.region in FALL_HOLIDAYS_EARLY_REGIONS[year]:
            start = start - timedelta(weeks=1)

        return [
            (start + timedelta(days=i), "Fall holiday") for i in range(n_days)
        ]

    def get_christmas_holidays(self, year):
        """
        Return Christmas holidays

        Christmas holidays run partially in December and partially in January
        (spillover from previous year).
        """
        return (
            self._get_christmas_holidays_december(year) +
            self._get_christmas_holidays_january(year)
        )

    def _get_christmas_holidays_december(self, year):
        # 27 December is always in a full week of holidays
        week = date(year, 12, 27).isocalendar()[1]

        # Holiday starts on the preceding Saturday
        start = self.get_iso_week_date(year, week - 1, ISO_SAT)
        return [
            (start + timedelta(days=i), "Christmas holiday")
            for i in range((date(year, 12, 31) - start).days + 1)
        ]

    def _get_christmas_holidays_january(self, year):
        # 27 December is always in a full week of holidays
        week = date(year - 1, 12, 27).isocalendar()[1]

        # Holiday ends 15 days after the preceding Saturday
        # Saturday of the previous week (previous year!)
        start = self.get_iso_week_date(year - 1, week - 1, ISO_SAT)
        end = start + timedelta(days=15)

        return [
            (date(year, 1, 1) + timedelta(days=i), "Christmas holiday")
            for i in range((end - date(year, 1, 1)).days + 1)
        ]

    def get_spring_holidays(self, year):
        """
        Return the Spring holidays

        They start at week 8 or 9 and last for 9 days.
        """
        if year not in SPRING_HOLIDAYS_EARLY_REGIONS:
            raise NotImplementedError(f"Unknown spring holidays for {year}.")

        n_days = 9
        week = 9

        # Exceptional years
        if year in [2024, 2021]:
            week = 8

        # Holiday starts on the preceding Saturday
        start = self.get_iso_week_date(year, week - 1, ISO_SAT)

        # Some regions have their spring holiday 1 week earlier
        if self.region in SPRING_HOLIDAYS_EARLY_REGIONS[year]:
            start = start - timedelta(weeks=1)

        return [
            (start + timedelta(days=i), "Spring holiday")
            for i in range(n_days)
        ]

    def get_carnival_holidays(self, year):
        """
        Return Carnival holidays

        Carnival holidays start 7 weeks and 1 day before Easter Sunday
        and last 9 days.
        """
        n_days = 9
        start = self.get_easter_sunday(year) - timedelta(weeks=7, days=1)

        return [
            (start + timedelta(days=i), "Carnival holiday")
            for i in range(n_days)
        ]

    def get_may_holidays(self, year):
        """
        Return May holidays

        They start at week 18 (or 17) and last for 18 days
        """
        n_days = 9
        week = 18

        # Exceptional years
        if year == 2017:
            week = 17

        # Holiday starts on the preceding Saturday
        start = self.get_iso_week_date(year, week - 1, ISO_SAT)

        return [
            (start + timedelta(days=i), "May holiday") for i in range(n_days)
        ]

    def get_summer_holidays(self, year):
        """
        Return the summer holidays as a list
        """

        if year not in SUMMER_HOLIDAYS_EARLY_REGIONS or \
                year not in SUMMER_HOLIDAYS_LATE_REGIONS:
            raise NotImplementedError(f"Unknown summer holidays for {year}.")

        n_days = 44
        week = 29

        # Holiday starts on the preceding Saturday
        start = self.get_iso_week_date(year, week - 1, ISO_SAT)

        # Some regions have their summer holiday 1 week earlier
        if self.region in SUMMER_HOLIDAYS_EARLY_REGIONS[year]:
            start = start - timedelta(weeks=1)

        if self.region in SUMMER_HOLIDAYS_LATE_REGIONS[year]:
            start = start + timedelta(weeks=1)

        return [
            (start + timedelta(days=i), "Summer holiday")
            for i in range(n_days)
        ]

    def get_variable_days(self, year):
        days = super().get_variable_days(year)
        days.extend(self.get_fall_holidays(year))
        days.extend(self.get_christmas_holidays(year))
        if self.carnival_instead_of_spring:
            days.extend(self.get_carnival_holidays(year))
        else:
            days.extend(self.get_spring_holidays(year))
        days.extend(self.get_may_holidays(year))
        days.extend(self.get_summer_holidays(year))
        return days