Projet

Général

Profil

0001-add-school-holidays-connector-63013.patch

Valentin Deniaud, 24 mars 2022 17:17

Télécharger (41,9 ko)

Voir les différences:

Subject: [PATCH] add school holidays connector (#63013)

 debian/control                                |   1 +
 passerelle/apps/holidays/__init__.py          |   0
 passerelle/apps/holidays/forms.py             |  32 ++
 .../apps/holidays/migrations/0001_initial.py  |  65 +++
 .../apps/holidays/migrations/__init__.py      |   0
 passerelle/apps/holidays/models.py            | 150 +++++++
 passerelle/settings.py                        |   1 +
 passerelle/urls.py                            |   2 +-
 setup.py                                      |   1 +
 tests/data/holidays/Zone-A.ics                | 416 ++++++++++++++++++
 tests/data/holidays/Zone-B.ics                | 416 ++++++++++++++++++
 tests/test_holidays.py                        | 184 ++++++++
 12 files changed, 1267 insertions(+), 1 deletion(-)
 create mode 100644 passerelle/apps/holidays/__init__.py
 create mode 100644 passerelle/apps/holidays/forms.py
 create mode 100644 passerelle/apps/holidays/migrations/0001_initial.py
 create mode 100644 passerelle/apps/holidays/migrations/__init__.py
 create mode 100644 passerelle/apps/holidays/models.py
 create mode 100644 tests/data/holidays/Zone-A.ics
 create mode 100644 tests/data/holidays/Zone-B.ics
 create mode 100644 tests/test_holidays.py
debian/control
37 37
    python3-feedparser,
38 38
    python3-pdfrw,
39 39
    python3-httplib2,
40
    python3-vobject,
40 41
    python3-xmlschema
41 42
Description: Uniform access to multiple data sources and services (Python module)
42 43

  
passerelle/apps/holidays/forms.py
1
# passerelle - uniform access to multiple data sources and services
2
# Copyright (C) 2022 Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
from django import forms
17
from django.utils.translation import ugettext_lazy as _
18

  
19
from passerelle.forms import GenericConnectorForm
20

  
21

  
22
class HolidaysConnectorForm(GenericConnectorForm):
23
    def __init__(self, *args, **kwargs):
24
        from .models import HOLIDAYS_CHOICES
25

  
26
        super().__init__(*args, **kwargs)
27
        self.fields['holidays'] = forms.MultipleChoiceField(
28
            choices=HOLIDAYS_CHOICES,
29
            widget=forms.CheckboxSelectMultiple,
30
            initial=[x[0] for x in HOLIDAYS_CHOICES],
31
            label=_('Holidays'),
32
        )
passerelle/apps/holidays/migrations/0001_initial.py
1
# Generated by Django 2.2.19 on 2022-03-23 12:09
2

  
3
import django.contrib.postgres.fields
4
from django.db import migrations, models
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    initial = True
10

  
11
    dependencies = [
12
        ('base', '0029_auto_20210202_1627'),
13
    ]
14

  
15
    operations = [
16
        migrations.CreateModel(
17
            name='Holidays',
18
            fields=[
19
                (
20
                    'id',
21
                    models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
22
                ),
23
                ('title', models.CharField(max_length=50, verbose_name='Title')),
24
                ('slug', models.SlugField(unique=True, verbose_name='Identifier')),
25
                ('description', models.TextField(verbose_name='Description')),
26
                (
27
                    'zone',
28
                    models.CharField(
29
                        choices=[('a', 'A'), ('b', 'B'), ('c', 'C')], max_length=16, verbose_name='Zone'
30
                    ),
31
                ),
32
                (
33
                    'holidays',
34
                    django.contrib.postgres.fields.ArrayField(
35
                        base_field=models.CharField(
36
                            choices=[
37
                                ('winter_holidays', 'Vacances d’Hiver'),
38
                                ('spring_holidays', 'Vacances de Pâques'),
39
                                ('summer_holidays', 'Vacances d’Été'),
40
                                ('all_saints_holidays', 'Vacances de la Toussaint'),
41
                                ('christmas_holidays', 'Vacances de Noël'),
42
                                ('fathers_day', 'Fête des Pères'),
43
                                ('mothers_day', 'Fête des Mères'),
44
                            ],
45
                            max_length=32,
46
                        ),
47
                        size=None,
48
                        verbose_name='Holidays',
49
                    ),
50
                ),
51
                (
52
                    'users',
53
                    models.ManyToManyField(
54
                        blank=True,
55
                        related_name='_holidays_users_+',
56
                        related_query_name='+',
57
                        to='base.ApiUser',
58
                    ),
59
                ),
60
            ],
61
            options={
62
                'verbose_name': 'School holidays',
63
            },
64
        ),
65
    ]
passerelle/apps/holidays/models.py
1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2022  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import datetime
18

  
19
import requests
20
import vobject
21
from dateutil import easter
22
from dateutil.relativedelta import SU, relativedelta
23
from django.contrib.postgres.fields import ArrayField
24
from django.db import models
25
from django.http import HttpResponse
26
from django.utils.translation import ugettext_lazy as _
27

  
28
from passerelle.base.models import BaseResource
29
from passerelle.utils.api import endpoint
30
from passerelle.utils.jsonresponse import APIError
31

  
32
from .forms import HolidaysConnectorForm
33

  
34
ZONE_CHOICES = (
35
    ('a', 'A'),
36
    ('b', 'B'),
37
    ('c', 'C'),
38
)
39

  
40
HOLIDAYS_LABELS = {
41
    'winter_holidays': "Vacances d’Hiver",
42
    'spring_holidays': 'Vacances de Pâques',
43
    'summer_holidays': "Vacances d’Été",
44
    'all_saints_holidays': 'Vacances de la Toussaint',
45
    'christmas_holidays': 'Vacances de Noël',
46
    'fathers_day': 'Fête des Pères',
47
    'mothers_day': 'Fête des Mères',
48
}
49

  
50

  
51
HOLIDAYS_CHOICES = [(x[0], x[1]) for x in HOLIDAYS_LABELS.items()]
52

  
53

  
54
ZONE_URLS = {
55
    'a': 'https://fr.ftp.opendatasoft.com/openscol/fr-en-calendrier-scolaire/Zone-A.ics',
56
    'b': 'https://fr.ftp.opendatasoft.com/openscol/fr-en-calendrier-scolaire/Zone-B.ics',
57
    'c': 'https://fr.ftp.opendatasoft.com/openscol/fr-en-calendrier-scolaire/Zone-C.ics',
58
}
59

  
60

  
61
HOLIDAYS_MAPPING = {
62
    "Vacances d'Hiver": 'winter_holidays',
63
    'Vacances de Printemps': 'spring_holidays',
64
    "Vacances d'Été (Élèves)": 'summer_holidays',
65
    "Vacances d'Été": 'summer_holidays',
66
    'Vacances de la Toussaint': 'all_saints_holidays',
67
    'Vacances de Noël': 'christmas_holidays',
68
}
69

  
70

  
71
class Holidays(BaseResource):
72
    zone = models.CharField(_('Zone'), max_length=16, choices=ZONE_CHOICES)
73
    holidays = ArrayField(
74
        models.CharField(max_length=32, choices=HOLIDAYS_CHOICES),
75
        verbose_name=_('Holidays'),
76
        blank=False,
77
        null=False,
78
    )
79

  
80
    category = _('Misc')
81
    manager_form_base_class = HolidaysConnectorForm
82

  
83
    class Meta:
84
        verbose_name = _('School holidays')
85

  
86
    def get_holidays_display(self):
87
        return ', '.join(HOLIDAYS_LABELS[holiday] for holiday in self.holidays)
88

  
89
    @endpoint(name='holidays.ics', description=_('Get holidays ICS.'))
90
    def holidays_ics(self, request):
91
        try:
92
            response = requests.get(ZONE_URLS[self.zone])
93
            response.raise_for_status()
94
        except requests.exceptions.RequestException as e:
95
            raise APIError('Error while getting ICS file: %s' % e)
96

  
97
        try:
98
            parsed = vobject.readOne(response.text)
99
        except vobject.base.ParseError as e:
100
            raise APIError('Invalid ICS file: %s' % e)
101

  
102
        if not parsed.contents.get('vevent'):
103
            raise APIError('ICS file does not contain events.')
104

  
105
        cal = vobject.iCalendar()
106
        seen_years = set()
107
        for vevent in parsed.contents.get('vevent'):
108
            description = vevent.contents['description'][0].value
109

  
110
            holiday_id = HOLIDAYS_MAPPING.get(description)
111
            if holiday_id not in self.holidays:
112
                continue
113

  
114
            self.add_vevent(cal, holiday_id, vevent.dtstart.value, vevent.dtend.value)
115

  
116
            seen_years.add(vevent.dtstart.value.year)
117

  
118
        special_holidays = {'mothers_day', 'fathers_day'}.intersection(self.holidays)
119
        for holiday_id in special_holidays:
120
            for year in seen_years:
121
                dtstart = getattr(self, 'get_%s' % holiday_id)(year)
122
                self.add_vevent(cal, holiday_id, dtstart, dtstart + datetime.timedelta(days=1))
123

  
124
        return HttpResponse(cal.serialize(), content_type='text/calendar')
125

  
126
    @staticmethod
127
    def add_vevent(cal, holiday_id, dtstart, dtend):
128
        vevent = cal.add('vevent')
129
        vevent.add('summary').value = HOLIDAYS_LABELS[holiday_id]
130
        vevent.add('uid').value = '%s-%s' % (holiday_id, dtstart.year)
131
        vevent.add('categories').value = [holiday_id]
132
        vevent.add('dtstart').value = dtstart
133
        vevent.add('dtend').value = dtend
134

  
135
    @staticmethod
136
    def get_mothers_day(year):
137
        first_of_january = datetime.date(year, 1, 1)
138
        mothers_day = first_of_january + relativedelta(month=5, day=31, weekday=SU(-1))
139

  
140
        easter_sunday = easter.easter(year)
141
        pentecost_sunday = easter_sunday + datetime.timedelta(days=49)
142
        if mothers_day == pentecost_sunday:
143
            mothers_day += datetime.timedelta(days=7)
144

  
145
        return mothers_day
146

  
147
    @staticmethod
148
    def get_fathers_day(year):
149
        first_of_january = datetime.date(year, 1, 1)
150
        return first_of_january + relativedelta(month=6, day=1, weekday=SU(3))
passerelle/settings.py
146 146
    'passerelle.apps.franceconnect_data',
147 147
    'passerelle.apps.gdc',
148 148
    'passerelle.apps.gesbac',
149
    'passerelle.apps.holidays',
149 150
    'passerelle.apps.jsondatastore',
150 151
    'passerelle.apps.maelis',
151 152
    'passerelle.apps.mdel',
passerelle/urls.py
125 125
        r'^(?P<connector>[\w,-]+)/(?P<slug>[\w,-]+)/$', GenericConnectorView.as_view(), name='view-connector'
126 126
    ),
127 127
    url(
128
        r'^(?P<connector>[\w,-]+)/(?P<slug>[\w,-]+)/(?P<endpoint>[\w,-]+)(?:/(?P<rest>.*))?$',
128
        r'^(?P<connector>[\w,-]+)/(?P<slug>[\w,-]+)/(?P<endpoint>[\w,-.]+)(?:/(?P<rest>.*))?$',
129 129
        GenericEndpointView.as_view(),
130 130
        name='generic-endpoint',
131 131
    ),
setup.py
125 125
        'httplib2',
126 126
        'xmlschema',
127 127
        'pytz',
128
        'vobject',
128 129
    ],
129 130
    cmdclass={
130 131
        'build': build,
tests/data/holidays/Zone-A.ics
1
BEGIN:VCALENDAR
2
PRODID:-//MENJS//Calendrier//FR
3
VERSION:2.0
4
CALSCALE:GREGORIAN
5
X-WR-CALNAME:Calendrier scolaire - Zone A
6
X-WR-TIMEZONE:Europe/Paris
7
X-WR-CALDESC:Congés scolaires de la zone A depuis l'année 2017-2018
8
BEGIN:VTIMEZONE
9
TZID:Europe/Paris
10
X-LIC-LOCATION:Europe/Paris
11
BEGIN:DAYLIGHT
12
DTSTART:19700329T020000
13
TZNAME:CEST
14
TZOFFSETFROM:+0100
15
TZOFFSETTO:+0200
16
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
17
TZNAME:CET
18
END:DAYLIGHT
19
BEGIN:STANDARD
20
DTSTART:19701025T030000
21
TZOFFSETFROM:+0200
22
TZOFFSETTO:+0100
23
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
24
END:STANDARD
25
END:VTIMEZONE
26
BEGIN:VEVENT
27
DTSTAMP:20220301T030012Z
28
DTSTART;VALUE=DATE:20171021
29
DTEND;VALUE=DATE:20171106
30
SUMMARY:Vacances de la Toussaint
31
UID:20220301T030012Z-Zone-A@data.education.gouv.fr
32
LOCATION:
33
DESCRIPTION:Vacances de la Toussaint
34
TRANSP:TRANSPARENT
35
END:VEVENT
36
BEGIN:VEVENT
37
DTSTAMP:20220301T030012Z
38
DTSTART;VALUE=DATE:20171223
39
DTEND;VALUE=DATE:20180108
40
SUMMARY:Vacances de Noël
41
UID:20220301T030013Z-Zone-A@data.education.gouv.fr
42
LOCATION:
43
DESCRIPTION:Vacances de Noël
44
TRANSP:TRANSPARENT
45
END:VEVENT
46
BEGIN:VEVENT
47
DTSTAMP:20220301T030012Z
48
DTSTART;VALUE=DATE:20180210
49
DTEND;VALUE=DATE:20180226
50
SUMMARY:Vacances d'Hiver
51
UID:20220301T030014Z-Zone-A@data.education.gouv.fr
52
LOCATION:
53
DESCRIPTION:Vacances d'Hiver
54
TRANSP:TRANSPARENT
55
END:VEVENT
56
BEGIN:VEVENT
57
DTSTAMP:20220301T030012Z
58
DTSTART;VALUE=DATE:20180407
59
DTEND;VALUE=DATE:20180423
60
SUMMARY:Vacances de Printemps
61
UID:20220301T030015Z-Zone-A@data.education.gouv.fr
62
LOCATION:
63
DESCRIPTION:Vacances de Printemps
64
TRANSP:TRANSPARENT
65
END:VEVENT
66
BEGIN:VEVENT
67
DTSTAMP:20220301T030012Z
68
DTSTART;VALUE=DATE:20180707
69
DTEND;VALUE=DATE:20180831
70
SUMMARY:Vacances d'Été
71
UID:20220301T030016Z-Zone-A@data.education.gouv.fr
72
LOCATION:
73
DESCRIPTION:Vacances d'Été (Enseignants)
74
TRANSP:TRANSPARENT
75
END:VEVENT
76
BEGIN:VEVENT
77
DTSTAMP:20220301T030012Z
78
DTSTART;VALUE=DATE:20180707
79
DTEND;VALUE=DATE:20180903
80
SUMMARY:Vacances d'Été
81
UID:20220301T030017Z-Zone-A@data.education.gouv.fr
82
LOCATION:
83
DESCRIPTION:Vacances d'Été (Élèves)
84
TRANSP:TRANSPARENT
85
END:VEVENT
86
BEGIN:VEVENT
87
DTSTAMP:20220301T030012Z
88
DTSTART;VALUE=DATE:20181020
89
DTEND;VALUE=DATE:20181105
90
SUMMARY:Vacances de la Toussaint
91
UID:20220301T030018Z-Zone-A@data.education.gouv.fr
92
LOCATION:
93
DESCRIPTION:Vacances de la Toussaint
94
TRANSP:TRANSPARENT
95
END:VEVENT
96
BEGIN:VEVENT
97
DTSTAMP:20220301T030012Z
98
DTSTART;VALUE=DATE:20181222
99
DTEND;VALUE=DATE:20190107
100
SUMMARY:Vacances de Noël
101
UID:20220301T030019Z-Zone-A@data.education.gouv.fr
102
LOCATION:
103
DESCRIPTION:Vacances de Noël
104
TRANSP:TRANSPARENT
105
END:VEVENT
106
BEGIN:VEVENT
107
DTSTAMP:20220301T030012Z
108
DTSTART;VALUE=DATE:20190216
109
DTEND;VALUE=DATE:20190304
110
SUMMARY:Vacances d'Hiver
111
UID:20220301T030020Z-Zone-A@data.education.gouv.fr
112
LOCATION:
113
DESCRIPTION:Vacances d'Hiver
114
TRANSP:TRANSPARENT
115
END:VEVENT
116
BEGIN:VEVENT
117
DTSTAMP:20220301T030012Z
118
DTSTART;VALUE=DATE:20190413
119
DTEND;VALUE=DATE:20190429
120
SUMMARY:Vacances de Printemps
121
UID:20220301T030021Z-Zone-A@data.education.gouv.fr
122
LOCATION:
123
DESCRIPTION:Vacances de Printemps
124
TRANSP:TRANSPARENT
125
END:VEVENT
126
BEGIN:VEVENT
127
DTSTAMP:20220301T030012Z
128
DTSTART;VALUE=DATE:20190529
129
DTEND;VALUE=DATE:20190603
130
SUMMARY:Pont de l'Ascension
131
UID:20220301T030022Z-Zone-A@data.education.gouv.fr
132
LOCATION:
133
DESCRIPTION:Pont de l'Ascension
134
TRANSP:TRANSPARENT
135
END:VEVENT
136
BEGIN:VEVENT
137
DTSTAMP:20220301T030012Z
138
DTSTART;VALUE=DATE:20190706
139
DTEND;VALUE=DATE:20190830
140
SUMMARY:Vacances d'Été
141
UID:20220301T030023Z-Zone-A@data.education.gouv.fr
142
LOCATION:
143
DESCRIPTION:Vacances d'Été (Enseignants)
144
TRANSP:TRANSPARENT
145
END:VEVENT
146
BEGIN:VEVENT
147
DTSTAMP:20220301T030012Z
148
DTSTART;VALUE=DATE:20190706
149
DTEND;VALUE=DATE:20190902
150
SUMMARY:Vacances d'Été
151
UID:20220301T030024Z-Zone-A@data.education.gouv.fr
152
LOCATION:
153
DESCRIPTION:Vacances d'Été (Élèves)
154
TRANSP:TRANSPARENT
155
END:VEVENT
156
BEGIN:VEVENT
157
DTSTAMP:20220301T030012Z
158
DTSTART;VALUE=DATE:20191019
159
DTEND;VALUE=DATE:20191104
160
SUMMARY:Vacances de la Toussaint
161
UID:20220301T030025Z-Zone-A@data.education.gouv.fr
162
LOCATION:
163
DESCRIPTION:Vacances de la Toussaint
164
TRANSP:TRANSPARENT
165
END:VEVENT
166
BEGIN:VEVENT
167
DTSTAMP:20220301T030012Z
168
DTSTART;VALUE=DATE:20191221
169
DTEND;VALUE=DATE:20200106
170
SUMMARY:Vacances de Noël
171
UID:20220301T030026Z-Zone-A@data.education.gouv.fr
172
LOCATION:
173
DESCRIPTION:Vacances de Noël
174
TRANSP:TRANSPARENT
175
END:VEVENT
176
BEGIN:VEVENT
177
DTSTAMP:20220301T030012Z
178
DTSTART;VALUE=DATE:20200222
179
DTEND;VALUE=DATE:20200309
180
SUMMARY:Vacances d'Hiver
181
UID:20220301T030027Z-Zone-A@data.education.gouv.fr
182
LOCATION:
183
DESCRIPTION:Vacances d'Hiver
184
TRANSP:TRANSPARENT
185
END:VEVENT
186
BEGIN:VEVENT
187
DTSTAMP:20220301T030012Z
188
DTSTART;VALUE=DATE:20200418
189
DTEND;VALUE=DATE:20200504
190
SUMMARY:Vacances de Printemps
191
UID:20220301T030028Z-Zone-A@data.education.gouv.fr
192
LOCATION:
193
DESCRIPTION:Vacances de Printemps
194
TRANSP:TRANSPARENT
195
END:VEVENT
196
BEGIN:VEVENT
197
DTSTAMP:20220301T030012Z
198
DTSTART;VALUE=DATE:20200520
199
DTEND;VALUE=DATE:20200525
200
SUMMARY:Pont de l'Ascension
201
UID:20220301T030029Z-Zone-A@data.education.gouv.fr
202
LOCATION:
203
DESCRIPTION:Pont de l'Ascension
204
TRANSP:TRANSPARENT
205
END:VEVENT
206
BEGIN:VEVENT
207
DTSTAMP:20220301T030012Z
208
DTSTART;VALUE=DATE:20200704
209
DTEND;VALUE=DATE:20200901
210
SUMMARY:Vacances d'Été
211
UID:20220301T030030Z-Zone-A@data.education.gouv.fr
212
LOCATION:
213
DESCRIPTION:Vacances d'Été
214
TRANSP:TRANSPARENT
215
END:VEVENT
216
BEGIN:VEVENT
217
DTSTAMP:20220301T030012Z
218
DTSTART;VALUE=DATE:20201017
219
DTEND;VALUE=DATE:20201102
220
SUMMARY:Vacances de la Toussaint
221
UID:20220301T030031Z-Zone-A@data.education.gouv.fr
222
LOCATION:
223
DESCRIPTION:Vacances de la Toussaint
224
TRANSP:TRANSPARENT
225
END:VEVENT
226
BEGIN:VEVENT
227
DTSTAMP:20220301T030012Z
228
DTSTART;VALUE=DATE:20201219
229
DTEND;VALUE=DATE:20210104
230
SUMMARY:Vacances de Noël
231
UID:20220301T030032Z-Zone-A@data.education.gouv.fr
232
LOCATION:
233
DESCRIPTION:Vacances de Noël
234
TRANSP:TRANSPARENT
235
END:VEVENT
236
BEGIN:VEVENT
237
DTSTAMP:20220301T030012Z
238
DTSTART;VALUE=DATE:20210206
239
DTEND;VALUE=DATE:20210222
240
SUMMARY:Vacances d'Hiver
241
UID:20220301T030033Z-Zone-A@data.education.gouv.fr
242
LOCATION:
243
DESCRIPTION:Vacances d'Hiver
244
TRANSP:TRANSPARENT
245
END:VEVENT
246
BEGIN:VEVENT
247
DTSTAMP:20220301T030012Z
248
DTSTART;VALUE=DATE:20210410
249
DTEND;VALUE=DATE:20210426
250
SUMMARY:Vacances de Printemps
251
UID:20220301T030034Z-Zone-A@data.education.gouv.fr
252
LOCATION:
253
DESCRIPTION:Vacances de Printemps
254
TRANSP:TRANSPARENT
255
END:VEVENT
256
BEGIN:VEVENT
257
DTSTAMP:20220301T030012Z
258
DTSTART;VALUE=DATE:20210513
259
DTEND;VALUE=DATE:20210517
260
SUMMARY:Pont de l'Ascension
261
UID:20220301T030035Z-Zone-A@data.education.gouv.fr
262
LOCATION:
263
DESCRIPTION:Pont de l'Ascension
264
TRANSP:TRANSPARENT
265
END:VEVENT
266
BEGIN:VEVENT
267
DTSTAMP:20220301T030012Z
268
DTSTART;VALUE=DATE:20210706
269
DTEND;VALUE=DATE:20210901
270
SUMMARY:Vacances d'Été
271
UID:20220301T030036Z-Zone-A@data.education.gouv.fr
272
LOCATION:
273
DESCRIPTION:Vacances d'Été (Enseignants)
274
TRANSP:TRANSPARENT
275
END:VEVENT
276
BEGIN:VEVENT
277
DTSTAMP:20220301T030012Z
278
DTSTART;VALUE=DATE:20210706
279
DTEND;VALUE=DATE:20210902
280
SUMMARY:Vacances d'Été
281
UID:20220301T030037Z-Zone-A@data.education.gouv.fr
282
LOCATION:
283
DESCRIPTION:Vacances d'Été (Élèves)
284
TRANSP:TRANSPARENT
285
END:VEVENT
286
BEGIN:VEVENT
287
DTSTAMP:20220301T030012Z
288
DTSTART;VALUE=DATE:20211023
289
DTEND;VALUE=DATE:20211108
290
SUMMARY:Vacances de la Toussaint
291
UID:20220301T030038Z-Zone-A@data.education.gouv.fr
292
LOCATION:
293
DESCRIPTION:Vacances de la Toussaint
294
TRANSP:TRANSPARENT
295
END:VEVENT
296
BEGIN:VEVENT
297
DTSTAMP:20220301T030012Z
298
DTSTART;VALUE=DATE:20211218
299
DTEND;VALUE=DATE:20220103
300
SUMMARY:Vacances de Noël
301
UID:20220301T030039Z-Zone-A@data.education.gouv.fr
302
LOCATION:
303
DESCRIPTION:Vacances de Noël
304
TRANSP:TRANSPARENT
305
END:VEVENT
306
BEGIN:VEVENT
307
DTSTAMP:20220301T030012Z
308
DTSTART;VALUE=DATE:20220212
309
DTEND;VALUE=DATE:20220228
310
SUMMARY:Vacances d'Hiver
311
UID:20220301T030040Z-Zone-A@data.education.gouv.fr
312
LOCATION:
313
DESCRIPTION:Vacances d'Hiver
314
TRANSP:TRANSPARENT
315
END:VEVENT
316
BEGIN:VEVENT
317
DTSTAMP:20220301T030012Z
318
DTSTART;VALUE=DATE:20220416
319
DTEND;VALUE=DATE:20220502
320
SUMMARY:Vacances de Printemps
321
UID:20220301T030041Z-Zone-A@data.education.gouv.fr
322
LOCATION:
323
DESCRIPTION:Vacances de Printemps
324
TRANSP:TRANSPARENT
325
END:VEVENT
326
BEGIN:VEVENT
327
DTSTAMP:20220301T030012Z
328
DTSTART;VALUE=DATE:20220526
329
DTEND;VALUE=DATE:20220528
330
SUMMARY:Pont de l'Ascension
331
UID:20220301T030042Z-Zone-A@data.education.gouv.fr
332
LOCATION:
333
DESCRIPTION:Pont de l'Ascension
334
TRANSP:TRANSPARENT
335
END:VEVENT
336
BEGIN:VEVENT
337
DTSTAMP:20220301T030012Z
338
DTSTART;VALUE=DATE:20220707
339
DTEND;VALUE=DATE:20220831
340
SUMMARY:Vacances d'Été
341
UID:20220301T030043Z-Zone-A@data.education.gouv.fr
342
LOCATION:
343
DESCRIPTION:Vacances d'Été (Enseignants)
344
TRANSP:TRANSPARENT
345
END:VEVENT
346
BEGIN:VEVENT
347
DTSTAMP:20220301T030012Z
348
DTSTART;VALUE=DATE:20220707
349
DTEND;VALUE=DATE:20220901
350
SUMMARY:Vacances d'Été
351
UID:20220301T030044Z-Zone-A@data.education.gouv.fr
352
LOCATION:
353
DESCRIPTION:Vacances d'Été (Élèves)
354
TRANSP:TRANSPARENT
355
END:VEVENT
356
BEGIN:VEVENT
357
DTSTAMP:20220301T030012Z
358
DTSTART;VALUE=DATE:20221022
359
DTEND;VALUE=DATE:20221107
360
SUMMARY:Vacances de la Toussaint
361
UID:20220301T030045Z-Zone-A@data.education.gouv.fr
362
LOCATION:
363
DESCRIPTION:Vacances de la Toussaint
364
TRANSP:TRANSPARENT
365
END:VEVENT
366
BEGIN:VEVENT
367
DTSTAMP:20220301T030012Z
368
DTSTART;VALUE=DATE:20221217
369
DTEND;VALUE=DATE:20230103
370
SUMMARY:Vacances de Noël
371
UID:20220301T030046Z-Zone-A@data.education.gouv.fr
372
LOCATION:
373
DESCRIPTION:Vacances de Noël
374
TRANSP:TRANSPARENT
375
END:VEVENT
376
BEGIN:VEVENT
377
DTSTAMP:20220301T030012Z
378
DTSTART;VALUE=DATE:20230204
379
DTEND;VALUE=DATE:20230220
380
SUMMARY:Vacances d'Hiver
381
UID:20220301T030047Z-Zone-A@data.education.gouv.fr
382
LOCATION:
383
DESCRIPTION:Vacances d'Hiver
384
TRANSP:TRANSPARENT
385
END:VEVENT
386
BEGIN:VEVENT
387
DTSTAMP:20220301T030012Z
388
DTSTART;VALUE=DATE:20230408
389
DTEND;VALUE=DATE:20230424
390
SUMMARY:Vacances de Printemps
391
UID:20220301T030048Z-Zone-A@data.education.gouv.fr
392
LOCATION:
393
DESCRIPTION:Vacances de Printemps
394
TRANSP:TRANSPARENT
395
END:VEVENT
396
BEGIN:VEVENT
397
DTSTAMP:20220301T030012Z
398
DTSTART;VALUE=DATE:20230518
399
DTEND;VALUE=DATE:20230522
400
SUMMARY:Pont de l'Ascension
401
UID:20220301T030049Z-Zone-A@data.education.gouv.fr
402
LOCATION:
403
DESCRIPTION:Pont de l'Ascension
404
TRANSP:TRANSPARENT
405
END:VEVENT
406
BEGIN:VEVENT
407
DTSTAMP:20220301T030012Z
408
DTSTART;VALUE=DATE:20230708
409
DTEND;VALUE=DATE:20230708
410
SUMMARY:Début des Vacances d'Été
411
UID:20220301T030050Z-Zone-A@data.education.gouv.fr
412
LOCATION:
413
DESCRIPTION:Début des Vacances d'Été
414
TRANSP:TRANSPARENT
415
END:VEVENT
416
END:VCALENDAR
tests/data/holidays/Zone-B.ics
1
BEGIN:VCALENDAR
2
PRODID:-//MENJS//Calendrier//FR
3
VERSION:2.0
4
CALSCALE:GREGORIAN
5
X-WR-CALNAME:Calendrier scolaire - Zone B
6
X-WR-TIMEZONE:Europe/Paris
7
X-WR-CALDESC:Congés scolaires de la zone B depuis l'année 2017-2018
8
BEGIN:VTIMEZONE
9
TZID:Europe/Paris
10
X-LIC-LOCATION:Europe/Paris
11
BEGIN:DAYLIGHT
12
DTSTART:19700329T020000
13
TZNAME:CEST
14
TZOFFSETFROM:+0100
15
TZOFFSETTO:+0200
16
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=-1SU
17
TZNAME:CET
18
END:DAYLIGHT
19
BEGIN:STANDARD
20
DTSTART:19701025T030000
21
TZOFFSETFROM:+0200
22
TZOFFSETTO:+0100
23
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
24
END:STANDARD
25
END:VTIMEZONE
26
BEGIN:VEVENT
27
DTSTAMP:20220301T030012Z
28
DTSTART;VALUE=DATE:20171021
29
DTEND;VALUE=DATE:20171106
30
SUMMARY:Vacances de la Toussaint
31
UID:20220301T030051Z-Zone-B@data.education.gouv.fr
32
LOCATION:
33
DESCRIPTION:Vacances de la Toussaint
34
TRANSP:TRANSPARENT
35
END:VEVENT
36
BEGIN:VEVENT
37
DTSTAMP:20220301T030012Z
38
DTSTART;VALUE=DATE:20171223
39
DTEND;VALUE=DATE:20180108
40
SUMMARY:Vacances de Noël
41
UID:20220301T030052Z-Zone-B@data.education.gouv.fr
42
LOCATION:
43
DESCRIPTION:Vacances de Noël
44
TRANSP:TRANSPARENT
45
END:VEVENT
46
BEGIN:VEVENT
47
DTSTAMP:20220301T030012Z
48
DTSTART;VALUE=DATE:20180224
49
DTEND;VALUE=DATE:20180312
50
SUMMARY:Vacances d'Hiver
51
UID:20220301T030053Z-Zone-B@data.education.gouv.fr
52
LOCATION:
53
DESCRIPTION:Vacances d'Hiver
54
TRANSP:TRANSPARENT
55
END:VEVENT
56
BEGIN:VEVENT
57
DTSTAMP:20220301T030012Z
58
DTSTART;VALUE=DATE:20180421
59
DTEND;VALUE=DATE:20180507
60
SUMMARY:Vacances de Printemps
61
UID:20220301T030054Z-Zone-B@data.education.gouv.fr
62
LOCATION:
63
DESCRIPTION:Vacances de Printemps
64
TRANSP:TRANSPARENT
65
END:VEVENT
66
BEGIN:VEVENT
67
DTSTAMP:20220301T030012Z
68
DTSTART;VALUE=DATE:20180707
69
DTEND;VALUE=DATE:20180831
70
SUMMARY:Vacances d'Été
71
UID:20220301T030055Z-Zone-B@data.education.gouv.fr
72
LOCATION:
73
DESCRIPTION:Vacances d'Été (Enseignants)
74
TRANSP:TRANSPARENT
75
END:VEVENT
76
BEGIN:VEVENT
77
DTSTAMP:20220301T030012Z
78
DTSTART;VALUE=DATE:20180707
79
DTEND;VALUE=DATE:20180903
80
SUMMARY:Vacances d'Été
81
UID:20220301T030056Z-Zone-B@data.education.gouv.fr
82
LOCATION:
83
DESCRIPTION:Vacances d'Été (Élèves)
84
TRANSP:TRANSPARENT
85
END:VEVENT
86
BEGIN:VEVENT
87
DTSTAMP:20220301T030012Z
88
DTSTART;VALUE=DATE:20181020
89
DTEND;VALUE=DATE:20181105
90
SUMMARY:Vacances de la Toussaint
91
UID:20220301T030057Z-Zone-B@data.education.gouv.fr
92
LOCATION:
93
DESCRIPTION:Vacances de la Toussaint
94
TRANSP:TRANSPARENT
95
END:VEVENT
96
BEGIN:VEVENT
97
DTSTAMP:20220301T030012Z
98
DTSTART;VALUE=DATE:20181222
99
DTEND;VALUE=DATE:20190107
100
SUMMARY:Vacances de Noël
101
UID:20220301T030058Z-Zone-B@data.education.gouv.fr
102
LOCATION:
103
DESCRIPTION:Vacances de Noël
104
TRANSP:TRANSPARENT
105
END:VEVENT
106
BEGIN:VEVENT
107
DTSTAMP:20220301T030012Z
108
DTSTART;VALUE=DATE:20190209
109
DTEND;VALUE=DATE:20190225
110
SUMMARY:Vacances d'Hiver
111
UID:20220301T030059Z-Zone-B@data.education.gouv.fr
112
LOCATION:
113
DESCRIPTION:Vacances d'Hiver
114
TRANSP:TRANSPARENT
115
END:VEVENT
116
BEGIN:VEVENT
117
DTSTAMP:20220301T030012Z
118
DTSTART;VALUE=DATE:20190406
119
DTEND;VALUE=DATE:20190423
120
SUMMARY:Vacances de Printemps
121
UID:20220301T030100Z-Zone-B@data.education.gouv.fr
122
LOCATION:
123
DESCRIPTION:Vacances de Printemps
124
TRANSP:TRANSPARENT
125
END:VEVENT
126
BEGIN:VEVENT
127
DTSTAMP:20220301T030012Z
128
DTSTART;VALUE=DATE:20190529
129
DTEND;VALUE=DATE:20190603
130
SUMMARY:Pont de l'Ascension
131
UID:20220301T030101Z-Zone-B@data.education.gouv.fr
132
LOCATION:
133
DESCRIPTION:Pont de l'Ascension
134
TRANSP:TRANSPARENT
135
END:VEVENT
136
BEGIN:VEVENT
137
DTSTAMP:20220301T030012Z
138
DTSTART;VALUE=DATE:20190706
139
DTEND;VALUE=DATE:20190830
140
SUMMARY:Vacances d'Été
141
UID:20220301T030102Z-Zone-B@data.education.gouv.fr
142
LOCATION:
143
DESCRIPTION:Vacances d'Été (Enseignants)
144
TRANSP:TRANSPARENT
145
END:VEVENT
146
BEGIN:VEVENT
147
DTSTAMP:20220301T030012Z
148
DTSTART;VALUE=DATE:20190706
149
DTEND;VALUE=DATE:20190902
150
SUMMARY:Vacances d'Été
151
UID:20220301T030103Z-Zone-B@data.education.gouv.fr
152
LOCATION:
153
DESCRIPTION:Vacances d'Été (Élèves)
154
TRANSP:TRANSPARENT
155
END:VEVENT
156
BEGIN:VEVENT
157
DTSTAMP:20220301T030012Z
158
DTSTART;VALUE=DATE:20191019
159
DTEND;VALUE=DATE:20191104
160
SUMMARY:Vacances de la Toussaint
161
UID:20220301T030104Z-Zone-B@data.education.gouv.fr
162
LOCATION:
163
DESCRIPTION:Vacances de la Toussaint
164
TRANSP:TRANSPARENT
165
END:VEVENT
166
BEGIN:VEVENT
167
DTSTAMP:20220301T030012Z
168
DTSTART;VALUE=DATE:20191221
169
DTEND;VALUE=DATE:20200106
170
SUMMARY:Vacances de Noël
171
UID:20220301T030105Z-Zone-B@data.education.gouv.fr
172
LOCATION:
173
DESCRIPTION:Vacances de Noël
174
TRANSP:TRANSPARENT
175
END:VEVENT
176
BEGIN:VEVENT
177
DTSTAMP:20220301T030012Z
178
DTSTART;VALUE=DATE:20200215
179
DTEND;VALUE=DATE:20200302
180
SUMMARY:Vacances d'Hiver
181
UID:20220301T030106Z-Zone-B@data.education.gouv.fr
182
LOCATION:
183
DESCRIPTION:Vacances d'Hiver
184
TRANSP:TRANSPARENT
185
END:VEVENT
186
BEGIN:VEVENT
187
DTSTAMP:20220301T030012Z
188
DTSTART;VALUE=DATE:20200411
189
DTEND;VALUE=DATE:20200427
190
SUMMARY:Vacances de Printemps
191
UID:20220301T030107Z-Zone-B@data.education.gouv.fr
192
LOCATION:
193
DESCRIPTION:Vacances de Printemps
194
TRANSP:TRANSPARENT
195
END:VEVENT
196
BEGIN:VEVENT
197
DTSTAMP:20220301T030012Z
198
DTSTART;VALUE=DATE:20200520
199
DTEND;VALUE=DATE:20200525
200
SUMMARY:Pont de l'Ascension
201
UID:20220301T030108Z-Zone-B@data.education.gouv.fr
202
LOCATION:
203
DESCRIPTION:Pont de l'Ascension
204
TRANSP:TRANSPARENT
205
END:VEVENT
206
BEGIN:VEVENT
207
DTSTAMP:20220301T030012Z
208
DTSTART;VALUE=DATE:20200704
209
DTEND;VALUE=DATE:20200901
210
SUMMARY:Vacances d'Été
211
UID:20220301T030109Z-Zone-B@data.education.gouv.fr
212
LOCATION:
213
DESCRIPTION:Vacances d'Été
214
TRANSP:TRANSPARENT
215
END:VEVENT
216
BEGIN:VEVENT
217
DTSTAMP:20220301T030012Z
218
DTSTART;VALUE=DATE:20201017
219
DTEND;VALUE=DATE:20201102
220
SUMMARY:Vacances de la Toussaint
221
UID:20220301T030110Z-Zone-B@data.education.gouv.fr
222
LOCATION:
223
DESCRIPTION:Vacances de la Toussaint
224
TRANSP:TRANSPARENT
225
END:VEVENT
226
BEGIN:VEVENT
227
DTSTAMP:20220301T030012Z
228
DTSTART;VALUE=DATE:20201219
229
DTEND;VALUE=DATE:20210104
230
SUMMARY:Vacances de Noël
231
UID:20220301T030111Z-Zone-B@data.education.gouv.fr
232
LOCATION:
233
DESCRIPTION:Vacances de Noël
234
TRANSP:TRANSPARENT
235
END:VEVENT
236
BEGIN:VEVENT
237
DTSTAMP:20220301T030012Z
238
DTSTART;VALUE=DATE:20210220
239
DTEND;VALUE=DATE:20210308
240
SUMMARY:Vacances d'Hiver
241
UID:20220301T030112Z-Zone-B@data.education.gouv.fr
242
LOCATION:
243
DESCRIPTION:Vacances d'Hiver
244
TRANSP:TRANSPARENT
245
END:VEVENT
246
BEGIN:VEVENT
247
DTSTAMP:20220301T030012Z
248
DTSTART;VALUE=DATE:20210410
249
DTEND;VALUE=DATE:20210426
250
SUMMARY:Vacances de Printemps
251
UID:20220301T030113Z-Zone-B@data.education.gouv.fr
252
LOCATION:
253
DESCRIPTION:Vacances de Printemps
254
TRANSP:TRANSPARENT
255
END:VEVENT
256
BEGIN:VEVENT
257
DTSTAMP:20220301T030012Z
258
DTSTART;VALUE=DATE:20210513
259
DTEND;VALUE=DATE:20210517
260
SUMMARY:Pont de l'Ascension
261
UID:20220301T030114Z-Zone-B@data.education.gouv.fr
262
LOCATION:
263
DESCRIPTION:Pont de l'Ascension
264
TRANSP:TRANSPARENT
265
END:VEVENT
266
BEGIN:VEVENT
267
DTSTAMP:20220301T030012Z
268
DTSTART;VALUE=DATE:20210706
269
DTEND;VALUE=DATE:20210901
270
SUMMARY:Vacances d'Été
271
UID:20220301T030115Z-Zone-B@data.education.gouv.fr
272
LOCATION:
273
DESCRIPTION:Vacances d'Été (Enseignants)
274
TRANSP:TRANSPARENT
275
END:VEVENT
276
BEGIN:VEVENT
277
DTSTAMP:20220301T030012Z
278
DTSTART;VALUE=DATE:20210706
279
DTEND;VALUE=DATE:20210902
280
SUMMARY:Vacances d'Été
281
UID:20220301T030116Z-Zone-B@data.education.gouv.fr
282
LOCATION:
283
DESCRIPTION:Vacances d'Été (Élèves)
284
TRANSP:TRANSPARENT
285
END:VEVENT
286
BEGIN:VEVENT
287
DTSTAMP:20220301T030012Z
288
DTSTART;VALUE=DATE:20211023
289
DTEND;VALUE=DATE:20211108
290
SUMMARY:Vacances de la Toussaint
291
UID:20220301T030117Z-Zone-B@data.education.gouv.fr
292
LOCATION:
293
DESCRIPTION:Vacances de la Toussaint
294
TRANSP:TRANSPARENT
295
END:VEVENT
296
BEGIN:VEVENT
297
DTSTAMP:20220301T030012Z
298
DTSTART;VALUE=DATE:20211218
299
DTEND;VALUE=DATE:20220103
300
SUMMARY:Vacances de Noël
301
UID:20220301T030118Z-Zone-B@data.education.gouv.fr
302
LOCATION:
303
DESCRIPTION:Vacances de Noël
304
TRANSP:TRANSPARENT
305
END:VEVENT
306
BEGIN:VEVENT
307
DTSTAMP:20220301T030012Z
308
DTSTART;VALUE=DATE:20220205
309
DTEND;VALUE=DATE:20220221
310
SUMMARY:Vacances d'Hiver
311
UID:20220301T030119Z-Zone-B@data.education.gouv.fr
312
LOCATION:
313
DESCRIPTION:Vacances d'Hiver
314
TRANSP:TRANSPARENT
315
END:VEVENT
316
BEGIN:VEVENT
317
DTSTAMP:20220301T030012Z
318
DTSTART;VALUE=DATE:20220409
319
DTEND;VALUE=DATE:20220425
320
SUMMARY:Vacances de Printemps
321
UID:20220301T030120Z-Zone-B@data.education.gouv.fr
322
LOCATION:
323
DESCRIPTION:Vacances de Printemps
324
TRANSP:TRANSPARENT
325
END:VEVENT
326
BEGIN:VEVENT
327
DTSTAMP:20220301T030012Z
328
DTSTART;VALUE=DATE:20220526
329
DTEND;VALUE=DATE:20220528
330
SUMMARY:Pont de l'Ascension
331
UID:20220301T030121Z-Zone-B@data.education.gouv.fr
332
LOCATION:
333
DESCRIPTION:Pont de l'Ascension
334
TRANSP:TRANSPARENT
335
END:VEVENT
336
BEGIN:VEVENT
337
DTSTAMP:20220301T030012Z
338
DTSTART;VALUE=DATE:20220707
339
DTEND;VALUE=DATE:20220831
340
SUMMARY:Vacances d'Été
341
UID:20220301T030122Z-Zone-B@data.education.gouv.fr
342
LOCATION:
343
DESCRIPTION:Vacances d'Été (Enseignants)
344
TRANSP:TRANSPARENT
345
END:VEVENT
346
BEGIN:VEVENT
347
DTSTAMP:20220301T030012Z
348
DTSTART;VALUE=DATE:20220707
349
DTEND;VALUE=DATE:20220901
350
SUMMARY:Vacances d'Été
351
UID:20220301T030123Z-Zone-B@data.education.gouv.fr
352
LOCATION:
353
DESCRIPTION:Vacances d'Été (Élèves)
354
TRANSP:TRANSPARENT
355
END:VEVENT
356
BEGIN:VEVENT
357
DTSTAMP:20220301T030012Z
358
DTSTART;VALUE=DATE:20221022
359
DTEND;VALUE=DATE:20221107
360
SUMMARY:Vacances de la Toussaint
361
UID:20220301T030124Z-Zone-B@data.education.gouv.fr
362
LOCATION:
363
DESCRIPTION:Vacances de la Toussaint
364
TRANSP:TRANSPARENT
365
END:VEVENT
366
BEGIN:VEVENT
367
DTSTAMP:20220301T030012Z
368
DTSTART;VALUE=DATE:20221217
369
DTEND;VALUE=DATE:20230103
370
SUMMARY:Vacances de Noël
371
UID:20220301T030125Z-Zone-B@data.education.gouv.fr
372
LOCATION:
373
DESCRIPTION:Vacances de Noël
374
TRANSP:TRANSPARENT
375
END:VEVENT
376
BEGIN:VEVENT
377
DTSTAMP:20220301T030012Z
378
DTSTART;VALUE=DATE:20230211
379
DTEND;VALUE=DATE:20230227
380
SUMMARY:Vacances d'Hiver
381
UID:20220301T030126Z-Zone-B@data.education.gouv.fr
382
LOCATION:
383
DESCRIPTION:Vacances d'Hiver
384
TRANSP:TRANSPARENT
385
END:VEVENT
386
BEGIN:VEVENT
387
DTSTAMP:20220301T030012Z
388
DTSTART;VALUE=DATE:20230415
389
DTEND;VALUE=DATE:20230502
390
SUMMARY:Vacances de Printemps
391
UID:20220301T030127Z-Zone-B@data.education.gouv.fr
392
LOCATION:
393
DESCRIPTION:Vacances de Printemps
394
TRANSP:TRANSPARENT
395
END:VEVENT
396
BEGIN:VEVENT
397
DTSTAMP:20220301T030012Z
398
DTSTART;VALUE=DATE:20230518
399
DTEND;VALUE=DATE:20230522
400
SUMMARY:Pont de l'Ascension
401
UID:20220301T030128Z-Zone-B@data.education.gouv.fr
402
LOCATION:
403
DESCRIPTION:Pont de l'Ascension
404
TRANSP:TRANSPARENT
405
END:VEVENT
406
BEGIN:VEVENT
407
DTSTAMP:20220301T030012Z
408
DTSTART;VALUE=DATE:20230708
409
DTEND;VALUE=DATE:20230708
410
SUMMARY:Début des Vacances d'Été
411
UID:20220301T030129Z-Zone-B@data.education.gouv.fr
412
LOCATION:
413
DESCRIPTION:Début des Vacances d'Été
414
TRANSP:TRANSPARENT
415
END:VEVENT
416
END:VCALENDAR
tests/test_holidays.py
1
import datetime
2
import os
3

  
4
import pytest
5
import vobject
6
from httmock import HTTMock, with_httmock
7
from test_manager import login
8

  
9
from passerelle.apps.holidays.models import Holidays
10

  
11
pytestmark = pytest.mark.django_db
12

  
13
RESPONSE_ZONE_A = open(os.path.join(os.path.dirname(__file__), 'data/holidays/Zone-A.ics')).read()
14

  
15
RESPONSE_ZONE_B = open(os.path.join(os.path.dirname(__file__), 'data/holidays/Zone-B.ics')).read()
16

  
17

  
18
def ics_data(url, request):
19
    if url.path == 'https://fr.ftp.opendatasoft.com/openscol/fr-en-calendrier-scolaire/Zone-A.ics':
20
        return {'content': RESPONSE_ZONE_A, 'request': request, 'status_code': 200}
21
    if url.path == 'https://fr.ftp.opendatasoft.com/openscol/fr-en-calendrier-scolaire/Zone-B.ics':
22
        return {'content': RESPONSE_ZONE_B, 'request': request, 'status_code': 200}
23

  
24

  
25
@with_httmock(ics_data)
26
def test_holidays_ics(app):
27
    connector = Holidays.objects.create(slug='test', zone='a', holidays=[])
28

  
29
    resp = app.get('/holidays/test/holidays.ics')
30
    calendar = vobject.readOne(resp.text)
31
    assert not 'vevent' in calendar.contents
32

  
33
    connector.holidays.append('winter_holidays')
34
    connector.save()
35

  
36
    resp = app.get('/holidays/test/holidays.ics')
37
    calendar = vobject.readOne(resp.text)
38
    first_event = calendar.contents['vevent'][0]
39
    assert first_event.uid.value == 'winter_holidays-2018'
40
    assert first_event.summary.value == 'Vacances d’Hiver'
41
    assert first_event.categories.value == ['winter_holidays']
42
    assert str(first_event.dtstart.value) == '2018-02-10'
43
    assert str(first_event.dtend.value) == '2018-02-26'
44

  
45
    for i, event in enumerate(calendar.contents['vevent'][1:], start=2019):
46
        assert event.uid.value == 'winter_holidays-%s' % i
47
        assert event.summary.value == 'Vacances d’Hiver'
48
        assert event.categories.value == ['winter_holidays']
49

  
50
    connector.zone = 'b'
51
    connector.save()
52

  
53
    resp = app.get('/holidays/test/holidays.ics')
54
    calendar = vobject.readOne(resp.text)
55
    first_event = calendar.contents['vevent'][0]
56
    assert first_event.uid.value == 'winter_holidays-2018'
57
    assert str(first_event.dtstart.value) == '2018-02-24'
58
    assert str(first_event.dtend.value) == '2018-03-12'
59

  
60
    connector.holidays.append('summer_holidays')
61
    connector.save()
62

  
63
    resp = app.get('/holidays/test/holidays.ics')
64
    calendar = vobject.readOne(resp.text)
65
    assert calendar.contents['vevent'][0].uid.value == 'winter_holidays-2018'
66
    first_summer_event = next(x for x in calendar.contents['vevent'] if x.uid.value.startswith('summer'))
67
    assert first_summer_event.uid.value == 'summer_holidays-2018'
68
    assert first_summer_event.summary.value == 'Vacances d’Été'
69
    assert first_summer_event.categories.value == ['summer_holidays']
70
    assert str(first_summer_event.dtstart.value) == '2018-07-07'
71
    assert str(first_summer_event.dtend.value) == '2018-09-03'
72

  
73
    connector.holidays.append('mothers_day')
74
    connector.save()
75

  
76
    resp = app.get('/holidays/test/holidays.ics')
77
    calendar = vobject.readOne(resp.text)
78
    mothers_day_events = [x for x in calendar.contents['vevent'] if x.uid.value.startswith('mothers')]
79
    assert len(mothers_day_events) == 6
80
    first_event = mothers_day_events[0]
81
    assert first_event.uid.value == 'mothers_day-2018'
82
    assert first_event.summary.value == 'Fête des Mères'
83
    assert first_event.categories.value == ['mothers_day']
84
    assert str(first_event.dtstart.value) == '2018-05-27'
85
    assert str(first_event.dtend.value) == '2018-05-28'
86

  
87
    for i, event in enumerate(mothers_day_events[1:], start=2019):
88
        assert event.uid.value == 'mothers_day-%s' % i
89
        assert event.summary.value == 'Fête des Mères'
90
        assert event.categories.value == ['mothers_day']
91

  
92
    connector.holidays = [
93
        'winter_holidays',
94
        'spring_holidays',
95
        'summer_holidays',
96
        'all_saints_holidays',
97
        'christmas_holidays',
98
        'fathers_day',
99
        'mothers_day',
100
    ]
101
    connector.save()
102

  
103
    resp = app.get('/holidays/test/holidays.ics')
104
    calendar = vobject.readOne(resp.text)
105
    holiday_ids = {x.categories.value[0] for x in calendar.contents['vevent']}
106
    assert all(holiday_id in holiday_ids for holiday_id in connector.holidays)
107

  
108

  
109
def test_holidays_ics_invalid_responses(app):
110
    connector = Holidays.objects.create(slug='test', zone='a', holidays=[])
111

  
112
    def server_error(url, request):
113
        return {'content': 'error', 'status_code': 500}
114

  
115
    with HTTMock(server_error):
116
        resp = app.get('/holidays/test/holidays.ics')
117
    assert resp.json['err'] == 1
118
    assert 'Error while getting ICS file' in resp.json['err_desc']
119

  
120
    def invalid_file(url, request):
121
        return {'content': 'invalid', 'status_code': 200}
122

  
123
    with HTTMock(invalid_file):
124
        resp = app.get('/holidays/test/holidays.ics')
125
    assert resp.json['err'] == 1
126
    assert 'Invalid ICS file' in resp.json['err_desc']
127

  
128
    def empty_file(url, request):
129
        return {
130
            'content': 'BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//PYVOBJECT//NONSGML Version 1//EN\r\nEND:VCALENDAR\r\n',
131
            'status_code': 200,
132
        }
133

  
134
    with HTTMock(empty_file):
135
        resp = app.get('/holidays/test/holidays.ics')
136
    assert resp.json['err'] == 1
137
    assert 'ICS file does not contain events' in resp.json['err_desc']
138

  
139

  
140
@pytest.mark.parametrize(
141
    'year,date',
142
    [
143
        (2017, datetime.date(year=2017, month=5, day=28)),
144
        (2018, datetime.date(year=2018, month=5, day=27)),
145
        (2019, datetime.date(year=2019, month=5, day=26)),
146
        (2020, datetime.date(year=2020, month=6, day=7)),
147
        (2021, datetime.date(year=2021, month=5, day=30)),
148
        (2022, datetime.date(year=2022, month=5, day=29)),
149
        (2023, datetime.date(year=2023, month=6, day=4)),
150
    ],
151
)
152
def test_mothers_day_dates(year, date):
153
    assert Holidays.get_mothers_day(year) == date
154

  
155

  
156
@pytest.mark.parametrize(
157
    'year,date',
158
    [
159
        (2017, datetime.date(year=2017, month=6, day=18)),
160
        (2018, datetime.date(year=2018, month=6, day=17)),
161
        (2019, datetime.date(year=2019, month=6, day=16)),
162
        (2020, datetime.date(year=2020, month=6, day=21)),
163
        (2021, datetime.date(year=2021, month=6, day=20)),
164
        (2022, datetime.date(year=2022, month=6, day=19)),
165
        (2023, datetime.date(year=2023, month=6, day=18)),
166
    ],
167
)
168
def test_fathers_day_dates(year, date):
169
    assert Holidays.get_fathers_day(year) == date
170

  
171

  
172
def test_holidays_manager(app, admin_user):
173
    login(app)
174

  
175
    resp = app.get('/manage/holidays/add')
176
    resp.form['title'] = 'Holidays'
177
    resp.form['slug'] = 'test'
178
    resp.form['description'] = 'test'
179
    resp.form['zone'] = 'c'
180
    resp.form['holidays'] = ['mothers_day', 'christmas_holidays']
181

  
182
    resp = resp.form.submit().follow()
183
    assert 'Holidays:\n  Vacances de Noël, Fête des Mères' in resp.text
184
    assert 'Zone:\n  C' in resp.text
0
-