source: josm/trunk/data/validator/opening_hours.js@ 7078

Last change on this file since 7078 was 7046, checked in by Don-vip, 10 years ago

see #9923 - update opening_hours.js + translate/shorten a bit error messages

File size: 160.3 KB
Line 
1(function (root, factory) {
2 // constants (holidays, error correction) {{{
3 // holidays {{{
4 var holidays = {
5 'fr': {
6 'PH': { // http://fr.wikipedia.org/wiki/F%C3%AAtes_et_jours_f%C3%A9ri%C3%A9s_en_France
7 "Jour de l'an" : [ 1, 1 ],
8 "Vendredi saint" : [ 'easter', -2, [ 'Moselle', 'Bas-Rhin', 'Haut-Rhin', 'Guadeloupe', 'Martinique', 'Polynésie française' ] ],
9 "Lundi de Pâques" : [ 'easter', 1 ],
10 "Saint-Pierre-Chanel" : [ 4, 28, [ 'Wallis-et-Futuna' ] ],
11 "Fête du Travail" : [ 5, 1 ],
12 "Fête de la Victoire" : [ 5, 8 ],
13 "Abolition de l'esclavage" : [ 5, 22, [ 'Martinique' ] ],
14 "Abolition de l'esclavage" : [ 5, 27, [ 'Guadeloupe' ] ],
15 "Jeudi de l'Ascension" : [ 'easter', 39 ],
16 "Lundi de Pentecôte" : [ 'easter', 50 ],
17 "Abolition de l'esclavage" : [ 6, 10, [ 'Guyane' ] ],
18 "Fête de l'autonomie" : [ 6, 29, [ 'Polynésie française' ] ],
19 "Fête nationale" : [ 7, 14 ],
20 "Fête Victor Schoelcher" : [ 7, 21, [ 'Guadeloupe', 'Martinique' ] ],
21 "Fête du Territoire" : [ 7, 29, [ 'Wallis-et-Futuna' ] ],
22 "Assomption" : [ 8, 15 ],
23 "Fête de la citoyenneté" : [ 9, 24, [ 'Nouvelle-Calédonie' ] ],
24 "Toussaint" : [ 11, 1 ],
25 "Armistice" : [ 11, 11 ],
26 "Abolition de l'esclavage" : [ 12, 20, [ 'Réunion' ] ],
27 "Noël" : [ 12, 25 ],
28 "Saint-Étienne " : [ 12, 26, [ 'Moselle', 'Bas-Rhin', 'Haut-Rhin' ] ]
29 }
30 },
31 'de': {
32 'PH': { // http://de.wikipedia.org/wiki/Feiertage_in_Deutschland
33 'Neujahrstag' : [ 1, 1 ], // month 1, day 1, whole Germany
34 'Heilige Drei Könige' : [ 1, 6, [ 'Baden-Württemberg', 'Bayern', 'Sachsen-Anhalt'] ], // only in the specified states
35 'Tag der Arbeit' : [ 5, 1 ], // whole Germany
36 'Karfreitag' : [ 'easter', -2 ], // two days before easter
37 'Ostersonntag' : [ 'easter', 0, [ 'Brandenburg'] ],
38 'Ostermontag' : [ 'easter', 1 ],
39 'Christi Himmelfahrt' : [ 'easter', 39 ],
40 'Pfingstsonntag' : [ 'easter', 49, [ 'Brandenburg'] ],
41 'Pfingstmontag' : [ 'easter', 50 ],
42 'Fronleichnam' : [ 'easter', 60, [ 'Baden-Württemberg', 'Bayern', 'Hessen', 'Nordrhein-Westfalen', 'Rheinland-Pfalz', 'Saarland' ] ],
43 'Mariä Himmelfahrt' : [ 8, 15, [ 'Saarland'] ],
44 'Tag der Deutschen Einheit' : [ 10, 3 ],
45 'Reformationstag' : [ 10, 31, [ 'Brandenburg', 'Mecklenburg-Vorpommern', 'Sachsen', 'Sachsen-Anhalt', 'Thüringen'] ],
46 'Allerheiligen' : [ 11, 1, [ 'Baden-Württemberg', 'Bayern', 'Nordrhein-Westfalen', 'Rheinland-Pfalz', 'Saarland' ] ],
47 '1. Weihnachtstag' : [ 12, 25 ],
48 '2. Weihnachtstag' : [ 12, 26 ],
49 // 'Silvester' : [ 12, 31 ], // for testing
50 },
51 'Baden-Württemberg': { // does only apply in Baden-Württemberg
52 // This more specific rule set overwrites the country wide one (they are just ignored).
53 // You may use this instead of the country wide with some
54 // additional holidays for some states, if one state
55 // totally disagrees about how to do public holidays …
56 // 'PH': {
57 // '2. Weihnachtstag' : [ 12, 26 ],
58 // },
59
60 // school holiday normally variate between states
61 'SH': [ // generated by convert_ical_to_json
62 // You may can adjust this script to use other resources (for other countries) too.
63 {
64 name: 'Osterferien',
65 2005: [ 3, 24, /* to */ 3, 24, 3, 29, /* to */ 4, 2 ],
66 2006: [ 4, 18, /* to */ 4, 22 ],
67 2007: [ 4, 2, /* to */ 4, 14 ],
68 2008: [ 3, 17, /* to */ 3, 28 ],
69 2009: [ 4, 9, /* to */ 4, 9, 4, 14, /* to */ 4, 17 ],
70 2010: [ 4, 1, /* to */ 4, 1, 4, 6, /* to */ 4, 10 ],
71 2011: [ 4, 21, /* to */ 4, 21, 4, 26, /* to */ 4, 30 ],
72 2012: [ 4, 2, /* to */ 4, 13 ],
73 2013: [ 3, 25, /* to */ 4, 5 ],
74 2014: [ 4, 14, /* to */ 4, 25 ],
75 2015: [ 3, 30, /* to */ 4, 10 ],
76 2016: [ 3, 29, /* to */ 4, 2 ],
77 2017: [ 4, 10, /* to */ 4, 21 ],
78 },
79 {
80 name: 'Pfingstferien',
81 2005: [ 5, 17, /* to */ 5, 28 ],
82 2006: [ 5, 29, /* to */ 6, 10 ],
83 2007: [ 5, 29, /* to */ 6, 9 ],
84 2008: [ 5, 13, /* to */ 5, 23 ],
85 2009: [ 5, 25, /* to */ 6, 6 ],
86 2010: [ 5, 25, /* to */ 6, 5 ],
87 2011: [ 6, 14, /* to */ 6, 25 ],
88 2012: [ 5, 29, /* to */ 6, 9 ],
89 2013: [ 5, 21, /* to */ 6, 1 ],
90 2014: [ 6, 10, /* to */ 6, 21 ],
91 2015: [ 5, 26, /* to */ 6, 6 ],
92 2016: [ 5, 17, /* to */ 5, 28 ],
93 2017: [ 6, 6, /* to */ 6, 16 ],
94 },
95 {
96 name: 'Sommerferien',
97 2005: [ 7, 28, /* to */ 9, 10 ],
98 2006: [ 8, 3, /* to */ 9, 16 ],
99 2007: [ 7, 26, /* to */ 9, 8 ],
100 2008: [ 7, 24, /* to */ 9, 6 ],
101 2009: [ 7, 30, /* to */ 9, 12 ],
102 2010: [ 7, 29, /* to */ 9, 11 ],
103 2011: [ 7, 28, /* to */ 9, 10 ],
104 2012: [ 7, 26, /* to */ 9, 8 ],
105 2013: [ 7, 25, /* to */ 9, 7 ],
106 2014: [ 7, 31, /* to */ 9, 13 ],
107 2015: [ 7, 30, /* to */ 9, 12 ],
108 2016: [ 7, 28, /* to */ 9, 10 ],
109 2017: [ 7, 27, /* to */ 9, 9 ],
110 },
111 {
112 name: 'Herbstferien',
113 2005: [ 11, 2, /* to */ 11, 4 ],
114 2006: [ 10, 30, /* to */ 11, 3 ],
115 2007: [ 10, 29, /* to */ 11, 3 ],
116 2008: [ 10, 27, /* to */ 10, 31 ],
117 2009: [ 10, 26, /* to */ 10, 31 ],
118 2010: [ 11, 2, /* to */ 11, 6 ],
119 2011: [ 10, 31, /* to */ 10, 31, 11, 2, /* to */ 11, 4 ],
120 2012: [ 10, 29, /* to */ 11, 2 ],
121 2013: [ 10, 28, /* to */ 10, 30 ],
122 2014: [ 10, 27, /* to */ 10, 30 ],
123 2015: [ 11, 2, /* to */ 11, 6 ],
124 2016: [ 11, 2, /* to */ 11, 4 ],
125 },
126 {
127 name: 'Weihnachtsferien',
128 2005: [ 12, 22, /* to */ 1, 5 ],
129 2006: [ 12, 27, /* to */ 1, 5 ],
130 2007: [ 12, 24, /* to */ 1, 5 ],
131 2008: [ 12, 22, /* to */ 1, 10 ],
132 2009: [ 12, 23, /* to */ 1, 9 ],
133 2010: [ 12, 23, /* to */ 1, 8 ],
134 2011: [ 12, 23, /* to */ 1, 5 ],
135 2012: [ 12, 24, /* to */ 1, 5 ],
136 2013: [ 12, 23, /* to */ 1, 4 ],
137 2014: [ 12, 22, /* to */ 1, 5 ],
138 2015: [ 12, 23, /* to */ 1, 9 ],
139 2016: [ 12, 23, /* to */ 1, 7 ],
140 },
141 ],
142 },
143 'Mecklenburg-Vorpommern': {
144 'SH': [
145 {
146 name: 'Winterferien',
147 2010: [ 2, 6, /* to */ 2, 20 ],
148 2011: [ 2, 7, /* to */ 2, 19 ],
149 2012: [ 2, 6, /* to */ 2, 17 ],
150 2013: [ 2, 4, /* to */ 2, 15 ],
151 2014: [ 2, 3, /* to */ 2, 15 ],
152 2015: [ 2, 2, /* to */ 2, 14 ],
153 2016: [ 2, 1, /* to */ 2, 13 ],
154 2017: [ 2, 6, /* to */ 2, 18 ],
155 },
156 {
157 name: 'Osterferien',
158 2010: [ 3, 29, /* to */ 4, 7 ],
159 2011: [ 4, 16, /* to */ 4, 27 ],
160 2012: [ 4, 2, /* to */ 4, 11 ],
161 2013: [ 3, 25, /* to */ 4, 3 ],
162 2014: [ 4, 14, /* to */ 4, 23 ],
163 2015: [ 3, 30, /* to */ 4, 8 ],
164 2016: [ 3, 21, /* to */ 3, 30 ],
165 2017: [ 4, 10, /* to */ 4, 19 ],
166 },
167 {
168 name: 'Pfingstferien',
169 2010: [ 5, 21, /* to */ 5, 22 ],
170 2011: [ 6, 10, /* to */ 6, 14 ],
171 2012: [ 5, 25, /* to */ 5, 29 ],
172 2013: [ 5, 17, /* to */ 5, 21 ],
173 2014: [ 6, 6, /* to */ 6, 10 ],
174 2015: [ 5, 22, /* to */ 5, 26 ],
175 2016: [ 5, 14, /* to */ 5, 17 ],
176 2017: [ 6, 2, /* to */ 6, 6 ],
177 },
178 {
179 name: 'Sommerferien',
180 2010: [ 7, 12, /* to */ 8, 21 ],
181 2011: [ 7, 4, /* to */ 8, 13 ],
182 2012: [ 6, 23, /* to */ 8, 4 ],
183 2013: [ 6, 22, /* to */ 8, 3 ],
184 2014: [ 7, 14, /* to */ 8, 23 ],
185 2015: [ 7, 20, /* to */ 8, 29 ],
186 2016: [ 7, 25, /* to */ 9, 3 ],
187 2017: [ 7, 24, /* to */ 9, 2 ],
188 },
189 {
190 name: 'Herbstferien',
191 2010: [ 10, 18, /* to */ 10, 23 ],
192 2011: [ 10, 17, /* to */ 10, 21 ],
193 2012: [ 10, 1, /* to */ 10, 5 ],
194 2013: [ 10, 14, /* to */ 10, 19 ],
195 2014: [ 10, 20, /* to */ 10, 25 ],
196 2015: [ 10, 24, /* to */ 10, 30 ],
197 2016: [ 10, 24, /* to */ 10, 28 ],
198 },
199 {
200 name: 'Weihnachtsferien',
201 2010: [ 12, 23, /* to */ 12, 31 ],
202 2011: [ 12, 23, /* to */ 1, 3 ],
203 2012: [ 12, 21, /* to */ 1, 4 ],
204 2013: [ 12, 23, /* to */ 1, 3 ],
205 2014: [ 12, 22, /* to */ 1, 2 ],
206 2015: [ 12, 21, /* to */ 1, 2 ],
207 2016: [ 12, 22, /* to */ 1, 2 ],
208 },
209 ],
210 },
211 'Hessen': {
212 'SH': [
213 {
214 name: 'Osterferien',
215 2010: [ 3, 29, /* to */ 4, 10 ],
216 2011: [ 4, 18, /* to */ 4, 30 ],
217 2012: [ 4, 2, /* to */ 4, 14 ],
218 2013: [ 3, 25, /* to */ 4, 6 ],
219 2014: [ 4, 14, /* to */ 4, 26 ],
220 2015: [ 3, 30, /* to */ 4, 11 ],
221 2016: [ 3, 29, /* to */ 4, 9 ],
222 2017: [ 4, 3, /* to */ 4, 15 ],
223 2018: [ 3, 26, /* to */ 4, 7 ],
224 },
225 {
226 name: 'Sommerferien',
227 2010: [ 7, 5, /* to */ 8, 14 ],
228 2011: [ 6, 27, /* to */ 8, 5 ],
229 2012: [ 7, 2, /* to */ 8, 10 ],
230 2013: [ 7, 8, /* to */ 8, 16 ],
231 2014: [ 7, 28, /* to */ 9, 5 ],
232 2015: [ 7, 27, /* to */ 9, 5 ],
233 2016: [ 7, 18, /* to */ 8, 26 ],
234 2017: [ 7, 3, /* to */ 8, 11 ],
235 },
236 {
237 name: 'Herbstferien',
238 2010: [ 10, 11, /* to */ 10, 22 ],
239 2011: [ 10, 10, /* to */ 10, 22 ],
240 2012: [ 10, 15, /* to */ 10, 27 ],
241 2013: [ 10, 14, /* to */ 10, 26 ],
242 2014: [ 10, 20, /* to */ 11, 1 ],
243 2015: [ 10, 19, /* to */ 10, 31 ],
244 2016: [ 10, 17, /* to */ 10, 29 ],
245 2017: [ 10, 9, /* to */ 10, 21 ],
246 },
247 {
248 name: 'Weihnachtsferien',
249 2010: [ 12, 20, /* to */ 1, 7 ],
250 2011: [ 12, 21, /* to */ 1, 6 ],
251 2012: [ 12, 24, /* to */ 1, 12 ],
252 2013: [ 12, 23, /* to */ 1, 11 ],
253 2014: [ 12, 22, /* to */ 1, 10 ],
254 2015: [ 12, 23, /* to */ 1, 9 ],
255 2016: [ 12, 22, /* to */ 1, 7 ],
256 2017: [ 12, 24, /* to */ 1, 13 ],
257 },
258 ],
259 },
260 'Schleswig-Holstein': {
261 'SH': [
262 {
263 name: 'Osterferien',
264 2010: [ 4, 3, /* to */ 4, 17 ],
265 2011: [ 4, 15, /* to */ 4, 30 ],
266 2012: [ 3, 30, /* to */ 4, 13 ],
267 2013: [ 3, 25, /* to */ 4, 9 ],
268 2014: [ 4, 16, /* to */ 5, 2 ],
269 2015: [ 4, 1, /* to */ 4, 17 ],
270 2016: [ 3, 24, /* to */ 4, 9 ],
271 2017: [ 4, 7, /* to */ 4, 21 ],
272 },
273 {
274 name: 'Sommerferien',
275 2010: [ 7, 12, /* to */ 8, 21 ],
276 2011: [ 7, 4, /* to */ 8, 13 ],
277 2012: [ 6, 25, /* to */ 8, 4 ],
278 2013: [ 6, 24, /* to */ 8, 3 ],
279 2014: [ 7, 14, /* to */ 8, 23 ],
280 2015: [ 7, 20, /* to */ 8, 29 ],
281 2016: [ 7, 25, /* to */ 9, 3 ],
282 2017: [ 7, 24, /* to */ 9, 2 ],
283 },
284 {
285 name: 'Pfingstferien',
286 2011: [ 6, 3, /* to */ 6, 4 ],
287 2012: [ 5, 18, /* to */ 5, 18 ],
288 2013: [ 5, 10, /* to */ 5, 10 ],
289 2014: [ 5, 30, /* to */ 5, 30 ],
290 2015: [ 5, 15, /* to */ 5, 15 ],
291 2016: [ 5, 6, /* to */ 5, 6 ],
292 2017: [ 5, 26, /* to */ 5, 26 ],
293 },
294 {
295 name: 'Herbstferien',
296 2010: [ 10, 11, /* to */ 10, 23 ],
297 2011: [ 10, 10, /* to */ 10, 22 ],
298 2012: [ 10, 4, /* to */ 10, 19 ],
299 2013: [ 10, 4, /* to */ 10, 18 ],
300 2014: [ 10, 13, /* to */ 10, 25 ],
301 2015: [ 10, 19, /* to */ 10, 31 ],
302 2016: [ 10, 17, /* to */ 10, 29 ],
303 },
304 {
305 name: 'Weihnachtsferien',
306 2010: [ 12, 23, /* to */ 1, 7 ],
307 2011: [ 12, 23, /* to */ 1, 6 ],
308 2012: [ 12, 24, /* to */ 1, 5 ],
309 2013: [ 12, 23, /* to */ 1, 6 ],
310 2014: [ 12, 22, /* to */ 1, 6 ],
311 2015: [ 12, 21, /* to */ 1, 6 ],
312 2016: [ 12, 23, /* to */ 1, 6 ],
313 },
314 ],
315 },
316 'Berlin': {
317 'SH': [
318 {
319 name: 'Winterferien',
320 2010: [ 2, 1, /* to */ 2, 6 ],
321 2011: [ 1, 31, /* to */ 2, 5 ],
322 2012: [ 1, 30, /* to */ 2, 4 ],
323 2013: [ 2, 4, /* to */ 2, 9 ],
324 2014: [ 2, 3, /* to */ 2, 8 ],
325 2015: [ 2, 2, /* to */ 2, 7 ],
326 2016: [ 2, 1, /* to */ 2, 6 ],
327 2017: [ 1, 30, /* to */ 2, 4 ],
328 },
329 {
330 name: 'Osterferien',
331 2010: [ 3, 31, /* to */ 4, 10 ],
332 2011: [ 4, 18, /* to */ 4, 30 ],
333 2012: [ 4, 2, /* to */ 4, 14, 4, 30, /* to */ 4, 30 ],
334 2013: [ 3, 25, /* to */ 4, 6 ],
335 2014: [ 4, 14, /* to */ 4, 26, 5, 2, /* to */ 5, 2 ],
336 2015: [ 3, 30, /* to */ 4, 11 ],
337 2016: [ 3, 21, /* to */ 4, 2 ],
338 2017: [ 4, 10, /* to */ 4, 22 ],
339 },
340 {
341 name: 'Pfingstferien',
342 2010: [ 5, 14, /* to */ 5, 14, 5, 25, /* to */ 5, 25 ],
343 2011: [ 6, 3, /* to */ 6, 3 ],
344 2012: [ 5, 18, /* to */ 5, 18 ],
345 2013: [ 5, 10, /* to */ 5, 10, 5, 21, /* to */ 5, 21 ],
346 2014: [ 5, 30, /* to */ 5, 30 ],
347 2015: [ 5, 15, /* to */ 5, 15 ],
348 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 17 ],
349 2017: [ 5, 26, /* to */ 5, 26 ],
350 },
351 {
352 name: 'Sommerferien',
353 2010: [ 7, 7, /* to */ 8, 21 ],
354 2011: [ 6, 29, /* to */ 8, 12 ],
355 2012: [ 6, 20, /* to */ 8, 3 ],
356 2013: [ 6, 19, /* to */ 8, 2 ],
357 2014: [ 7, 9, /* to */ 8, 22 ],
358 2015: [ 7, 15, /* to */ 8, 28 ],
359 2016: [ 7, 20, /* to */ 9, 2 ],
360 2017: [ 7, 19, /* to */ 9, 1 ],
361 },
362 {
363 name: 'Herbstferien',
364 2010: [ 10, 11, /* to */ 10, 23 ],
365 2011: [ 10, 4, /* to */ 10, 14 ],
366 2012: [ 10, 1, /* to */ 10, 13 ],
367 2013: [ 9, 30, /* to */ 10, 12 ],
368 2014: [ 10, 20, /* to */ 11, 1 ],
369 2015: [ 10, 19, /* to */ 10, 31 ],
370 2016: [ 10, 17, /* to */ 10, 28 ],
371 },
372 {
373 name: 'Weihnachtsferien',
374 2010: [ 12, 23, /* to */ 1, 1 ],
375 2011: [ 12, 23, /* to */ 1, 3 ],
376 2012: [ 12, 24, /* to */ 1, 4 ],
377 2013: [ 12, 23, /* to */ 1, 3 ],
378 2014: [ 12, 22, /* to */ 1, 2 ],
379 2015: [ 12, 23, /* to */ 1, 2 ],
380 2016: [ 12, 23, /* to */ 1, 3 ],
381 },
382 ],
383 },
384 'Saarland': {
385 'SH': [
386 {
387 name: 'Winterferien',
388 2010: [ 2, 15, /* to */ 2, 20 ],
389 2011: [ 3, 7, /* to */ 3, 12 ],
390 2012: [ 2, 20, /* to */ 2, 25 ],
391 2013: [ 2, 11, /* to */ 2, 16 ],
392 2014: [ 3, 3, /* to */ 3, 8 ],
393 2015: [ 2, 16, /* to */ 2, 21 ],
394 },
395 {
396 name: 'Osterferien',
397 2010: [ 3, 29, /* to */ 4, 10 ],
398 2011: [ 4, 18, /* to */ 4, 30 ],
399 2012: [ 4, 2, /* to */ 4, 14 ],
400 2013: [ 3, 25, /* to */ 4, 6 ],
401 2014: [ 4, 14, /* to */ 4, 26 ],
402 2015: [ 3, 30, /* to */ 4, 11 ],
403 },
404 {
405 name: 'Sommerferien',
406 2010: [ 7, 5, /* to */ 8, 14 ],
407 2011: [ 6, 24, /* to */ 8, 6 ],
408 2012: [ 7, 2, /* to */ 8, 14 ],
409 2013: [ 7, 8, /* to */ 8, 17 ],
410 2014: [ 7, 28, /* to */ 9, 6 ],
411 2015: [ 7, 27, /* to */ 9, 4 ],
412 2016: [ 7, 18, /* to */ 8, 26 ],
413 2017: [ 7, 3, /* to */ 8, 14 ],
414 },
415 {
416 name: 'Herbstferien',
417 2010: [ 10, 11, /* to */ 10, 23 ],
418 2011: [ 10, 4, /* to */ 10, 15 ],
419 2012: [ 10, 22, /* to */ 11, 3 ],
420 2013: [ 10, 21, /* to */ 11, 2 ],
421 2014: [ 10, 20, /* to */ 10, 31 ],
422 },
423 {
424 name: 'Weihnachtsferien',
425 2010: [ 12, 20, /* to */ 1, 1 ],
426 2011: [ 12, 23, /* to */ 1, 4 ],
427 2012: [ 12, 24, /* to */ 1, 5 ],
428 2013: [ 12, 20, /* to */ 1, 4 ],
429 2014: [ 12, 22, /* to */ 1, 7 ],
430 },
431 ],
432 },
433 'Bremen': {
434 'SH': [
435 {
436 name: 'Winterferien',
437 2010: [ 2, 1, /* to */ 2, 2 ],
438 2011: [ 1, 31, /* to */ 2, 1 ],
439 2012: [ 1, 30, /* to */ 1, 31 ],
440 2013: [ 1, 31, /* to */ 2, 1 ],
441 2014: [ 1, 30, /* to */ 1, 31 ],
442 2015: [ 2, 2, /* to */ 2, 3 ],
443 2016: [ 1, 28, /* to */ 1, 29 ],
444 2017: [ 1, 30, /* to */ 1, 31 ],
445 },
446 {
447 name: 'Osterferien',
448 2010: [ 3, 19, /* to */ 4, 6 ],
449 2011: [ 4, 16, /* to */ 4, 30 ],
450 2012: [ 3, 26, /* to */ 4, 11, 4, 30, /* to */ 4, 30 ],
451 2013: [ 3, 16, /* to */ 4, 2 ],
452 2014: [ 4, 3, /* to */ 4, 22, 5, 2, /* to */ 5, 2 ],
453 2015: [ 3, 25, /* to */ 4, 10 ],
454 2016: [ 3, 18, /* to */ 4, 2 ],
455 2017: [ 4, 10, /* to */ 4, 22 ],
456 },
457 {
458 name: 'Pfingstferien',
459 2010: [ 5, 14, /* to */ 5, 14, 5, 25, /* to */ 5, 25 ],
460 2011: [ 6, 3, /* to */ 6, 3, 6, 14, /* to */ 6, 14 ],
461 2012: [ 5, 18, /* to */ 5, 18, 5, 29, /* to */ 5, 29 ],
462 2013: [ 5, 10, /* to */ 5, 10, 5, 21, /* to */ 5, 21 ],
463 2014: [ 5, 30, /* to */ 5, 30, 6, 10, /* to */ 6, 10 ],
464 2015: [ 5, 15, /* to */ 5, 15, 5, 26, /* to */ 5, 26 ],
465 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 17 ],
466 2017: [ 5, 26, /* to */ 5, 26, 6, 6, /* to */ 6, 6 ],
467 },
468 {
469 name: 'Sommerferien',
470 2010: [ 6, 24, /* to */ 8, 4 ],
471 2011: [ 7, 7, /* to */ 8, 17 ],
472 2012: [ 7, 23, /* to */ 8, 31 ],
473 2013: [ 6, 27, /* to */ 8, 7 ],
474 2014: [ 7, 31, /* to */ 9, 10 ],
475 2015: [ 7, 23, /* to */ 9, 2 ],
476 2016: [ 6, 23, /* to */ 8, 3 ],
477 2017: [ 6, 22, /* to */ 8, 2 ],
478 },
479 {
480 name: 'Herbstferien',
481 2010: [ 10, 9, /* to */ 10, 23 ],
482 2011: [ 10, 17, /* to */ 10, 29 ],
483 2012: [ 10, 22, /* to */ 11, 3 ],
484 2013: [ 10, 4, /* to */ 10, 18 ],
485 2014: [ 10, 27, /* to */ 11, 8 ],
486 2015: [ 10, 19, /* to */ 10, 31 ],
487 2016: [ 10, 4, /* to */ 10, 15 ],
488 },
489 {
490 name: 'Weihnachtsferien',
491 2010: [ 12, 22, /* to */ 1, 5 ],
492 2011: [ 12, 23, /* to */ 1, 4 ],
493 2012: [ 12, 24, /* to */ 1, 5 ],
494 2013: [ 12, 23, /* to */ 1, 3 ],
495 2014: [ 12, 22, /* to */ 1, 5 ],
496 2015: [ 12, 23, /* to */ 1, 6 ],
497 2016: [ 12, 21, /* to */ 1, 6 ],
498 },
499 ],
500 },
501 'Bayern': {
502 'SH': [
503 {
504 name: 'Winterferien',
505 2010: [ 2, 15, /* to */ 2, 20 ],
506 2011: [ 3, 7, /* to */ 3, 11 ],
507 2012: [ 2, 20, /* to */ 2, 24 ],
508 2013: [ 2, 11, /* to */ 2, 15 ],
509 2014: [ 3, 3, /* to */ 3, 7 ],
510 2015: [ 2, 16, /* to */ 2, 20 ],
511 2016: [ 2, 8, /* to */ 2, 12 ],
512 2017: [ 2, 27, /* to */ 3, 3 ],
513 },
514 {
515 name: 'Osterferien',
516 2010: [ 3, 29, /* to */ 4, 10 ],
517 2011: [ 4, 18, /* to */ 4, 30 ],
518 2012: [ 4, 2, /* to */ 4, 14 ],
519 2013: [ 3, 25, /* to */ 4, 6 ],
520 2014: [ 4, 14, /* to */ 4, 26 ],
521 2015: [ 3, 30, /* to */ 4, 11 ],
522 2016: [ 3, 21, /* to */ 4, 1 ],
523 2017: [ 4, 10, /* to */ 4, 22 ],
524 },
525 {
526 name: 'Pfingstferien',
527 2010: [ 5, 25, /* to */ 6, 5 ],
528 2011: [ 6, 14, /* to */ 6, 25 ],
529 2012: [ 5, 29, /* to */ 6, 9 ],
530 2013: [ 5, 21, /* to */ 5, 31 ],
531 2014: [ 6, 10, /* to */ 6, 21 ],
532 2015: [ 5, 26, /* to */ 6, 5 ],
533 2016: [ 5, 17, /* to */ 5, 28 ],
534 2017: [ 6, 6, /* to */ 6, 16 ],
535 },
536 {
537 name: 'Sommerferien',
538 2010: [ 8, 2, /* to */ 9, 13 ],
539 2011: [ 7, 30, /* to */ 9, 12 ],
540 2012: [ 8, 1, /* to */ 9, 12 ],
541 2013: [ 7, 31, /* to */ 9, 11 ],
542 2014: [ 7, 30, /* to */ 9, 15 ],
543 2015: [ 8, 1, /* to */ 9, 14 ],
544 2016: [ 7, 30, /* to */ 9, 12 ],
545 2017: [ 7, 29, /* to */ 9, 11 ],
546 },
547 {
548 name: 'Herbstferien',
549 2010: [ 11, 2, /* to */ 11, 5 ],
550 2011: [ 10, 31, /* to */ 11, 5 ],
551 2012: [ 10, 29, /* to */ 11, 3 ],
552 2013: [ 10, 28, /* to */ 10, 31 ],
553 2014: [ 10, 27, /* to */ 10, 31 ],
554 2015: [ 11, 2, /* to */ 11, 7 ],
555 2016: [ 10, 31, /* to */ 11, 4 ],
556 },
557 {
558 name: 'Weihnachtsferien',
559 2010: [ 12, 24, /* to */ 1, 7 ],
560 2011: [ 12, 27, /* to */ 1, 5 ],
561 2012: [ 12, 24, /* to */ 1, 5 ],
562 2013: [ 12, 23, /* to */ 1, 4 ],
563 2014: [ 12, 24, /* to */ 1, 5 ],
564 2015: [ 12, 24, /* to */ 1, 5 ],
565 2016: [ 12, 24, /* to */ 1, 5 ],
566 },
567 ],
568 },
569 'Niedersachsen': {
570 'SH': [
571 {
572 name: 'Winterferien',
573 2010: [ 2, 1, /* to */ 2, 2 ],
574 2011: [ 1, 31, /* to */ 2, 1 ],
575 2012: [ 1, 30, /* to */ 1, 31 ],
576 2013: [ 1, 31, /* to */ 2, 1 ],
577 2014: [ 1, 30, /* to */ 1, 31 ],
578 2015: [ 2, 2, /* to */ 2, 3 ],
579 2016: [ 1, 28, /* to */ 1, 29 ],
580 2017: [ 1, 30, /* to */ 1, 31 ],
581 },
582 {
583 name: 'Osterferien',
584 2010: [ 3, 19, /* to */ 4, 6 ],
585 2011: [ 4, 16, /* to */ 4, 30 ],
586 2012: [ 3, 26, /* to */ 4, 11, 4, 30, /* to */ 4, 30 ],
587 2013: [ 3, 16, /* to */ 4, 2 ],
588 2014: [ 4, 3, /* to */ 4, 22, 5, 2, /* to */ 5, 2 ],
589 2015: [ 3, 25, /* to */ 4, 10 ],
590 2016: [ 3, 18, /* to */ 4, 2 ],
591 2017: [ 4, 10, /* to */ 4, 22 ],
592 },
593 {
594 name: 'Pfingstferien',
595 2010: [ 5, 14, /* to */ 5, 14, 5, 25, /* to */ 5, 25 ],
596 2011: [ 6, 3, /* to */ 6, 3, 6, 14, /* to */ 6, 14 ],
597 2012: [ 5, 18, /* to */ 5, 18, 5, 29, /* to */ 5, 29 ],
598 2013: [ 5, 10, /* to */ 5, 10, 5, 21, /* to */ 5, 21 ],
599 2014: [ 5, 30, /* to */ 5, 30, 6, 10, /* to */ 6, 10 ],
600 2015: [ 5, 15, /* to */ 5, 15, 5, 26, /* to */ 5, 26 ],
601 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 17 ],
602 2017: [ 5, 26, /* to */ 5, 26, 6, 6, /* to */ 6, 6 ],
603 },
604 {
605 name: 'Sommerferien',
606 2010: [ 6, 24, /* to */ 8, 4 ],
607 2011: [ 7, 7, /* to */ 8, 17 ],
608 2012: [ 7, 23, /* to */ 8, 31 ],
609 2013: [ 6, 27, /* to */ 8, 7 ],
610 2014: [ 7, 31, /* to */ 9, 10 ],
611 2015: [ 7, 23, /* to */ 9, 2 ],
612 2016: [ 6, 23, /* to */ 8, 3 ],
613 2017: [ 6, 22, /* to */ 8, 2 ],
614 },
615 {
616 name: 'Herbstferien',
617 2010: [ 10, 9, /* to */ 10, 23 ],
618 2011: [ 10, 17, /* to */ 10, 29 ],
619 2012: [ 10, 22, /* to */ 11, 3 ],
620 2013: [ 10, 4, /* to */ 10, 18 ],
621 2014: [ 10, 27, /* to */ 11, 8 ],
622 2015: [ 10, 19, /* to */ 10, 31 ],
623 2016: [ 10, 4, /* to */ 10, 15 ],
624 },
625 {
626 name: 'Weihnachtsferien',
627 2010: [ 12, 22, /* to */ 1, 5 ],
628 2011: [ 12, 23, /* to */ 1, 4 ],
629 2012: [ 12, 24, /* to */ 1, 5 ],
630 2013: [ 12, 23, /* to */ 1, 3 ],
631 2014: [ 12, 22, /* to */ 1, 5 ],
632 2015: [ 12, 23, /* to */ 1, 6 ],
633 2016: [ 12, 21, /* to */ 1, 6 ],
634 },
635 ],
636 },
637 'Nordrhein-Westfalen': {
638 'SH': [
639 {
640 name: 'Osterferien',
641 2010: [ 3, 27, /* to */ 4, 10 ],
642 2011: [ 4, 18, /* to */ 4, 30 ],
643 2012: [ 4, 2, /* to */ 4, 14 ],
644 2013: [ 3, 25, /* to */ 4, 6 ],
645 2014: [ 4, 14, /* to */ 4, 26 ],
646 2015: [ 3, 30, /* to */ 4, 11 ],
647 2016: [ 3, 21, /* to */ 4, 2 ],
648 2017: [ 4, 10, /* to */ 4, 22 ],
649 },
650 {
651 name: 'Pfingstferien',
652 2010: [ 5, 25, /* to */ 5, 25 ],
653 2012: [ 5, 29, /* to */ 5, 29 ],
654 2013: [ 5, 21, /* to */ 5, 21 ],
655 2014: [ 6, 10, /* to */ 6, 10 ],
656 2015: [ 5, 26, /* to */ 5, 26 ],
657 2016: [ 5, 17, /* to */ 5, 17 ],
658 2017: [ 6, 6, /* to */ 6, 6 ],
659 },
660 {
661 name: 'Sommerferien',
662 2010: [ 7, 15, /* to */ 8, 27 ],
663 2011: [ 7, 25, /* to */ 9, 6 ],
664 2012: [ 7, 9, /* to */ 8, 21 ],
665 2013: [ 7, 22, /* to */ 9, 3 ],
666 2014: [ 7, 7, /* to */ 8, 19 ],
667 2015: [ 6, 29, /* to */ 8, 11 ],
668 2016: [ 7, 11, /* to */ 8, 23 ],
669 2017: [ 7, 17, /* to */ 8, 29 ],
670 },
671 {
672 name: 'Herbstferien',
673 2010: [ 10, 11, /* to */ 10, 23 ],
674 2011: [ 10, 24, /* to */ 11, 5 ],
675 2012: [ 10, 8, /* to */ 10, 20 ],
676 2013: [ 10, 21, /* to */ 11, 2 ],
677 2014: [ 10, 6, /* to */ 10, 18 ],
678 2015: [ 10, 5, /* to */ 10, 17 ],
679 2016: [ 10, 10, /* to */ 10, 21 ],
680 },
681 {
682 name: 'Weihnachtsferien',
683 2010: [ 12, 24, /* to */ 1, 8 ],
684 2011: [ 12, 23, /* to */ 1, 6 ],
685 2012: [ 12, 21, /* to */ 1, 4 ],
686 2013: [ 12, 23, /* to */ 1, 7 ],
687 2014: [ 12, 22, /* to */ 1, 6 ],
688 2015: [ 12, 23, /* to */ 1, 6 ],
689 2016: [ 12, 23, /* to */ 1, 6 ],
690 },
691 ],
692 },
693 'Sachsen': {
694 'SH': [
695 {
696 name: 'Winterferien',
697 2010: [ 2, 8, /* to */ 2, 20 ],
698 2011: [ 2, 12, /* to */ 2, 26 ],
699 2012: [ 2, 13, /* to */ 2, 25 ],
700 2013: [ 2, 4, /* to */ 2, 15 ],
701 2014: [ 2, 17, /* to */ 3, 1 ],
702 2015: [ 2, 9, /* to */ 2, 21 ],
703 2016: [ 2, 8, /* to */ 2, 20 ],
704 2017: [ 2, 13, /* to */ 2, 24 ],
705 },
706 {
707 name: 'Osterferien',
708 2010: [ 4, 1, /* to */ 4, 10 ],
709 2011: [ 4, 22, /* to */ 4, 30 ],
710 2012: [ 4, 6, /* to */ 4, 14 ],
711 2013: [ 3, 29, /* to */ 4, 6 ],
712 2014: [ 4, 18, /* to */ 4, 26 ],
713 2015: [ 4, 2, /* to */ 4, 11 ],
714 2016: [ 3, 25, /* to */ 4, 2 ],
715 2017: [ 4, 13, /* to */ 4, 22 ],
716 },
717 {
718 name: 'Pfingstferien',
719 2010: [ 5, 14, /* to */ 5, 14 ],
720 2011: [ 6, 3, /* to */ 6, 3 ],
721 2012: [ 5, 18, /* to */ 5, 18 ],
722 2013: [ 5, 10, /* to */ 5, 10, 5, 18, /* to */ 5, 22 ],
723 2014: [ 5, 30, /* to */ 5, 30 ],
724 2015: [ 5, 15, /* to */ 5, 15 ],
725 2016: [ 5, 6, /* to */ 5, 6 ],
726 2017: [ 5, 26, /* to */ 5, 26 ],
727 },
728 {
729 name: 'Sommerferien',
730 2010: [ 6, 28, /* to */ 8, 6 ],
731 2011: [ 7, 11, /* to */ 8, 19 ],
732 2012: [ 7, 23, /* to */ 8, 31 ],
733 2013: [ 7, 15, /* to */ 8, 23 ],
734 2014: [ 7, 21, /* to */ 8, 29 ],
735 2015: [ 7, 13, /* to */ 8, 21 ],
736 2016: [ 6, 27, /* to */ 8, 5 ],
737 2017: [ 6, 26, /* to */ 8, 4 ],
738 },
739 {
740 name: 'Herbstferien',
741 2010: [ 10, 4, /* to */ 10, 16 ],
742 2011: [ 10, 17, /* to */ 10, 28 ],
743 2012: [ 10, 22, /* to */ 11, 2 ],
744 2013: [ 10, 21, /* to */ 11, 1 ],
745 2014: [ 10, 20, /* to */ 10, 31 ],
746 2015: [ 10, 12, /* to */ 10, 24 ],
747 2016: [ 10, 3, /* to */ 10, 15 ],
748 },
749 {
750 name: 'Weihnachtsferien',
751 2010: [ 12, 23, /* to */ 1, 1 ],
752 2011: [ 12, 23, /* to */ 1, 2 ],
753 2012: [ 12, 22, /* to */ 1, 2 ],
754 2013: [ 12, 21, /* to */ 1, 3 ],
755 2014: [ 12, 22, /* to */ 1, 3 ],
756 2015: [ 12, 21, /* to */ 1, 2 ],
757 2016: [ 12, 23, /* to */ 1, 2 ],
758 },
759 ],
760 },
761 'Thüringen': {
762 'SH': [
763 {
764 name: 'Winterferien',
765 2010: [ 2, 1, /* to */ 2, 6 ],
766 2011: [ 1, 31, /* to */ 2, 5 ],
767 2012: [ 2, 6, /* to */ 2, 11 ],
768 2013: [ 2, 18, /* to */ 2, 23 ],
769 2014: [ 2, 17, /* to */ 2, 22 ],
770 2015: [ 2, 2, /* to */ 2, 7 ],
771 2016: [ 2, 1, /* to */ 2, 6 ],
772 2017: [ 2, 6, /* to */ 2, 11 ],
773 },
774 {
775 name: 'Osterferien',
776 2010: [ 3, 29, /* to */ 4, 9 ],
777 2011: [ 4, 18, /* to */ 4, 30 ],
778 2012: [ 4, 2, /* to */ 4, 13 ],
779 2013: [ 3, 25, /* to */ 4, 6 ],
780 2014: [ 4, 19, /* to */ 5, 2 ],
781 2015: [ 3, 30, /* to */ 4, 11 ],
782 2016: [ 3, 24, /* to */ 4, 2 ],
783 2017: [ 4, 10, /* to */ 4, 21 ],
784 },
785 {
786 name: 'Sommerferien',
787 2010: [ 6, 24, /* to */ 8, 4 ],
788 2011: [ 7, 11, /* to */ 8, 19 ],
789 2012: [ 7, 23, /* to */ 8, 31 ],
790 2013: [ 7, 15, /* to */ 8, 23 ],
791 2014: [ 7, 21, /* to */ 8, 29 ],
792 2015: [ 7, 13, /* to */ 8, 21 ],
793 2016: [ 6, 27, /* to */ 8, 10 ],
794 2017: [ 6, 26, /* to */ 8, 9 ],
795 },
796 {
797 name: 'Pfingstferien',
798 2011: [ 6, 11, /* to */ 6, 14 ],
799 2012: [ 5, 25, /* to */ 5, 29 ],
800 2013: [ 5, 10, /* to */ 5, 10 ],
801 2014: [ 5, 30, /* to */ 5, 30 ],
802 2015: [ 5, 15, /* to */ 5, 15 ],
803 2016: [ 5, 6, /* to */ 5, 6 ],
804 2017: [ 5, 26, /* to */ 5, 26 ],
805 },
806 {
807 name: 'Herbstferien',
808 2010: [ 10, 9, /* to */ 10, 23 ],
809 2011: [ 10, 17, /* to */ 10, 28 ],
810 2012: [ 10, 22, /* to */ 11, 3 ],
811 2013: [ 10, 21, /* to */ 11, 2 ],
812 2014: [ 10, 6, /* to */ 10, 18 ],
813 2015: [ 10, 5, /* to */ 10, 17 ],
814 2016: [ 10, 10, /* to */ 10, 22 ],
815 },
816 {
817 name: 'Weihnachtsferien',
818 2010: [ 12, 23, /* to */ 1, 1 ],
819 2011: [ 12, 23, /* to */ 1, 1 ],
820 2012: [ 12, 24, /* to */ 1, 5 ],
821 2013: [ 12, 23, /* to */ 1, 4 ],
822 2014: [ 12, 22, /* to */ 1, 3 ],
823 2015: [ 12, 23, /* to */ 1, 2 ],
824 2016: [ 12, 23, /* to */ 12, 31 ],
825 },
826 ],
827 },
828 'Hamburg': {
829 'SH': [
830 {
831 name: 'Winterferien',
832 2010: [ 1, 29, /* to */ 1, 29 ],
833 2011: [ 1, 31, /* to */ 1, 31 ],
834 2012: [ 1, 30, /* to */ 1, 30 ],
835 2013: [ 2, 1, /* to */ 2, 1 ],
836 2014: [ 1, 31, /* to */ 1, 31 ],
837 2015: [ 1, 30, /* to */ 1, 30 ],
838 2016: [ 1, 29, /* to */ 1, 29 ],
839 2017: [ 1, 30, /* to */ 1, 30 ],
840 },
841 {
842 name: 'Osterferien',
843 2010: [ 3, 8, /* to */ 3, 20 ],
844 2011: [ 3, 7, /* to */ 3, 18 ],
845 2012: [ 3, 5, /* to */ 3, 16 ],
846 2013: [ 3, 4, /* to */ 3, 15 ],
847 2014: [ 3, 3, /* to */ 3, 14 ],
848 2015: [ 3, 2, /* to */ 3, 13 ],
849 2016: [ 3, 7, /* to */ 3, 18 ],
850 2017: [ 3, 6, /* to */ 3, 17 ],
851 },
852 {
853 name: 'Pfingstferien',
854 2010: [ 5, 14, /* to */ 5, 22 ],
855 2011: [ 4, 26, /* to */ 4, 29, 6, 3, /* to */ 6, 3 ],
856 2012: [ 4, 30, /* to */ 5, 4, 5, 18, /* to */ 5, 18 ],
857 2013: [ 5, 2, /* to */ 5, 10 ],
858 2014: [ 4, 28, /* to */ 5, 2, 5, 30, /* to */ 5, 30 ],
859 2015: [ 5, 11, /* to */ 5, 15 ],
860 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 20 ],
861 2017: [ 5, 22, /* to */ 5, 26 ],
862 },
863 {
864 name: 'Sommerferien',
865 2010: [ 7, 8, /* to */ 8, 18 ],
866 2011: [ 6, 30, /* to */ 8, 10 ],
867 2012: [ 6, 21, /* to */ 8, 1 ],
868 2013: [ 6, 20, /* to */ 7, 31 ],
869 2014: [ 7, 10, /* to */ 8, 20 ],
870 2015: [ 7, 16, /* to */ 8, 26 ],
871 2016: [ 7, 21, /* to */ 8, 31 ],
872 2017: [ 7, 20, /* to */ 8, 30 ],
873 },
874 {
875 name: 'Herbstferien',
876 2010: [ 10, 4, /* to */ 10, 15 ],
877 2011: [ 10, 4, /* to */ 10, 14 ],
878 2012: [ 10, 1, /* to */ 10, 12 ],
879 2013: [ 9, 30, /* to */ 10, 11 ],
880 2014: [ 10, 13, /* to */ 10, 24 ],
881 2015: [ 10, 19, /* to */ 10, 30 ],
882 2016: [ 10, 17, /* to */ 10, 28 ],
883 },
884 {
885 name: 'Weihnachtsferien',
886 2010: [ 12, 23, /* to */ 1, 3 ],
887 2011: [ 12, 27, /* to */ 1, 6 ],
888 2012: [ 12, 21, /* to */ 1, 4 ],
889 2013: [ 12, 19, /* to */ 1, 3 ],
890 2014: [ 12, 22, /* to */ 1, 6 ],
891 2015: [ 12, 21, /* to */ 1, 1 ],
892 2016: [ 12, 27, /* to */ 1, 6 ],
893 },
894 ],
895 },
896 'Sachsen-Anhalt': {
897 'SH': [
898 {
899 name: 'Winterferien',
900 2010: [ 2, 8, /* to */ 2, 13 ],
901 2011: [ 2, 5, /* to */ 2, 12 ],
902 2012: [ 2, 4, /* to */ 2, 11 ],
903 2013: [ 2, 1, /* to */ 2, 8 ],
904 2014: [ 2, 1, /* to */ 2, 12 ],
905 2015: [ 2, 2, /* to */ 2, 14 ],
906 2016: [ 2, 1, /* to */ 2, 10 ],
907 2017: [ 2, 4, /* to */ 2, 11 ],
908 },
909 {
910 name: 'Osterferien',
911 2010: [ 3, 29, /* to */ 4, 9 ],
912 2011: [ 4, 18, /* to */ 4, 27 ],
913 2012: [ 4, 2, /* to */ 4, 7 ],
914 2013: [ 3, 25, /* to */ 3, 30 ],
915 2014: [ 4, 14, /* to */ 4, 17 ],
916 2015: [ 4, 2, /* to */ 4, 2 ],
917 2016: [ 3, 24, /* to */ 3, 24 ],
918 2017: [ 4, 10, /* to */ 4, 13 ],
919 },
920 {
921 name: 'Pfingstferien',
922 2010: [ 5, 14, /* to */ 5, 22 ],
923 2011: [ 6, 14, /* to */ 6, 18 ],
924 2012: [ 5, 18, /* to */ 5, 25 ],
925 2013: [ 5, 10, /* to */ 5, 18 ],
926 2014: [ 5, 30, /* to */ 6, 7 ],
927 2015: [ 5, 15, /* to */ 5, 23 ],
928 2016: [ 5, 6, /* to */ 5, 14 ],
929 2017: [ 5, 26, /* to */ 5, 26 ],
930 },
931 {
932 name: 'Sommerferien',
933 2010: [ 6, 24, /* to */ 8, 4 ],
934 2011: [ 7, 11, /* to */ 8, 24 ],
935 2012: [ 7, 23, /* to */ 9, 5 ],
936 2013: [ 7, 15, /* to */ 8, 28 ],
937 2014: [ 7, 21, /* to */ 9, 3 ],
938 2015: [ 7, 13, /* to */ 8, 26 ],
939 2016: [ 6, 27, /* to */ 8, 10 ],
940 2017: [ 6, 26, /* to */ 8, 9 ],
941 },
942 {
943 name: 'Herbstferien',
944 2010: [ 10, 18, /* to */ 10, 23 ],
945 2011: [ 10, 17, /* to */ 10, 22 ],
946 2012: [ 10, 29, /* to */ 11, 2 ],
947 2013: [ 10, 21, /* to */ 10, 25 ],
948 2014: [ 10, 27, /* to */ 10, 30 ],
949 2015: [ 10, 17, /* to */ 10, 24 ],
950 2016: [ 10, 4, /* to */ 10, 15 ],
951 },
952 {
953 name: 'Weihnachtsferien',
954 2010: [ 12, 22, /* to */ 1, 5 ],
955 2011: [ 12, 22, /* to */ 1, 7 ],
956 2012: [ 12, 19, /* to */ 1, 4 ],
957 2013: [ 12, 21, /* to */ 1, 3 ],
958 2014: [ 12, 22, /* to */ 1, 5 ],
959 2015: [ 12, 21, /* to */ 1, 5 ],
960 2016: [ 12, 19, /* to */ 1, 2 ],
961 },
962 ],
963 },
964 'Rheinland-Pfalz': {
965 'SH': [
966 {
967 name: 'Osterferien',
968 2010: [ 3, 26, /* to */ 4, 9 ],
969 2011: [ 4, 18, /* to */ 4, 29 ],
970 2012: [ 3, 29, /* to */ 4, 13 ],
971 2013: [ 3, 20, /* to */ 4, 5 ],
972 2014: [ 4, 11, /* to */ 4, 25 ],
973 2015: [ 3, 26, /* to */ 4, 10 ],
974 2016: [ 3, 18, /* to */ 4, 1 ],
975 2017: [ 4, 10, /* to */ 4, 21 ],
976 },
977 {
978 name: 'Sommerferien',
979 2010: [ 7, 5, /* to */ 8, 13 ],
980 2011: [ 6, 27, /* to */ 8, 5 ],
981 2012: [ 7, 2, /* to */ 8, 10 ],
982 2013: [ 7, 8, /* to */ 8, 16 ],
983 2014: [ 7, 28, /* to */ 9, 5 ],
984 2015: [ 7, 27, /* to */ 9, 4 ],
985 2016: [ 7, 18, /* to */ 8, 26 ],
986 2017: [ 7, 3, /* to */ 8, 11 ],
987 },
988 {
989 name: 'Herbstferien',
990 2010: [ 10, 11, /* to */ 10, 22 ],
991 2011: [ 10, 4, /* to */ 10, 14 ],
992 2012: [ 10, 1, /* to */ 10, 12 ],
993 2013: [ 10, 4, /* to */ 10, 18 ],
994 2014: [ 10, 20, /* to */ 10, 31 ],
995 2015: [ 10, 19, /* to */ 10, 30 ],
996 2016: [ 10, 10, /* to */ 10, 21 ],
997 },
998 {
999 name: 'Weihnachtsferien',
1000 2010: [ 12, 23, /* to */ 1, 7 ],
1001 2011: [ 12, 22, /* to */ 1, 6 ],
1002 2012: [ 12, 20, /* to */ 1, 4 ],
1003 2013: [ 12, 23, /* to */ 1, 7 ],
1004 2014: [ 12, 22, /* to */ 1, 7 ],
1005 2015: [ 12, 23, /* to */ 1, 8 ],
1006 2016: [ 12, 22, /* to */ 1, 6 ],
1007 },
1008 ],
1009 },
1010 'Brandenburg': {
1011 'SH': [
1012 {
1013 name: 'Winterferien',
1014 2010: [ 2, 1, /* to */ 2, 6 ],
1015 2011: [ 1, 31, /* to */ 2, 5 ],
1016 2012: [ 1, 30, /* to */ 2, 4 ],
1017 2013: [ 2, 4, /* to */ 2, 9 ],
1018 2014: [ 2, 3, /* to */ 2, 8 ],
1019 2015: [ 2, 2, /* to */ 2, 7 ],
1020 2016: [ 2, 1, /* to */ 2, 6 ],
1021 2017: [ 1, 30, /* to */ 2, 4 ],
1022 },
1023 {
1024 name: 'Osterferien',
1025 2010: [ 3, 31, /* to */ 4, 10 ],
1026 2011: [ 4, 20, /* to */ 4, 30 ],
1027 2012: [ 4, 4, /* to */ 4, 14, 4, 30, /* to */ 4, 30 ],
1028 2013: [ 3, 27, /* to */ 4, 6 ],
1029 2014: [ 4, 16, /* to */ 4, 26, 5, 2, /* to */ 5, 2 ],
1030 2015: [ 4, 1, /* to */ 4, 11 ],
1031 2016: [ 3, 23, /* to */ 4, 2 ],
1032 2017: [ 4, 12, /* to */ 4, 22 ],
1033 },
1034 {
1035 name: 'Pfingstferien',
1036 2010: [ 5, 14, /* to */ 5, 14 ],
1037 2011: [ 6, 3, /* to */ 6, 3 ],
1038 2012: [ 5, 18, /* to */ 5, 18 ],
1039 2013: [ 5, 10, /* to */ 5, 10 ],
1040 2014: [ 5, 30, /* to */ 5, 30 ],
1041 2015: [ 5, 15, /* to */ 5, 15 ],
1042 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 17 ],
1043 2017: [ 5, 26, /* to */ 5, 26 ],
1044 },
1045 {
1046 name: 'Sommerferien',
1047 2010: [ 7, 8, /* to */ 8, 21 ],
1048 2011: [ 6, 30, /* to */ 8, 13 ],
1049 2012: [ 6, 21, /* to */ 8, 3 ],
1050 2013: [ 6, 20, /* to */ 8, 2 ],
1051 2014: [ 7, 10, /* to */ 8, 22 ],
1052 2015: [ 7, 16, /* to */ 8, 28 ],
1053 2016: [ 7, 21, /* to */ 9, 3 ],
1054 2017: [ 7, 20, /* to */ 9, 1 ],
1055 },
1056 {
1057 name: 'Herbstferien',
1058 2010: [ 10, 11, /* to */ 10, 23 ],
1059 2011: [ 10, 4, /* to */ 10, 14 ],
1060 2012: [ 10, 1, /* to */ 10, 13 ],
1061 2013: [ 9, 30, /* to */ 10, 12, 11, 1, /* to */ 11, 1 ],
1062 2014: [ 10, 20, /* to */ 11, 1 ],
1063 2015: [ 10, 19, /* to */ 10, 30 ],
1064 2016: [ 10, 17, /* to */ 10, 28 ],
1065 },
1066 {
1067 name: 'Weihnachtsferien',
1068 2010: [ 12, 23, /* to */ 1, 1 ],
1069 2011: [ 12, 23, /* to */ 1, 3 ],
1070 2012: [ 12, 24, /* to */ 1, 4 ],
1071 2013: [ 12, 23, /* to */ 1, 3 ],
1072 2014: [ 12, 22, /* to */ 1, 2 ],
1073 2015: [ 12, 23, /* to */ 1, 2 ],
1074 2016: [ 12, 23, /* to */ 1, 3 ],
1075 },
1076 ],
1077 },
1078 },
1079 'at': {
1080 'PH': { // http://de.wikipedia.org/wiki/Feiertage_in_%C3%96sterreich
1081 'Neujahrstag' : [ 1, 1 ],
1082 'Heilige Drei Könige' : [ 1, 6 ],
1083 // 'Josef' : [ 3, 19, [ 'Kärnten', 'Steiermark', 'Tirol', 'Vorarlberg' ] ],
1084 // 'Karfreitag' : [ 'easter', -2 ],
1085 'Ostermontag' : [ 'easter', 1 ],
1086 'Staatsfeiertag' : [ 5, 1 ],
1087 // 'Florian' : [ 5, 4, [ 'Oberösterreich' ] ],
1088 'Christi Himmelfahrt' : [ 'easter', 39 ],
1089 'Pfingstmontag' : [ 'easter', 50 ],
1090 'Fronleichnam' : [ 'easter', 60 ],
1091 'Mariä Himmelfahrt' : [ 8, 15 ],
1092 // 'Rupert' : [ 9, 24, [ 'Salzburg' ] ],
1093 // 'Tag der Volksabstimmung' : [ 10, 10, [ 'Kärnten' ] ],
1094 'Nationalfeiertag' : [ 10, 26 ],
1095 'Allerheiligen' : [ 11, 1 ],
1096 // 'Martin' : [ 11, 11, [ 'Burgenland' ] ],
1097 // 'Leopold' : [ 11, 15, [ 'Niederösterreich', 'Wien' ] ],
1098 'Mariä Empfängnis' : [ 12, 8 ],
1099 // 'Heiliger Abend' : [ 12, 24 ],
1100 'Christtag' : [ 12, 25 ],
1101 'Stefanitag' : [ 12, 26 ],
1102 // 'Silvester' : [ 12, 31 ],
1103 },
1104 },
1105 'ca': {
1106 'PH': { // https://en.wikipedia.org/wiki/Public_holidays_in_Canada
1107 "New Year's Day" : [ 1, 1 ],
1108 "Good Friday" : [ 'easter', -2 ],
1109 "Canada Day" : [ 'canadaDay', 0 ],
1110 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1111 "Christmas Day" : [ 12, 25 ]
1112 },
1113 'Alberta': {
1114 'PH': {
1115 "New Year's Day" : [ 1, 1 ],
1116 "Alberta Family Day" : [ 'firstFebruaryMonday', 14 ],
1117 "Good Friday" : [ 'easter', -2 ],
1118 "Easter Monday" : [ 'easter', 1 ],
1119 "Victoria Day" : [ 'victoriaDay', 0 ],
1120 "Canada Day" : [ 'canadaDay', 0 ],
1121 "Heritage Day" : [ 'firstAugustMonday', 0 ],
1122 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1123 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1124 "Remembrance Day" : [ 11, 11 ],
1125 "Christmas Day" : [ 12, 25 ],
1126 "Boxing Day" : [ 12, 26 ]
1127 },
1128 },
1129 'British Columbia': {
1130 'PH': {
1131 "New Year's Day" : [ 1, 1 ],
1132 "Family Day" : [ 'firstFebruaryMonday', 7 ],
1133 "Good Friday" : [ 'easter', -2 ],
1134 "Victoria Day" : [ 'victoriaDay', 0 ],
1135 "Canada Day" : [ 'canadaDay', 0 ],
1136 "British Columbia Day" : [ 'firstAugustMonday', 0 ],
1137 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1138 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1139 "Remembrance Day" : [ 11, 11 ],
1140 "Christmas Day" : [ 12, 25 ]
1141 },
1142 },
1143 'Manitoba': {
1144 'PH': {
1145 "New Year's Day" : [ 1, 1 ],
1146 "Louis Riel Day" : [ 'firstFebruaryMonday', 14 ],
1147 "Good Friday" : [ 'easter', -2 ],
1148 "Victoria Day" : [ 'victoriaDay', 0 ],
1149 "Canada Day" : [ 'canadaDay', 0 ],
1150 "Civic Holiday" : [ 'firstAugustMonday', 0 ],
1151 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1152 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1153 "Remembrance Day" : [ 11, 11 ],
1154 "Christmas Day" : [ 12, 25 ]
1155 },
1156 },
1157 'New Brunswick': {
1158 'PH': {
1159 "New Year's Day" : [ 1, 1 ],
1160 "Good Friday" : [ 'easter', -2 ],
1161 "Victoria Day" : [ 'victoriaDay', 0 ],
1162 "Canada Day" : [ 'canadaDay', 0 ],
1163 "New Brunswick Day" : [ 'firstAugustMonday', 0 ],
1164 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1165 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1166 "Remembrance Day" : [ 11, 11 ],
1167 "Christmas Day" : [ 12, 25 ],
1168 "Boxing Day" : [ 12, 26 ]
1169 },
1170 },
1171 'Newfoundland and Labrador': {
1172 'PH': {
1173 "New Year's Day" : [ 1, 1 ],
1174 "Saint Patrick's Day" : [ 3, 17 ],
1175 "Good Friday" : [ 'easter', -2 ],
1176 "Saint George's Day" : [ 4, 23 ],
1177 "Discovery Day" : [ 6, 24 ],
1178 "Memorial Day" : [ 7, 1 ],
1179 "Orangemen's Day" : [ 7, 12 ],
1180 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1181 "Armistice Day" : [ 11, 11 ],
1182 "Christmas Day" : [ 12, 25 ]
1183 },
1184 },
1185 'Northwest Territories': {
1186 'PH': {
1187 "New Year's Day" : [ 1, 1 ],
1188 "Good Friday" : [ 'easter', -2 ],
1189 "Victoria Day" : [ 'victoriaDay', 0 ],
1190 "National Aboriginal Day" : [ 6, 21 ],
1191 "Canada Day" : [ 'canadaDay', 0 ],
1192 "Civic Holiday" : [ 'firstAugustMonday', 0 ],
1193 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1194 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1195 "Remembrance Day" : [ 11, 11 ],
1196 "Christmas Day" : [ 12, 25 ]
1197 },
1198 },
1199 'Nova Scotia': {
1200 'PH': {
1201 "New Year's Day" : [ 1, 1 ],
1202 "Good Friday" : [ 'easter', -2 ],
1203 "Victoria Day" : [ 'victoriaDay', 0 ],
1204 "Canada Day" : [ 'canadaDay', 0 ],
1205 "Natal Day" : [ 'firstAugustMonday', 0 ],
1206 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1207 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1208 "Remembrance Day" : [ 11, 11 ],
1209 "Christmas Day" : [ 12, 25 ],
1210 "Boxing Day" : [ 12, 26 ]
1211 },
1212 },
1213 'Nunavut': {
1214 'PH': {
1215 "New Year's Day" : [ 1, 1 ],
1216 "Good Friday" : [ 'easter', -2 ],
1217 "Victoria Day" : [ 'victoriaDay', 0 ],
1218 "Canada Day" : [ 'canadaDay', 0 ],
1219 "Nunavut Day" : [ 7, 9 ],
1220 "Civic Holiday" : [ 'firstAugustMonday', 0 ],
1221 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1222 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1223 "Remembrance Day" : [ 11, 11 ],
1224 "Christmas Day" : [ 12, 25 ]
1225 },
1226 },
1227 'Ontario': {
1228 'PH': {
1229 "New Year's Day" : [ 1, 1 ],
1230 "Family Day" : [ 'firstFebruaryMonday', 14 ],
1231 "Good Friday" : [ 'easter', -2 ],
1232 "Victoria Day" : [ 'victoriaDay', 0 ],
1233 "Canada Day" : [ 'canadaDay', 0 ],
1234 "August Civic Public Holiday" : [ 'firstAugustMonday', 0 ],
1235 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1236 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1237 "Remembrance Day" : [ 11, 11 ],
1238 "Christmas Day" : [ 12, 25 ],
1239 "Boxing Day" : [ 12, 26 ]
1240 },
1241 },
1242 'Prince Edward Island': {
1243 'PH': {
1244 "New Year's Day" : [ 1, 1 ],
1245 "Islander Day" : [ 'firstFebruaryMonday', 14 ],
1246 "Good Friday" : [ 'easter', -2 ],
1247 "Easter Monday" : [ 'easter', 1 ],
1248 "Victoria Day" : [ 'victoriaDay', 0 ],
1249 "Canada Day" : [ 'canadaDay', 0 ],
1250 "Civic Holiday" : [ 'firstAugustMonday', 0 ],
1251 "Gold Cup Parade Day" : [ 'firstAugustMonday', 18 ],
1252 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1253 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1254 "Remembrance Day" : [ 11, 11 ],
1255 "Christmas Day" : [ 12, 25 ],
1256 "Boxing Day" : [ 12, 26 ]
1257 },
1258 },
1259 'Quebec': {
1260 'PH': {
1261 "Jour de l'an" : [ 1, 1 ],
1262 "Vendredi saint" : [ 'easter', -2 ],
1263 "Lundi de Pâques" : [ 'easter', 1 ],
1264 "Journée nationale des patriotes" : [ 'victoriaDay', 0 ],
1265 "Fête nationale du Québec" : [ 6, 24 ],
1266 "Fête du Canada" : [ 'canadaDay', 0 ],
1267 "Fête du Travail" : [ 'firstSeptemberMonday', 0 ],
1268 "Jour de l'Action de grâce" : [ 'firstOctoberMonday', 7 ],
1269 "Noël" : [ 12, 25 ]
1270 },
1271 },
1272 'Saskatchewan': {
1273 'PH': {
1274 "New Year's Day" : [ 1, 1 ],
1275 "Family Day" : [ 'firstFebruaryMonday', 14 ],
1276 "Good Friday" : [ 'easter', -2 ],
1277 "Victoria Day" : [ 'victoriaDay', 0 ],
1278 "Canada Day" : [ 'canadaDay', 0 ],
1279 "Saskatchewan Day" : [ 'firstAugustMonday', 0 ],
1280 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1281 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1282 "Remembrance Day" : [ 11, 11 ],
1283 "Christmas Day" : [ 12, 25 ]
1284 },
1285 },
1286 'Yukon': {
1287 'PH': {
1288 "New Year's Day" : [ 1, 1 ],
1289 "Heritage Day" : [ 'lastFebruarySunday', -2 ],
1290 "Good Friday" : [ 'easter', -2 ],
1291 "Easter Monday" : [ 'easter', 1 ],
1292 "Victoria Day" : [ 'victoriaDay', 0 ],
1293 "Canada Day" : [ 'canadaDay', 0 ],
1294 "Discovery Day" : [ 'firstAugustMonday', 14 ],
1295 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1296 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1297 "Remembrance Day" : [ 11, 11 ],
1298 "Christmas Day" : [ 12, 25 ],
1299 "Boxing Day" : [ 12, 26 ]
1300 },
1301 },
1302 },
1303 'ua': {
1304 'PH': { // http://uk.wikipedia.org/wiki/%D0%A1%D0%B2%D1%8F%D1%82%D0%B0_%D1%82%D0%B0_%D0%BF%D0%B0%D0%BC%27%D1%8F%D1%82%D0%BD%D1%96_%D0%B4%D0%BD%D1%96_%D0%B2_%D0%A3%D0%BA%D1%80%D0%B0%D1%97%D0%BD%D1%96
1305 "Новий рік" : [ 1, 1 ],
1306 "Різдво" : [ 1, 7 ],
1307 "Міжнародний жіночий день" : [ 3, 8 ],
1308 "Великдень" : [ 'orthodox easter', 1 ],
1309 "День Праці 1" : [ 5, 1 ],
1310 "День Праці 2" : [ 5, 2 ],
1311 "День Перемоги" : [ 5, 9 ],
1312 "День Конституції України" : [ 6, 28 ],
1313 "День Незалежності України" : [ 8, 24 ],
1314 }
1315 },
1316 'si': {
1317 'PH': { // http://www.vlada.si/o_sloveniji/politicni_sistem/prazniki/
1318 'novo leto' : [ 1, 1 ],
1319 'Prešernov dan, slovenski kulturni praznik' : [ 2, 8 ],
1320 'velikonočna nedelja' : [ 'easter', 0 ],
1321 'velikonočni ponedeljek' : [ 'easter', 1 ],
1322 'dan upora proti okupatorju' : [ 4, 27 ],
1323 'praznik dela 1' : [ 5, 1 ],
1324 'praznik dela 2' : [ 5, 2 ],
1325 'binkoštna nedelja - binkošti' : [ 'easter', 49 ],
1326 'dan državnosti' : [ 6, 25 ],
1327 'Marijino vnebovzetje' : [ 8, 15 ],
1328 'dan reformacije' : [ 10, 31 ],
1329 'dan spomina na mrtve' : [ 11, 1 ],
1330 'božič' : [ 12, 25 ],
1331 'dan samostojnosti in enotnosti' : [ 12, 26 ],
1332 },
1333 },
1334 };
1335 // }}}
1336
1337 // error correction {{{
1338 // Taken form http://www.netzwolf.info/j/osm/time_domain.js
1339 // Credits go to Netzwolf
1340 //
1341 // Key to word_error_correction is the token name except wrong_words
1342 var word_error_correction = {
1343 wrong_words: {
1344 'Assuming "<ok>" for "<ko>"': {
1345 spring: 'Mar-May',
1346 summer: 'Jun-Aug',
1347 autumn: 'Sep-Nov',
1348 winter: 'Dec-Feb',
1349 // morning: '08:00-12:00',
1350 // evening: '13:00-18:00',
1351 '_': '-',
1352 'daytime': 'sunrise-sunset',
1353 }, 'Bitte benutze die englische Schreibweise "<ok>" für "<ko>".': {
1354 sommer: 'summer',
1355 'werktag': 'Mo-Fr',
1356 'werktags': 'Mo-Fr',
1357 }, 'Bitte benutze "<ok>" für "<ko>". Beispiel: "Mo-Fr 08:00-12:00; Tu off"': {
1358 ruhetag: 'off',
1359 ruhetage: 'off',
1360 geschlossen: 'off',
1361 ausser: 'off',
1362 außer: 'off',
1363 }, 'Neem de engelse afkorting "<ok>" voor "<ko>" alstublieft.': {
1364 'gesloten': 'off',
1365 'feestdag': 'PH',
1366 }, 'Assuming "<ok>" for "<ko>". Please avoid using "workday": http://wiki.openstreetmap.org/wiki/Talk:Key:opening_hours#need_syntax_for_holidays_and_workingdays': {
1367 // // Used around 260 times but the problem is, that work day might be different in other countries.
1368 'wd': 'Mo-Fr',
1369 'weekday': 'Mo-Fr',
1370 'weekdays': 'Mo-Fr',
1371 'vardagar': 'Mo-Fr',
1372 }, 'Please use notation something like "Mo off" instead "<ko>".': {
1373 except: 'off',
1374 }, 'Please ommit "<ko>" or use a colon instead: "12:00-14:00".': {
1375 h: '',
1376 }, 'Please ommit "<ko>".': {
1377 season: '',
1378 hs: '',
1379 hrs: '',
1380 hours: '',
1381 }, 'Please ommit "<ko>". The key must not be in the value.': {
1382 'opening_hours=': '',
1383 }, 'Please ommit "<ko>". You might want to express open end which can be specified as "12:00+" for example.': {
1384 from: '',
1385 }, 'You can use notation "<ok>" for "<ko>". You might want to express open end which can be specified as "12:00+" for example.': {
1386 '-late': '+',
1387 }, 'Please use notation "<ok>" for "<ko>". If the times are unsure or vary consider a comment e.g. 12:00-14:00 "only on sunshine".': {
1388 '~': '-',
1389 '~': '-',
1390 }, 'Please use notation "<ok>" for "<ko>". Fallback rule: 12:00-14:00 || "call us"': {
1391 'otherwise': '||',
1392 }, 'You can use notation "<ok>" for "<ko>" temporally if the syntax will still be valid.': {
1393 '?': 'unknown "please add this if known"',
1394 }, 'Please use notation "<ok>" for "<ko>".': {
1395 '→': '-',
1396 '–': '-',
1397 '−': '-',
1398 '=': '-',
1399 'ー': '-',
1400 to: '-',
1401 'до': '-',
1402 a: '-', // language unknown
1403 as: '-', // language unknown
1404 'á': '-', // language unknown
1405 'ás': '-', // language unknown
1406 'à': '-', // language unknown
1407 'às': '-', // language unknown
1408 'ate': '-', // language unknown
1409 'till': '-',
1410 'til': '-',
1411 'until': '-',
1412 'through': '-',
1413 and: ',',
1414 '&': ',',
1415 ':': ':',
1416 '°°': ':00',
1417 'daily': 'Mo-Su',
1418 'everyday': 'Mo-Su',
1419 'every day': 'Mo-Su',
1420 always: '24/7',
1421 nonstop: '24/7',
1422 '24x7': '24/7',
1423 'anytime': '24/7',
1424 'all day': '24/7',
1425 'all days': 'Mo-Su',
1426 'every day': 'Mo-Su',
1427 '7days': 'Mo-Su',
1428 '7j/7': 'Mo-Su', // I guess that it means that
1429 '7/7': 'Mo-Su', // I guess that it means that
1430 '7 days': 'Mo-Su',
1431 '7 days a week': 'Mo-Su',
1432 'midnight': '00:00',
1433 holiday: 'PH',
1434 holidays: 'PH',
1435 'public holidays': 'PH',
1436 'public holiday': 'PH',
1437 // summerholiday: 'SH',
1438 // summerholidays: 'SH',
1439 weekend: 'Sa,Su',
1440 weekends: 'Sa,Su',
1441 'daylight': 'sunrise-sunset',
1442 'оff': 'off', // Russian o
1443 }, 'Please use time format in 24 hours notation ("<ko>"). If PM is used you might have to convert the hours to the 24 hours notation.': {
1444 'pm': '',
1445 'рм': '',
1446 'am': '',
1447 'ам': '',
1448 }, 'Bitte verzichte auf "<ko>".': {
1449 'uhr': '',
1450 'geöffnet': '',
1451 }, 'Bitte verzichte auf "<ko>". Sie möchten eventuell eine Öffnungszeit ohne vorgegebenes Ende angeben. Beispiel: "12:00+"': {
1452 ab: '',
1453 von: '',
1454 }, 'Bitte benutze die Schreibweise "<ok>" für "<ko>".': {
1455 bis: '-',
1456 'täglich': 'Mo-Su',
1457 }, 'Bitte benutze die Schreibweise "<ok>" als Ersatz für "und" bzw. "u.".': {
1458 und: ',',
1459 u: ',',
1460 }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>".': {
1461 feiertag: 'PH',
1462 feiertags: 'PH',
1463 feiertage: 'PH',
1464 feiertagen: 'PH'
1465 }, 'S\'il vous plaît utiliser "<ok>" pour "<ko>".': {
1466 'fermé': 'off',
1467 'et': ',',
1468 'à': '-',
1469 'jours fériés': 'PH',
1470 }, 'Neem de engelse afkorting "<ok>" voor "<ko>" alstublieft.': {
1471 feestdag: 'PH',
1472 feestdagen: 'PH',
1473 }
1474 },
1475
1476 month: {
1477 'default': {
1478 jan: 0,
1479 feb: 1,
1480 mar: 2,
1481 apr: 3,
1482 may: 4,
1483 jun: 5,
1484 jul: 6,
1485 aug: 7,
1486 sep: 8,
1487 oct: 9,
1488 nov: 10,
1489 dec: 11,
1490 }, 'Please use the English abbreviation "<ok>" for "<ko>".': {
1491 'jänner': 0, // Austria
1492 january: 0,
1493 february: 1,
1494 march: 2,
1495 april: 3,
1496 // may: 4,
1497 june: 5,
1498 july: 6,
1499 august: 7,
1500 september: 8,
1501 sept: 8,
1502 october: 9,
1503 november: 10,
1504 december: 11,
1505 }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>".': {
1506 januar: 0,
1507 februar: 1,
1508 'märz': 2,
1509 maerz: 2,
1510 mai: 4,
1511 juni: 5,
1512 juli: 6,
1513 okt: 9,
1514 oktober: 9,
1515 dez: 11,
1516 dezember: 11,
1517 }, 'S\'il vous plaît utiliser l\'abréviation "<ok>" pour "<ko>".': {
1518 janvier: 0,
1519 février: 1,
1520 fév: 1,
1521 mars: 2,
1522 avril: 3,
1523 avr: 3,
1524 mai: 4,
1525 juin: 5,
1526 juillet: 6,
1527 août: 7,
1528 aoû: 7,
1529 septembre: 8,
1530 octobre: 9,
1531 novembre: 10,
1532 décembre: 11,
1533 }, 'Neem de engelse afkorting "<ok>" voor "<ko>" alstublieft.': {
1534 januari: 0,
1535 februari: 1,
1536 maart: 2,
1537 mei: 4,
1538 augustus: 7,
1539 }
1540 },
1541
1542 weekday: { // good source: http://www.omniglot.com/language/time/days.htm
1543 'default': {
1544 su: 0,
1545 mo: 1,
1546 tu: 2,
1547 we: 3,
1548 th: 4,
1549 fr: 5,
1550 sa: 6,
1551 }, 'Assuming "<ok>" for "<ko>"': {
1552 m: 1,
1553 w: 3,
1554 f: 5,
1555 }, 'Please use the abbreviation "<ok>" for "<ko>".': {
1556 sun: 0,
1557 sunday: 0,
1558 sundays: 0,
1559 mon: 1,
1560 monday: 1,
1561 mondays: 1,
1562 tue: 2,
1563 tuesday: 2,
1564 tuesdays: 2,
1565 wed: 3,
1566 wednesday: 3,
1567 wednesdays: 3,
1568 thu: 4,
1569 thur: 4,
1570 thursday: 4,
1571 thursdays: 4,
1572 fri: 5,
1573 friday: 5,
1574 fridays: 5,
1575 sat: 6,
1576 saturday: 6,
1577 saturdays: 6,
1578 }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>". Could also mean Saturday in Polish …': {
1579 so: 0,
1580 }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>".': {
1581 son: 0,
1582 sonntag: 0,
1583 'sonn-': 0,
1584 sonntags: 0,
1585 montag: 1,
1586 montags: 1,
1587 di: 2,
1588 die: 2,
1589 dienstag: 2,
1590 dienstags: 2,
1591 mi: 3,
1592 mit: 3,
1593 mittwoch: 3,
1594 mittwochs: 3,
1595 'do': 4,
1596 don: 4,
1597 donnerstag: 4,
1598 donnerstags: 4,
1599 fre: 5,
1600 freitag: 5,
1601 freitags: 5,
1602 sam: 6,
1603 samstag: 6,
1604 samstags: 6,
1605 }, 'S\'il vous plaît utiliser l\'abréviation "<ok>" pour "<ko>".': {
1606 dim: 0,
1607 dimanche: 0,
1608 lu: 1,
1609 lun: 1,
1610 lundi: 1,
1611 mardi: 2,
1612 mer: 3,
1613 mercredi: 3,
1614 je: 4,
1615 jeu: 4,
1616 jeudi: 4,
1617 ve: 5,
1618 ven: 5,
1619 vendredi: 5,
1620 samedi: 6,
1621 }, 'Neem de engelse afkorting "<ok>" voor "<ko>" alstublieft.': {
1622 zo: 0,
1623 zon: 0,
1624 zontag: 0, // correct?
1625 zondag: 0,
1626 maandag: 1,
1627 din: 2,
1628 dinsdag: 2,
1629 wo: 3,
1630 woe: 3,
1631 woensdag: 3,
1632 donderdag: 4,
1633 vr: 5,
1634 vri: 5,
1635 vrijdag: 5,
1636 za: 6,
1637 zat: 6,
1638 zaterdag: 6,
1639 }, 'Please use the English abbreviation "<ok>" for "<ko>".': { // FIXME: Translate to Czech.
1640 'neděle': 0,
1641 'ne': 0,
1642 'pondělí': 1,
1643 'po': 1,
1644 'úterý': 2,
1645 'út': 2,
1646 'středa': 3,
1647 'st': 3,
1648 'čtvrtek': 4,
1649 'čt': 4,
1650 'pátek': 5,
1651 'pá': 5,
1652 'sobota': 6,
1653 }, 'Please use the English abbreviation "<ok>" for "<ko>".': {
1654 // Spanish.
1655 'martes': 0,
1656 'miércoles': 1,
1657 'jueves': 2,
1658 'viernes': 3,
1659 'sábado': 4,
1660 'domingo': 5,
1661 'lunes': 6,
1662 // Indonesian.
1663 'selasa': 0,
1664 'rabu': 1,
1665 'kami': 2,
1666 'jumat': 3,
1667 'sabtu': 4,
1668 'minggu': 5,
1669 'senin': 6,
1670 // Swedish
1671 'söndag': 0,
1672 'söndagar': 0,
1673 'måndag': 1,
1674 'ma': 1,
1675 'tisdag': 2,
1676 'onsdag': 3,
1677 'torsdag': 4,
1678 'fredag': 5,
1679 'lördag': 6,
1680 'lördagar': 6,
1681 // Polish
1682 'niedziela': 0, 'niedz': 0, 'n': 0, 'ndz': 0,
1683 'poniedziałek': 1, 'poniedzialek': 1, 'pon': 1, 'pn': 1,
1684 'wtorek': 2, 'wt': 2,
1685 'środa': 3, 'sroda': 3, 'śr': 3, 'sr': 3,
1686 'czwartek': 4, 'czw': 4, 'cz': 4,
1687 'piątek': 5, 'piatek': 5, 'pt': 5,
1688 'sobota': 6, 'sob': 6, // 'so': 6 // abbreviation also used in German
1689 // Russian
1690 'воскресенье' : 0,
1691 'Вс' : 0,
1692 "voskresen'ye": 0,
1693 'понедельник' : 1,
1694 'Пн' : 1,
1695 "ponedel'nik" : 1,
1696 'вторник' : 2,
1697 'vtornik' : 2,
1698 'среда' : 3,
1699 'sreda' : 3,
1700 'четверг' : 4,
1701 'chetverk' : 4,
1702 'пятница' : 5,
1703 'pyatnitsa' : 5,
1704 'суббота' : 6,
1705 'subbota' : 6,
1706 // Danish
1707 'søndag' : 0,
1708 'mandag' : 1,
1709 'tirsdag': 2,
1710 'onsdag' : 3,
1711 'torsdag': 4,
1712 'fredag' : 5,
1713 'lørdag' : 6,
1714 },
1715 },
1716
1717 timevar: { // Special time variables which actual value depends on the date and the position of the facility.
1718 'default': {
1719 'sunrise': 'sunrise',
1720 'sunset': 'sunset',
1721 'dawn': 'dawn',
1722 'dusk': 'dusk',
1723 }, 'Please use notation "<ok>" for "<ko>".': {
1724 'sundown': 'sunset',
1725 }, 'Bitte benutze die Schreibweise "<ok>" für "<ko>".': {
1726 'morgendämmerung': 'dawn',
1727 'abenddämmerung': 'dusk',
1728 'sonnenaufgang': 'sunrise',
1729 'sonnenuntergang': 'sunset',
1730 },
1731 },
1732
1733 'event': { // variable events
1734 'default': {
1735 'easter': 'easter',
1736 }, 'Bitte benutze die Schreibweise "<ok>" für "<ko>".': {
1737 'ostern': 'easter',
1738 },
1739 },
1740 };
1741 // }}}
1742 // }}}
1743
1744 // make the library accessible for the outside world {{{
1745 if (typeof exports === 'object') {
1746 // For nodejs
1747 var SunCalc = require('suncalc');
1748 module.exports = factory(SunCalc, holidays, word_error_correction);
1749 } else {
1750 // For browsers
1751 root.opening_hours = factory(root.SunCalc, holidays, word_error_correction);
1752 }
1753 /// }}}
1754}(this, function (SunCalc, holidays, word_error_correction) {
1755 return function(value, nominatiomJSON, oh_mode) {
1756 // short constants {{{
1757 var word_value_replacement = { // if the correct values can not be calculated
1758 dawn : 60 * 5 + 30,
1759 sunrise : 60 * 6,
1760 sunset : 60 * 18,
1761 dusk : 60 * 18 + 30,
1762 };
1763 var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
1764 var weekdays = ['Su','Mo','Tu','We','Th','Fr','Sa'];
1765 var default_prettify_conf = {
1766 'leading_zero_hour': true, // enforce leading zero
1767 'one_zero_if_hour_zero': false, // only one zero "0" if hour is zero "0"
1768 'leave_off_closed': true, // leave keywords of and closed as is
1769 'keyword_for_off_closed': 'off', // use given keyword instead of "off" or "closed"
1770 'block_sep_string': ' ', // separate blocks by string
1771 'print_semicolon': true, // print token which separates normal blocks
1772 'leave_weekday_sep_one_day_betw': true, // use the separator (either "," or "-" which is used to separate days which follow to each other like Sa,Su or Su-Mo
1773 'sep_one_day_between': ',' // separator which should be used
1774 };
1775
1776 var minutes_in_day = 60 * 24;
1777 var msec_in_day = 1000 * 60 * minutes_in_day;
1778 var msec_in_week = msec_in_day * 7;
1779 // }}}
1780
1781 // The big picture -- How does this library work? {{{
1782 //======================================================================
1783 // Constructor - entry to parsing code
1784 //======================================================================
1785 // Terminology:
1786 //
1787 // Mo-Fr 10:00-11:00; Th 10:00-12:00
1788 // \_____block_____/ \____block___/
1789 //
1790 // The README refers to blocks as rules, which is more intuitive but less clear.
1791 // Because of that only the README uses the term rule in that context.
1792 // In all internal parts of this project, the term block is used.
1793 //
1794 // Mo-Fr Jan 10:00-11:00
1795 // \__/ \_/ \_________/
1796 // selectors (left to right: weekday, month, time)
1797 //
1798 // Logic:
1799 // - Tokenize
1800 // Foreach block:
1801 // - Run top-level (block) parser
1802 // - Which calls sub parser for specific selector types
1803 // - Which produce selector functions
1804 // }}}
1805
1806 // constructor parameters {{{
1807 // Evaluate additional information which can be given. They are
1808 // required to reasonably calculate 'sunrise' and to use the correct
1809 // holidays.
1810 var location_cc, location_state, lat, lon;
1811 if (typeof nominatiomJSON != 'undefined') {
1812 if (typeof nominatiomJSON.address != 'undefined' &&
1813 typeof nominatiomJSON.address.state != 'undefined') { // country_code will be tested later …
1814 location_cc = nominatiomJSON.address.country_code;
1815 location_state = nominatiomJSON.address.state;
1816 }
1817
1818 if (typeof nominatiomJSON.lon != 'undefined') { // lat will be tested later …
1819 lat = nominatiomJSON.lat;
1820 lon = nominatiomJSON.lon;
1821 }
1822 }
1823
1824 // 0: time ranges (default), tags: opening_hours, lit, …
1825 // 1: points in time
1826 // 2: both (time ranges and points in time), tags: collection_times, service_times
1827 if (typeof oh_mode == 'undefined') {
1828 oh_mode = 0;
1829 } else if (!(typeof oh_mode == 'number' && (oh_mode == 0 || oh_mode == 1 || oh_mode == 2))) {
1830 throw 'The third constructor parameter is oh_mode and must be a number (0, 1 or 2)'
1831 }
1832 // }}}
1833
1834 // put tokenized blocks into list {{{
1835 if (value.match(/^(\s*;?\s*)+$/))
1836 throw 'Value contains nothing meaningful which can be parsed';
1837
1838 var parsing_warnings = [];
1839 var done_with_warnings = false; // The functions which throw warnings can be called multiple times.
1840 var has_token = {};
1841 var tokens = tokenize(value);
1842 // console.log(JSON.stringify(tokens, null, '\t'));
1843 var prettified_value = '';
1844 var used_subparsers = {}; // Used sub parsers for one block, will be reset for each block. Declared as global, because it is manipulation inside various sub parsers.
1845 var week_stable = true;
1846
1847 var blocks = [];
1848
1849 for (var nblock = 0; nblock < tokens.length; nblock++) {
1850 if (tokens[nblock][0].length == 0) continue;
1851 // Block does contain nothing useful e.g. second block of '10:00-12:00;' (empty) which needs to be handled.
1852
1853 var continue_at = 0;
1854 do {
1855 if (continue_at == tokens[nblock][0].length) break;
1856 // Additional block does contain nothing useful e.g. second block of '10:00-12:00,' (empty) which needs to be handled.
1857
1858 var selectors = {
1859 // Time selectors
1860 time: [],
1861
1862 // Temporary array of selectors from time wrapped to the next day
1863 wraptime: [],
1864
1865 // Date selectors
1866 weekday: [],
1867 holiday: [],
1868 week: [],
1869 month: [],
1870 monthday: [],
1871 year: [],
1872
1873 // Array with non-empty date selector types, with most optimal ordering
1874 date: [],
1875
1876 fallback: tokens[nblock][1],
1877 additional: continue_at ? true : false,
1878 meaning: true,
1879 unknown: false,
1880 comment: undefined,
1881 build_from_token_block: undefined,
1882 };
1883
1884 selectors.build_from_token_block = [ nblock, continue_at ];
1885 continue_at = parseGroup(tokens[nblock][0], continue_at, selectors, nblock);
1886 if (typeof continue_at == 'object')
1887 continue_at = continue_at[0];
1888 else
1889 continue_at = 0;
1890
1891 if (selectors.year.length > 0)
1892 selectors.date.push(selectors.year);
1893 if (selectors.holiday.length > 0)
1894 selectors.date.push(selectors.holiday);
1895 if (selectors.month.length > 0)
1896 selectors.date.push(selectors.month);
1897 if (selectors.monthday.length > 0)
1898 selectors.date.push(selectors.monthday);
1899 if (selectors.week.length > 0)
1900 selectors.date.push(selectors.week);
1901 if (selectors.weekday.length > 0)
1902 selectors.date.push(selectors.weekday);
1903
1904 // console.log('weekday: ' + JSON.stringify(selectors.weekday, null, '\t'));
1905 blocks.push(selectors);
1906
1907 // this handles selectors with time ranges wrapping over midnight (e.g. 10:00-02:00)
1908 // it generates wrappers for all selectors and creates a new block
1909 if (selectors.wraptime.length > 0) {
1910 var wrapselectors = {
1911 time: selectors.wraptime,
1912 date: [],
1913
1914 meaning: selectors.meaning,
1915 unknown: selectors.unknown,
1916 comment: selectors.comment,
1917
1918 wrapped: true,
1919 };
1920
1921 for (var dselg = 0; dselg < selectors.date.length; dselg++) {
1922 wrapselectors.date.push([]);
1923 for (var dsel = 0; dsel < selectors.date[dselg].length; dsel++) {
1924 wrapselectors.date[wrapselectors.date.length-1].push(
1925 generateDateShifter(selectors.date[dselg][dsel], -msec_in_day)
1926 );
1927 }
1928 }
1929
1930 blocks.push(wrapselectors);
1931 }
1932 } while (continue_at)
1933 }
1934 // }}}
1935
1936 // Tokenization function: Splits string into parts. {{{
1937 // output: array of arrays of pairs [content, type]
1938 function tokenize(value) {
1939 var all_tokens = new Array();
1940 var curr_block_tokens = new Array();
1941
1942 var last_block_fallback_terminated = false;
1943
1944 while (value != '') {
1945 var tmp;
1946 if (tmp = value.match(/^(?:week\b|open\b|unknown)/i)) {
1947 // reserved word
1948 curr_block_tokens.push([tmp[0].toLowerCase(), tmp[0].toLowerCase(), value.length ]);
1949 value = value.substr(tmp[0].length);
1950 } else if (tmp = value.match(/^24\/7/i)) {
1951 // reserved word
1952 has_token[tmp[0]] = true;
1953 curr_block_tokens.push([tmp[0], tmp[0], value.length ]);
1954 value = value.substr(tmp[0].length);
1955 } else if (tmp = value.match(/^(?:off|closed)/i)) {
1956 // reserved word
1957 curr_block_tokens.push([tmp[0].toLowerCase(), 'closed', value.length ]);
1958 value = value.substr(tmp[0].length);
1959 } else if (tmp = value.match(/^(?:PH|SH)/i)) {
1960 // special day name (holidays)
1961 curr_block_tokens.push([tmp[0].toUpperCase(), 'holiday', value.length ]);
1962 value = value.substr(2);
1963 } else if (tmp = value.match(/^days?/i)) {
1964 curr_block_tokens.push([tmp[0].toLowerCase(), 'calcday', value.length ]);
1965 value = value.substr(tmp[0].length);
1966 } else if (tmp = value.match(/^(&|_|→|–|−|=|opening_hours=|ー|\?|~|~|:|°°|25x7|7[ ]?days( a week|)|all days?|every day|-late|public holidays?|7j?\/7|every day|до|рм|ам|jours fériés|sonn-|[a-zäößàáéøčěíúýřПнВсо]+\b)\.?/i)) {
1967 // Handle all remaining words with error tolerance
1968 var correct_val = returnCorrectWordOrToken(tmp[1].toLowerCase(), value.length);
1969 if (typeof correct_val == 'object') {
1970 curr_block_tokens.push([ correct_val[0], correct_val[1], value.length ]);
1971 value = value.substr(tmp[0].length);
1972 } else if (typeof correct_val == 'string') {
1973 value = correct_val + value.substr(tmp[0].length);
1974 } else {
1975 // other single-character tokens
1976 curr_block_tokens.push([value[0].toLowerCase(), value[0].toLowerCase(), value.length - 1 ]);
1977 value = value.substr(1);
1978 }
1979 } else if (tmp = value.match(/^\d+/)) {
1980 // number
1981 if (tmp[0] > 1900) // assumed to be a year number
1982 curr_block_tokens.push([tmp[0], 'year', value.length ]);
1983 else
1984 curr_block_tokens.push([+tmp[0], 'number', value.length ]);
1985 value = value.substr(tmp[0].length);
1986 } else if (tmp = value.match(/^"([^"]*)"/)) {
1987 // comment
1988 curr_block_tokens.push([tmp[1], 'comment', value.length ]);
1989 value = value.substr(tmp[0].length);
1990 } else if (value.match(/^;/)) {
1991 // semicolon terminates block
1992 // next tokens belong to a new block
1993 all_tokens.push([ curr_block_tokens, last_block_fallback_terminated, value.length ]);
1994 value = value.substr(1);
1995
1996 curr_block_tokens = [];
1997 last_block_fallback_terminated = false;
1998 } else if (value.match(/^\|\|/)) {
1999 // || terminates block
2000 // next tokens belong to a fallback block
2001 if (curr_block_tokens.length == 0)
2002 throw formatWarnErrorMessage(-1, value.length - 2, 'Rule before fallback rule does not contain anything useful');
2003
2004 all_tokens.push([ curr_block_tokens, last_block_fallback_terminated, value.length ]);
2005 value = value.substr(2);
2006
2007 curr_block_tokens = [];
2008 last_block_fallback_terminated = true;
2009 } else if (value.match(/^(?:␣|\s)/)) {
2010 // Using "␣" as space is not expected to be a normal mistake. Just ignore it to make using taginfo easier.
2011 value = value.substr(1);
2012 } else if (tmp = value.match(/^\s+/)) {
2013 // whitespace is ignored
2014 value = value.substr(tmp[0].length);
2015 } else if (value.match(/^[:.]/)) {
2016 // time separator
2017 if (value[0] == '.' && !done_with_warnings)
2018 parsing_warnings.push([ -1, value.length - 1, 'Please use ":" as hour/minute-separator' ]);
2019 curr_block_tokens.push([ ':', 'timesep', value.length ]);
2020 value = value.substr(1);
2021 } else {
2022 // other single-character tokens
2023 curr_block_tokens.push([value[0].toLowerCase(), value[0].toLowerCase(), value.length ]);
2024 value = value.substr(1);
2025 }
2026 }
2027
2028 all_tokens.push([ curr_block_tokens, last_block_fallback_terminated ]);
2029
2030 return all_tokens;
2031 }
2032 // }}}
2033
2034 // error correction/tolerance function {{{
2035 // Go through word_error_correction hash and get correct value back.
2036 function returnCorrectWordOrToken(word, value_length) {
2037 for (var token_name in word_error_correction) {
2038 for (var comment in word_error_correction[token_name]) {
2039 for (var old_val in word_error_correction[token_name][comment]) {
2040 if (old_val == word) {
2041 var val = word_error_correction[token_name][comment][old_val];
2042 if (token_name == 'wrong_words' && !done_with_warnings) {
2043 parsing_warnings.push([ -1, value_length - old_val.length,
2044 comment.replace(/<ko>/, old_val).replace(/<ok>/, val) ]);
2045 return val;
2046 } else if (comment != 'default'){
2047 var correct_abbr;
2048 for (correct_abbr in word_error_correction[token_name]['default']) {
2049 if (word_error_correction[token_name]['default'][correct_abbr] == val)
2050 break; // FIXME
2051 }
2052 // FIXME
2053 if (token_name != 'timevar') { // normally written in lower case
2054 correct_abbr = correct_abbr.charAt(0).toUpperCase() + correct_abbr.slice(1);
2055 }
2056 if (!done_with_warnings)
2057 parsing_warnings.push([ -1, value_length - old_val.length,
2058 comment.replace(/<ko>/, old_val).replace(/<ok>/, correct_abbr) ]);
2059 }
2060 return [ val, token_name ];
2061 }
2062 }
2063 }
2064 }
2065 }
2066 // }}}
2067
2068 // return warnings as list {{{
2069 function getWarnings(it) {
2070 if (typeof it == 'object') { // getWarnings was called in a state without critical errors. We can do extended tests.
2071
2072 // Check if 24/7 is used and it does not mean 24/7 because there are other blocks.
2073 var has_advanced = it.advance();
2074
2075 if (has_advanced === true && has_token['24/7'] && !done_with_warnings) {
2076 parsing_warnings.push([ -1, 0, 'You used 24/7 in a way that is probably not interpreted as "24 hours 7 days a week".'
2077 // Probably because of: "24/7; 12:00-14:00 open", ". Needs extra testing.
2078 + ' For correctness you might want to use "open" or "closed"'
2079 + ' for this rule and then write your exceptions which should achieve the same goal and is more clear'
2080 + ' e.g. "open; Mo 12:00-14:00 off".']);
2081 }
2082 }
2083
2084 var warnings = [];
2085 for (var i = 0; i < parsing_warnings.length; i++) {
2086 warnings.push( formatWarnErrorMessage(parsing_warnings[i][0], parsing_warnings[i][1], parsing_warnings[i][2]) );
2087 }
2088 return warnings;
2089 }
2090 // }}}
2091
2092 // Function to check token array for specific pattern {{{
2093 function matchTokens(tokens, at /*, matches... */) {
2094 if (at + arguments.length - 2 > tokens.length)
2095 return false;
2096 for (var i = 0; i < arguments.length - 2; i++) {
2097 if (tokens[at + i][1] !== arguments[i + 2])
2098 return false;
2099 }
2100
2101 return true;
2102 }
2103 // }}}
2104
2105 // Generate selector wrapper with time offset {{{
2106 function generateDateShifter(func, shift) {
2107 return function(date) {
2108 var res = func(new Date(date.getTime() + shift));
2109
2110 if (typeof res[1] === 'undefined')
2111 return res;
2112 return [ res[0], new Date(res[1].getTime() - shift) ];
2113 }
2114 }
2115 // }}}
2116
2117 // Top-level parser {{{
2118 function parseGroup(tokens, at, selectors, nblock, conf) {
2119 var prettified_group_value = '';
2120 used_subparsers = { 'time ranges': [ ] };
2121
2122 // console.log(tokens); // useful for debugging of tokenize
2123 while (at < tokens.length) {
2124 var old_at = at;
2125 // console.log('Parsing at position', at +':', tokens[at]);
2126 if (matchTokens(tokens, at, 'weekday')) {
2127 at = parseWeekdayRange(tokens, at, selectors);
2128 } else if (matchTokens(tokens, at, '24/7')) {
2129 selectors.time.push(function(date) { return [true]; });
2130 // Not needed. If there is no selector it automatically matches everything.
2131 // WRONG: This only works if there is no other selector in this selector group ...
2132 at++;
2133 } else if (matchTokens(tokens, at, 'holiday')) {
2134 if (matchTokens(tokens, at+1, ','))
2135 at = parseHoliday(tokens, at, selectors, true);
2136 else
2137 at = parseHoliday(tokens, at, selectors, false);
2138 week_stable = false;
2139 } else if (matchTokens(tokens, at, 'month', 'number')
2140 || matchTokens(tokens, at, 'month', 'weekday')
2141 || matchTokens(tokens, at, 'year', 'month', 'number')
2142 || matchTokens(tokens, at, 'year', 'event')
2143 || matchTokens(tokens, at, 'event')) {
2144 at = parseMonthdayRange(tokens, at, nblock);
2145 week_stable = false;
2146 } else if (matchTokens(tokens, at, 'year')) {
2147 at = parseYearRange(tokens, at);
2148 week_stable = false;
2149 } else if (matchTokens(tokens, at, 'month')) {
2150 at = parseMonthRange(tokens, at);
2151 // week_stable = false; // decided based on actual values
2152 } else if (matchTokens(tokens, at, 'week')) {
2153 at = parseWeekRange(tokens, at + 1);
2154 week_stable = false;
2155
2156 // if (prettified_group_value[-1] != ' ')
2157 // prettified_group_value = prettified_group_value.substring(0, prettified_group_value.length - 1);
2158 } else if (at != 0 && at != tokens.length - 1 && tokens[at][0] == ':') {
2159 // Ignore colon if they appear somewhere else than as time separator.
2160 // Except the start or end of the value.
2161 // This provides compatibility with the syntax proposed by Netzwolf:
2162 // http://wiki.openstreetmap.org/wiki/Key:opening_hours:specification
2163 if (!done_with_warnings && (matchTokens(tokens, at-1, 'weekday') || matchTokens(tokens, at-1, 'holiday')))
2164 parsing_warnings.push([nblock, at, 'Please don’t use ":" after ' + tokens[at-1][1] + '.']);
2165
2166 if (prettified_group_value[-1] != ' ')
2167 prettified_group_value = prettified_group_value.substring(0, prettified_group_value.length - 1);
2168 at++;
2169 } else if (matchTokens(tokens, at, 'number', 'timesep')
2170 || matchTokens(tokens, at, 'timevar')
2171 || matchTokens(tokens, at, '(', 'timevar')
2172 || matchTokens(tokens, at, 'number', '-')) {
2173 at = parseTimeRange(tokens, at, selectors, false);
2174
2175 used_subparsers['time ranges'].push(at);
2176 } else if (matchTokens(tokens, at, 'closed')) {
2177 selectors.meaning = false;
2178 at++;
2179 if (matchTokens(tokens, at, ',')) // additional block
2180 at = [ at + 1 ];
2181
2182 if (typeof used_subparsers['state keywords'] != 'object')
2183 used_subparsers['state keywords'] = [ at ];
2184 else
2185 used_subparsers['state keywords'].push(at);
2186 } else if (matchTokens(tokens, at, 'open')) {
2187 selectors.meaning = true;
2188 at++;
2189 if (matchTokens(tokens, at, ',')) // additional block
2190 at = [ at + 1 ];
2191
2192 if (typeof used_subparsers['state keywords'] != 'object')
2193 used_subparsers['state keywords'] = [ at ];
2194 else
2195 used_subparsers['state keywords'].push(at);
2196 } else if (matchTokens(tokens, at, 'unknown')) {
2197 selectors.meaning = false;
2198 selectors.unknown = true;
2199 at++;
2200 if (matchTokens(tokens, at, ',')) // additional block
2201 at = [ at + 1 ];
2202
2203 if (typeof used_subparsers['state keywords'] != 'object')
2204 used_subparsers['state keywords'] = [ at ];
2205 else
2206 used_subparsers['state keywords'].push(at);
2207 } else if (matchTokens(tokens, at, 'comment')) {
2208 selectors.comment = tokens[at][0];
2209 if (at > 0) {
2210 if (!matchTokens(tokens, at - 1, 'open')
2211 && !matchTokens(tokens, at - 1, 'closed')) {
2212 // Then it is unknown. Either with unknown explicitly
2213 // specified or just a comment behind.
2214 selectors.meaning = false;
2215 selectors.unknown = true;
2216 }
2217 } else { // block starts with comment
2218 selectors.time.push(function(date) { return [true]; });
2219 // Not needed. If there is no selector it automatically matches everything.
2220 // WRONG: This only works if there is no other selector in this selector group ...
2221 selectors.meaning = false;
2222 selectors.unknown = true;
2223 }
2224 at++;
2225 if (matchTokens(tokens, at, ',')) // additional block
2226 at = [ at + 1 ];
2227
2228 if (typeof used_subparsers['comments'] != 'object')
2229 used_subparsers['comments'] = [ at ];
2230 else
2231 used_subparsers['comments'].push(at);
2232 } else {
2233 var warnings = getWarnings();
2234 throw formatWarnErrorMessage(nblock, at, 'Unexpected token: "' + tokens[at][1]
2235 + '" This means that the syntax is not valid at that point or it is currently not supported.')
2236 + (warnings ? ' ' + warnings.join('; ') : '');
2237 }
2238
2239 if (typeof conf != 'undefined') {
2240
2241 // 'Mo: 12:00-13:00' -> 'Mo 12:00-13:00'
2242 if (used_subparsers['time ranges'] && old_at > 1 && tokens[old_at-1][0] == ':'
2243 && matchTokens(tokens, old_at - 2, 'weekday'))
2244 prettified_group_value = prettified_group_value.substring(0, prettified_group_value.length - 2) + ' ';
2245
2246 // 'week 1, week 3' -> 'week 1,week 3'
2247 if (prettified_group_value.substr(prettified_group_value.length -2, 2) == ', '
2248 && matchTokens(tokens, old_at, 'week'))
2249 prettified_group_value = prettified_group_value.substring(0, prettified_group_value.length - 1);
2250
2251 prettified_group_value += prettifySelector(tokens, old_at, at, conf, used_subparsers['time ranges'].length);
2252 }
2253
2254 if (typeof at == 'object') // additional block
2255 break;
2256 }
2257
2258 prettified_value += prettified_group_value.replace(/\s+$/, '');
2259
2260 if (!done_with_warnings) {
2261 for (var subparser_name in used_subparsers) {
2262 if (used_subparsers[subparser_name].length > 1) {
2263 parsing_warnings.push([nblock, used_subparsers[subparser_name][used_subparsers[subparser_name].length - 1] - 1,
2264 'You have used ' + used_subparsers[subparser_name].length
2265 + (subparser_name.match(/^(?:comments|state keywords)/) ?
2266 ' ' + subparser_name + ' in one rule.'
2267 + ' You may only use one in one rule.'
2268 :
2269 ' not connected ' + subparser_name + ' in one rule.'
2270 + ' This is probably an error.'
2271 + ' Equal selector types can (and should) always be written in conjunction separated by comma or something.'
2272 + ' Example for time ranges "12:00-13:00,15:00-18:00".'
2273 + ' Example for weekdays "Mo-We,Fr".'
2274 )
2275 + ' Rules can be separated by ";".' ]
2276 );
2277 }
2278 }
2279 }
2280
2281 return at;
2282 }
2283 // }}}
2284
2285 // helper functions for sub parser {{{
2286 // for given date, returns date moved to the start of specified day minute
2287 function dateAtDayMinutes(date, minutes) {
2288 return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, minutes);
2289 }
2290
2291 // for given date, returns date moved to the specific day of week
2292 function dateAtNextWeekday(date, day) {
2293 var delta = day - date.getDay();
2294 return new Date(date.getFullYear(), date.getMonth(), date.getDate() + delta + (delta < 0 ? 7 : 0));
2295 }
2296
2297 function indexOf(needle) {
2298 if(typeof Array.prototype.indexOf === 'function') {
2299 indexOf = Array.prototype.indexOf;
2300 } else {
2301 indexOf = function(needle) {
2302 var i = -1, index = -1;
2303 for(i = 0; i < this.length; i++) {
2304 if(this[i] === needle) {
2305 index = i;
2306 break;
2307 }
2308 }
2309 return index;
2310 };
2311 }
2312 return indexOf.call(this, needle);
2313 }
2314
2315 // Numeric list parser (1,2,3-4,-1), used in weekday parser above
2316 function parseNumRange(tokens, at, func) {
2317 for (; at < tokens.length; at++) {
2318 if (matchTokens(tokens, at, 'number', '-', 'number')) {
2319 // Number range
2320 func(tokens[at][0], tokens[at+2][0], at);
2321 at += 3;
2322 } else if (matchTokens(tokens, at, '-', 'number')) {
2323 // Negative number
2324 func(-tokens[at+1][0], -tokens[at+1][0], at);
2325 at += 2
2326 } else if (matchTokens(tokens, at, 'number')) {
2327 // Single number
2328 func(tokens[at][0], tokens[at][0], at);
2329 at++;
2330 } else {
2331 throw formatWarnErrorMessage(nblock, at + matchTokens(tokens, at, '-'),
2332 'Unexpected token in number range: ' + tokens[at][1]);
2333 }
2334
2335 if (!matchTokens(tokens, at, ','))
2336 break;
2337 }
2338
2339 return at;
2340 }
2341
2342 function getConstrainedWeekday(tokens, at) {
2343 var number = 0;
2344 var endat = parseNumRange(tokens, at, function(from, to, at) {
2345
2346 // bad number
2347 if (from == 0 || from < -5 || from > 5)
2348 throw formatWarnErrorMessage(nblock, at,
2349 'Number between -5 and 5 (except 0) expected');
2350
2351 if (from == to) {
2352 if (number != 0)
2353 throw formatWarnErrorMessage(nblock, at,
2354 'You can not use a more than one constrained weekday in a month range');
2355 number = from;
2356 } else {
2357 throw formatWarnErrorMessage(nblock, at+2,
2358 'You can not use a range of constrained weekdays in a month range');
2359 }
2360 });
2361
2362 if (!matchTokens(tokens, endat, ']'))
2363 throw formatWarnErrorMessage(nblock, endat, '"]" expected.');
2364
2365 return [ number, endat + 1 ];
2366 }
2367
2368 // Check if period is ok. Period 0 or 1 don’t make much sense.
2369 function checkPeriod(at, period, period_type, parm_string) {
2370 if (done_with_warnings)
2371 return;
2372
2373 if (period === 0) {
2374 throw formatWarnErrorMessage(nblock, at,
2375 'You can not use '+ period_type +' ranges with period equals zero.');
2376 } else if (period === 1) {
2377 if (typeof parm_string == 'string' && parm_string == 'no_end_year')
2378 parsing_warnings.push([nblock, at,
2379 'Please don’t use '+ period_type +' ranges with period equals one.'
2380 + ' If you want to express that a facility is open starting from a year without limit use "<year>+".']);
2381 else
2382 parsing_warnings.push([nblock, at,
2383 'Please don’t use '+ period_type +' ranges with period equals one.']);
2384 }
2385 }
2386
2387 function getDateForConstrainedWeekday(year, month, weekday, constrained_weekday, add_days) {
2388 var tmp_date = dateAtNextWeekday(
2389 new Date(year, month + (constrained_weekday[0] > 0 ? 0 : 1), 1), weekday);
2390
2391 tmp_date.setDate(tmp_date.getDate() + (constrained_weekday[0] + (constrained_weekday[0] > 0 ? -1 : 0)) * 7);
2392
2393 if (typeof add_days != 'undefined' && add_days[1])
2394 tmp_date.setDate(tmp_date.getDate() + add_days[0]);
2395
2396 return tmp_date;
2397 }
2398
2399 function formatWarnErrorMessage(nblock, at, message) {
2400 var pos = 0;
2401 if (nblock == -1) { // Usage of block index not required because we do have access to value.length.
2402 pos = value.length - at;
2403 } else { // Issue accrued at a later time, position in string needs to be reconstructed.
2404 if (typeof tokens[nblock][0][at] == 'undefined') {
2405 pos = value.length;
2406 if (typeof tokens[nblock][0][tokens[nblock][0].length-1] != 'undefined') {
2407 // pos -= tokens[nblock][0][tokens[nblock][0].length-1][2];
2408 console.warn("FIXME");
2409 }
2410 } else {
2411 pos = value.length;
2412 if (typeof tokens[nblock][0][at+1] != 'undefined') {
2413 pos -= tokens[nblock][0][at+1][2];
2414 } else if (typeof tokens[nblock][2] != 'undefined') {
2415 pos -= tokens[nblock][2];
2416 } else {
2417 }
2418 }
2419 }
2420 return value.substring(0, pos) + ' <--- (' + message + ')';
2421 }
2422
2423 // check if date is valid
2424 function isValidDate(month, day, nblock, at) {
2425 // month == 0 is Jan
2426
2427 // May use this instead. Does not say, what is wrong as good was implementation below.
2428 // var testDate = new Date(year, month, day);
2429 // if (testDate.getDate() != day || testDate.getMonth() != month || testDate.getFullYear() != year) {
2430 // console.error('date not valid');
2431 // }
2432
2433 // https://en.wikipedia.org/wiki/Month#Julian_and_Gregorian_calendars
2434 if (day < 1 || day > 31)
2435 throw formatWarnErrorMessage(nblock, at, 'Day must be between 1 and 31.');
2436 if ((month==3 || month==5 || month==8 || month==10) && day==31)
2437 throw formatWarnErrorMessage(nblock, at, 'Month ' + months[month] + " doesn't have 31 days.!");
2438 if (month == 1 && day == 30)
2439 throw formatWarnErrorMessage(nblock, at, 'Month ' + months[1]+ " either has 28 or 29 days (leap years).");
2440 }
2441 // }}}
2442
2443 // Time range parser (10:00-12:00,14:00-16:00) {{{
2444 //
2445 // extended_open_end: <time> - <time> +
2446 // at is here A (if extended_open_end is true)
2447 function parseTimeRange(tokens, at, selectors, extended_open_end) {
2448 for (; at < tokens.length; at++) {
2449 var has_time_var_calc = [], has_normal_time = []; // element 0: start time, 1: end time
2450 has_normal_time[0] = matchTokens(tokens, at, 'number', 'timesep', 'number');
2451 has_time_var_calc[0] = matchTokens(tokens, at, '(', 'timevar');
2452 if (has_normal_time[0] || matchTokens(tokens, at, 'timevar') || has_time_var_calc[0]) {
2453 // relying on the fact that always *one* of them is true
2454
2455 var is_point_in_time = false; // default no time range
2456 var has_open_end = false; // default no open end
2457 var timevar_add = [ 0, 0 ];
2458 var timevar_string = []; // capture timevar string like 'sunrise' to calculate it for the current date.
2459
2460 // minutes_from
2461 if (has_normal_time[0]) {
2462 var minutes_from = getMinutesByHoursMinutes(tokens, nblock, at+has_time_var_calc[0]);
2463 } else {
2464 timevar_string[0] = tokens[at+has_time_var_calc[0]][0];
2465 var minutes_from = word_value_replacement[timevar_string[0]];
2466
2467 if (has_time_var_calc[0]) {
2468 timevar_add[0] = parseTimevarCalc(tokens, at);
2469 minutes_from += timevar_add[0];
2470 }
2471 }
2472
2473 var at_end_time = at+(has_normal_time[0] ? 3 : (has_time_var_calc[0] ? 7 : 1))+1; // after '-'
2474 if (!matchTokens(tokens, at_end_time - 1, '-')) { // not time range
2475 if (matchTokens(tokens, at_end_time - 1, '+')) {
2476 has_open_end = true;
2477 } else {
2478 if (oh_mode == 0) {
2479 throw formatWarnErrorMessage(nblock, at+(
2480 has_normal_time[0] ? (
2481 typeof tokens[at+3] == 'object' ? 3 : 2
2482 ) : (
2483 has_time_var_calc[0] ? 2 : 1
2484 )
2485 ),
2486 'hyphen (-) or open end (+) in time range '
2487 + (has_time_var_calc[0] ? 'calculation ' : '') + 'expected.'
2488 + ' For working with points in time, the mode for opening_hours.js has to be altered.'
2489 + ' Maybe wrong tag?');
2490 } else {
2491 var minutes_to = minutes_from + 1;
2492 is_point_in_time = true;
2493 }
2494 }
2495 }
2496
2497 // minutes_to
2498 if (has_open_end) {
2499 if (minutes_from >= 22 * 60)
2500 var minutes_to = minutes_from + 8 * 60;
2501 else if (minutes_from >= 17 * 60)
2502 var minutes_to = minutes_from + 10 * 60;
2503 else
2504 var minutes_to = minutes_in_day;
2505 } else if (!is_point_in_time) {
2506 has_normal_time[1] = matchTokens(tokens, at_end_time, 'number', 'timesep', 'number');
2507 has_time_var_calc[1] = matchTokens(tokens, at_end_time, '(', 'timevar');
2508 if (!has_normal_time[1] && !matchTokens(tokens, at_end_time, 'timevar') && !has_time_var_calc[1]) {
2509 throw formatWarnErrorMessage(nblock, at_end_time, 'time range does not continue as expected');
2510 } else {
2511 if (has_normal_time[1]) {
2512 var minutes_to = getMinutesByHoursMinutes(tokens, nblock, at_end_time);
2513 } else {
2514 timevar_string[1] = tokens[at_end_time+has_time_var_calc[1]][0]
2515 var minutes_to = word_value_replacement[timevar_string[1]];
2516 }
2517
2518 if (has_time_var_calc[1]) {
2519 timevar_add[1] = parseTimevarCalc(tokens, at_end_time);
2520 minutes_to += timevar_add[1];
2521 }
2522 }
2523 }
2524
2525 at = at_end_time + (is_point_in_time ? -1 :
2526 (has_normal_time[1] ? 3 : (has_time_var_calc[1] ? 7 : !has_open_end))
2527 );
2528
2529 if (matchTokens(tokens, at, '/', 'number')) {
2530 if (matchTokens(tokens, at + 2, 'timesep', 'number')) { // /hours:minutes
2531 var point_in_time_period = getMinutesByHoursMinutes(tokens, nblock, at + 1);
2532 at += 4;
2533 } else { // /minutes
2534 var point_in_time_period = tokens[at + 1][0];
2535 at += 2;
2536 if (matchTokens(tokens, at, 'timesep'))
2537 throw formatWarnErrorMessage(nblock, at,
2538 'Time period does not continue as expected. Exampe "/01:30".');
2539 }
2540
2541 if (oh_mode == 0)
2542 throw formatWarnErrorMessage(nblock, at - 1,
2543 'opening_hours is running in "time range mode". Found point in time.');
2544
2545 is_point_in_time = true;
2546 } else if (matchTokens(tokens, at, '+')) {
2547 parseTimeRange(tokens, at_end_time, selectors, true);
2548 at++;
2549 } else if (oh_mode == 1 && !is_point_in_time) {
2550 throw formatWarnErrorMessage(nblock, at_end_time,
2551 'opening_hours is running in "points in time mode". Found time range.');
2552 }
2553
2554 if (typeof lat != 'undefined') { // lon will also be defined (see above)
2555 if (!has_normal_time[0] || !(has_normal_time[1] || has_open_end || is_point_in_time) )
2556 week_stable = false;
2557 } else { // we can not calculate exact times so we use the already applied constants (word_value_replacement).
2558 timevar_string = [];
2559 }
2560
2561 // normalize minutes into range
2562 if (!extended_open_end && minutes_from >= minutes_in_day)
2563 throw formatWarnErrorMessage(nblock, at_end_time - 1,
2564 'Time range starts outside of the current day');
2565 if (minutes_to < minutes_from || ((has_normal_time[0] && has_normal_time[1]) && minutes_from == minutes_to))
2566 minutes_to += minutes_in_day;
2567 if (minutes_to > minutes_in_day * 2)
2568 throw formatWarnErrorMessage(nblock, at_end_time + (has_normal_time[1] ? 4 : (has_time_var_calc[1] ? 7 : 1)) - 2,
2569 'Time spanning more than two midnights not supported');
2570
2571 // this shortcut makes always-open range check faster
2572 if (!(minutes_from == 0 && minutes_to == minutes_in_day)) {
2573 if (minutes_to > minutes_in_day) { // has_normal_time[1] must be true
2574 selectors.time.push(function(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period) { return function(date) {
2575 var ourminutes = date.getHours() * 60 + date.getMinutes();
2576
2577 if (timevar_string[0]) {
2578 var date_from = eval('SunCalc.getTimes(date, lat, lon).' + timevar_string[0]);
2579 minutes_from = date_from.getHours() * 60 + date_from.getMinutes() + timevar_add[0];
2580 }
2581 if (timevar_string[1]) {
2582 var date_to = eval('SunCalc.getTimes(date, lat, lon).' + timevar_string[1]);
2583 minutes_to = date_to.getHours() * 60 + date_to.getMinutes() + timevar_add[1];
2584 minutes_to += minutes_in_day;
2585 // Needs to be added because it was added by
2586 // normal times: if (minutes_to < minutes_from)
2587 // above the selector construction.
2588 } else if (is_point_in_time && typeof point_in_time_period != 'number') {
2589 minutes_to = minutes_from + 1;
2590 }
2591
2592 if (typeof point_in_time_period == 'number') {
2593 if (ourminutes < minutes_from) {
2594 return [false, dateAtDayMinutes(date, minutes_from)];
2595 } else if (ourminutes <= minutes_to) {
2596 for (var cur_min = minutes_from; ourminutes + point_in_time_period >= cur_min; cur_min += point_in_time_period) {
2597 if (cur_min == ourminutes) {
2598 return [true, dateAtDayMinutes(date, ourminutes + 1)];
2599 } else if (ourminutes < cur_min) {
2600 return [false, dateAtDayMinutes(date, cur_min)];
2601 }
2602 }
2603 }
2604 return [false, dateAtDayMinutes(date, minutes_in_day)];
2605 } else {
2606 if (ourminutes < minutes_from)
2607 return [false, dateAtDayMinutes(date, minutes_from)];
2608 else
2609 return [true, dateAtDayMinutes(date, minutes_to), has_open_end];
2610 }
2611 }}(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period));
2612
2613 selectors.wraptime.push(function(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period) { return function(date) {
2614 var ourminutes = date.getHours() * 60 + date.getMinutes();
2615
2616 if (timevar_string[0]) {
2617 var date_from = eval('SunCalc.getTimes(date, lat, lon).' + timevar_string[0]);
2618 minutes_from = date_from.getHours() * 60 + date_from.getMinutes() + timevar_add[0];
2619 }
2620 if (timevar_string[1]) {
2621 var date_to = eval('SunCalc.getTimes(date, lat, lon).' + timevar_string[1]);
2622 minutes_to = date_to.getHours() * 60 + date_to.getMinutes() + timevar_add[1];
2623 // minutes_in_day does not need to be added.
2624 // For normal times in it was added in: if (minutes_to < // minutes_from)
2625 // above the selector construction and
2626 // subtracted in the selector construction call
2627 // which returns the selector function.
2628 }
2629
2630 if (typeof point_in_time_period == 'number') {
2631 if (ourminutes <= minutes_to) {
2632 for (var cur_min = 0; ourminutes + point_in_time_period >= cur_min; cur_min += point_in_time_period) {
2633 if (cur_min == ourminutes) {
2634 return [true, dateAtDayMinutes(date, ourminutes + 1)];
2635 } else if (ourminutes < cur_min) {
2636 return [false, dateAtDayMinutes(date, cur_min)];
2637 }
2638 }
2639 }
2640 } else {
2641 if (ourminutes < minutes_to)
2642 return [true, dateAtDayMinutes(date, minutes_to), has_open_end];
2643 }
2644 return [false, undefined];
2645 }}(minutes_from, minutes_to - minutes_in_day, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period));
2646 } else {
2647 selectors.time.push(function(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period) { return function(date) {
2648 var ourminutes = date.getHours() * 60 + date.getMinutes();
2649
2650 if (timevar_string[0]) {
2651 var date_from = eval('SunCalc.getTimes(date, lat, lon).' + timevar_string[0]);
2652 minutes_from = date_from.getHours() * 60 + date_from.getMinutes() + timevar_add[0];
2653 }
2654 if (timevar_string[1]) {
2655 var date_to = eval('SunCalc.getTimes(date, lat, lon).' + timevar_string[1]);
2656 minutes_to = date_to.getHours() * 60 + date_to.getMinutes() + timevar_add[1];
2657 } else if (is_point_in_time && typeof point_in_time_period != 'number') {
2658 minutes_to = minutes_from + 1;
2659 }
2660
2661 if (typeof point_in_time_period == 'number') {
2662 if (ourminutes < minutes_from) {
2663 return [false, dateAtDayMinutes(date, minutes_from)];
2664 } else if (ourminutes <= minutes_to) {
2665 for (var cur_min = minutes_from; ourminutes + point_in_time_period >= cur_min; cur_min += point_in_time_period) {
2666 if (cur_min == ourminutes) {
2667 return [true, dateAtDayMinutes(date, ourminutes + 1)];
2668 } else if (ourminutes < cur_min) {
2669 return [false, dateAtDayMinutes(date, cur_min)];
2670 }
2671 }
2672 }
2673 return [false, dateAtDayMinutes(date, minutes_in_day)];
2674 } else {
2675 if (ourminutes < minutes_from)
2676 return [false, dateAtDayMinutes(date, minutes_from)];
2677 else if (ourminutes < minutes_to)
2678 return [true, dateAtDayMinutes(date, minutes_to), has_open_end];
2679 else
2680 return [false, dateAtDayMinutes(date, minutes_from + minutes_in_day)];
2681 }
2682 }}(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period));
2683 }
2684 } else {
2685 selectors.time.push(function(date) { return [true]; });
2686 }
2687
2688 } else if (matchTokens(tokens, at, 'number', '-', 'number')) { // "Mo 09-18" (Please don’t use this) -> "Mo 09:00-18:00".
2689 var minutes_from = tokens[at][0] * 60;
2690 var minutes_to = tokens[at+2][0] * 60;
2691 if (!done_with_warnings)
2692 parsing_warnings.push([nblock, at + 2,
2693 'Time range without minutes specified. Not very explicit! Please use this syntax instead e.g. "12:00-14:00".']);
2694
2695 if (minutes_from >= minutes_in_day)
2696 throw formatWarnErrorMessage(nblock, at,
2697 'Time range starts outside of the current day');
2698 if (minutes_to < minutes_from)
2699 minutes_to += minutes_in_day;
2700 if (minutes_to > minutes_in_day * 2)
2701 throw formatWarnErrorMessage(nblock, at + 2,
2702 'Time spanning more than two midnights not supported');
2703
2704 if (minutes_to > minutes_in_day) {
2705 selectors.time.push(function(minutes_from, minutes_to) { return function(date) {
2706 var ourminutes = date.getHours() * 60 + date.getMinutes();
2707
2708 if (ourminutes < minutes_from)
2709 return [false, dateAtDayMinutes(date, minutes_from)];
2710 else
2711 return [true, dateAtDayMinutes(date, minutes_to)];
2712 }}(minutes_from, minutes_to));
2713
2714 selectors.wraptime.push(function(minutes_from, minutes_to) { return function(date) {
2715 var ourminutes = date.getHours() * 60 + date.getMinutes();
2716
2717 if (ourminutes < minutes_to)
2718 return [true, dateAtDayMinutes(date, minutes_to)];
2719 else
2720 return [false, undefined];
2721 }}(minutes_from, minutes_to - minutes_in_day));
2722 } else {
2723 selectors.time.push(function(minutes_from, minutes_to) { return function(date) {
2724 var ourminutes = date.getHours() * 60 + date.getMinutes();
2725
2726 if (ourminutes < minutes_from)
2727 return [false, dateAtDayMinutes(date, minutes_from)];
2728 else if (ourminutes < minutes_to)
2729 return [true, dateAtDayMinutes(date, minutes_to), has_open_end];
2730 else
2731 return [false, dateAtDayMinutes(date, minutes_from + minutes_in_day)];
2732 }}(minutes_from, minutes_to));
2733 }
2734
2735 at += 3;
2736 } else { // additional block
2737 if (matchTokens(tokens, at, '('))
2738 throw formatWarnErrorMessage(nblock, at, 'Missing variable time (e.g. sunrise) after: "' + tokens[at][1] + '"');
2739 if (matchTokens(tokens, at, 'number', 'timesep'))
2740 throw formatWarnErrorMessage(nblock, at+2, 'Missing minutes in time range after: "' + tokens[at+1][1] + '"');
2741 if (matchTokens(tokens, at, 'number'))
2742 throw formatWarnErrorMessage(nblock, at+2, 'Missing time seperator in time range after: "' + tokens[at][1] + '"');
2743 return [ at ];
2744 }
2745
2746 if (!matchTokens(tokens, at, ','))
2747 break;
2748 }
2749
2750 return at;
2751 }
2752
2753 // get time in minutes from <hour>:<minute> {{{
2754 // Only used if throwing an error is wanted.
2755 function getMinutesByHoursMinutes(tokens, nblock, at) {
2756 if (tokens[at+2][0] > 59)
2757 throw formatWarnErrorMessage(nblock, at+2,
2758 'Minutes are greater than 59.');
2759 return tokens[at][0] * 60 + tokens[at+2][0];
2760 }
2761 // }}}
2762
2763 // get time in minutes from "(sunrise-01:30)" {{{
2764 // Extract the added or subtracted time from "(sunrise-01:30)"
2765 // returns time in minutes e.g. -90
2766 function parseTimevarCalc(tokens, at) {
2767 if (matchTokens(tokens, at+2, '+') || matchTokens(tokens, at+2, '-')) {
2768 if (matchTokens(tokens, at+3, 'number', 'timesep', 'number')) {
2769 if (matchTokens(tokens, at+6, ')')) {
2770 var add_or_subtract = tokens[at+2][0] == '+' ? '1' : '-1';
2771 var minutes = getMinutesByHoursMinutes(tokens, nblock, at+3) * add_or_subtract;
2772 if (minutes == 0)
2773 parsing_warnings.push([ nblock, at+5, 'Adding zero in a variable time calculation does not change the variable time.'
2774 + ' Please omit the calculation (example: "12:00-sunset").' ]
2775 );
2776 return minutes;
2777 } else {
2778 error = [ at+6, '. Missing ")".'];
2779 }
2780 } else {
2781 error = [ at+5, ' (time).'];
2782 }
2783 } else {
2784 error = [ at+2, '. "+" or "-" expected.'];
2785 }
2786
2787 if (error)
2788 throw formatWarnErrorMessage(nblock, error[0],
2789 'Calculcation with variable time is not in the right syntax' + error[1]);
2790 }
2791 // }}}
2792 // }}}
2793
2794 // Weekday range parser (Mo,We-Fr,Sa[1-2,-1],PH) {{{
2795 function parseWeekdayRange(tokens, at, selectors) {
2796 for (; at < tokens.length; at++) {
2797 if (matchTokens(tokens, at, 'weekday', '[')) {
2798 // Conditional weekday (Mo[3])
2799 var numbers = [];
2800
2801 // Get list of constraints
2802 var endat = parseNumRange(tokens, at+2, function(from, to, at) {
2803
2804 // bad number
2805 if (from == 0 || from < -5 || from > 5)
2806 throw formatWarnErrorMessage(nblock, at,
2807 'Number between -5 and 5 (except 0) expected');
2808
2809 if (from == to) {
2810 numbers.push(from);
2811 } else if (from < to) {
2812 for (var i = from; i <= to; i++) {
2813 // bad number
2814 if (i == 0 || i < -5 || i > 5)
2815 throw formatWarnErrorMessage(nblock, at+2,
2816 'Number between -5 and 5 (except 0) expected.');
2817
2818 numbers.push(i);
2819 }
2820 } else {
2821 throw formatWarnErrorMessage(nblock, at+2,
2822 'Bad range: ' + from + '-' + to);
2823 }
2824 });
2825
2826 if (!matchTokens(tokens, endat, ']'))
2827 throw formatWarnErrorMessage(nblock, endat, '"]" or more numbers expected.');
2828
2829 var add_days = getMoveDays(tokens, endat+1, 6, 'constrained weekdays');
2830 week_stable = false;
2831
2832 // Create selector for each list element
2833 for (var nnumber = 0; nnumber < numbers.length; nnumber++) {
2834
2835 selectors.weekday.push(function(weekday, number, add_days) { return function(date) {
2836 var date_num = getValueForDate(date, false); // Year not needed to distinguish.
2837 var start_of_this_month = new Date(date.getFullYear(), date.getMonth(), 1);
2838 var start_of_next_month = new Date(date.getFullYear(), date.getMonth() + 1, 1);
2839
2840 var target_day_this_month;
2841
2842 target_day_this_month = getDateForConstrainedWeekday(date.getFullYear(), date.getMonth(), weekday, [ number ]);
2843
2844 var target_day_with_added_days_this_month = new Date(target_day_this_month.getFullYear(),
2845 target_day_this_month.getMonth(), target_day_this_month.getDate() + add_days);
2846
2847 // The target day with added days can be before this month
2848 if (target_day_with_added_days_this_month.getTime() < start_of_this_month.getTime()) {
2849 // but in this case, the target day without the days added needs to be in this month
2850 if (target_day_this_month.getTime() >= start_of_this_month.getTime()) {
2851 // so we calculate it for the month
2852 // following this month and hope that the
2853 // target day will actually be this month.
2854
2855 target_day_with_added_days_this_month = dateAtNextWeekday(
2856 new Date(date.getFullYear(), date.getMonth() + (number > 0 ? 0 : 1) + 1, 1), weekday);
2857 target_day_this_month.setDate(target_day_with_added_days_this_month.getDate()
2858 + (number + (number > 0 ? -1 : 0)) * 7 + add_days);
2859 } else {
2860 // Calculated target day is not inside this month
2861 // therefore the specified weekday (e.g. fifth Sunday)
2862 // does not exist this month. Try it next month.
2863 return [false, start_of_next_month];
2864 }
2865 } else if (target_day_with_added_days_this_month.getTime() >= start_of_next_month.getTime()) {
2866 // The target day is in the next month. If the target day without the added days is not in this month
2867 if (target_day_this_month.getTime() >= start_of_next_month.getTime())
2868 return [false, start_of_next_month];
2869 }
2870
2871 if (add_days > 0) {
2872 var target_day_with_added_moved_days_this_month = dateAtNextWeekday(
2873 new Date(date.getFullYear(), date.getMonth() + (number > 0 ? 0 : 1) -1, 1), weekday);
2874 target_day_with_added_moved_days_this_month.setDate(target_day_with_added_moved_days_this_month.getDate()
2875 + (number + (number > 0 ? -1 : 0)) * 7 + add_days);
2876
2877 if (date_num == getValueForDate(target_day_with_added_moved_days_this_month, false))
2878 return [true, dateAtDayMinutes(date, minutes_in_day)];
2879 } else if (add_days < 0) {
2880 var target_day_with_added_moved_days_this_month = dateAtNextWeekday(
2881 new Date(date.getFullYear(), date.getMonth() + (number > 0 ? 0 : 1) + 1, 1), weekday);
2882 target_day_with_added_moved_days_this_month.setDate(target_day_with_added_moved_days_this_month.getDate()
2883 + (number + (number > 0 ? -1 : 0)) * 7 + add_days);
2884
2885 if (target_day_with_added_moved_days_this_month.getTime() >= start_of_next_month.getTime()) {
2886 if (target_day_with_added_days_this_month.getTime() >= start_of_next_month.getTime())
2887 return [false, target_day_with_added_moved_days_this_month];
2888 } else {
2889 if (target_day_with_added_days_this_month.getTime() < start_of_next_month.getTime()
2890 && getValueForDate(target_day_with_added_days_this_month, false) == date_num)
2891 return [true, dateAtDayMinutes(date, minutes_in_day)];
2892
2893 target_day_with_added_days_this_month = target_day_with_added_moved_days_this_month;
2894 }
2895 }
2896
2897 // we hit the target day
2898 if (date.getDate() == target_day_with_added_days_this_month.getDate()) {
2899 return [true, dateAtDayMinutes(date, minutes_in_day)];
2900 }
2901
2902 // we're before target day
2903 if (date.getDate() < target_day_with_added_days_this_month.getDate()) {
2904 return [false, target_day_with_added_days_this_month];
2905 }
2906
2907 // we're after target day, set check date to next month
2908 return [false, start_of_next_month];
2909 }}(tokens[at][0], numbers[nnumber], add_days[0]));
2910 }
2911
2912 at = endat + 1 + add_days[1];
2913 } else if (matchTokens(tokens, at, 'weekday')) {
2914 // Single weekday (Mo) or weekday range (Mo-Fr)
2915 var is_range = matchTokens(tokens, at+1, '-', 'weekday');
2916
2917 var weekday_from = tokens[at][0];
2918 var weekday_to = is_range ? tokens[at+2][0] : weekday_from;
2919
2920 var inside = true;
2921
2922 // handle reversed range
2923 if (weekday_to < weekday_from) {
2924 var tmp = weekday_to;
2925 weekday_to = weekday_from - 1;
2926 weekday_from = tmp + 1;
2927 inside = false;
2928 }
2929
2930 if (weekday_to < weekday_from) { // handle full range
2931 selectors.weekday.push(function(date) { return [true]; });
2932 // Not needed. If there is no selector it automatically matches everything.
2933 // WRONG: This only works if there is no other selector in this selector group ...
2934 } else {
2935 selectors.weekday.push(function(weekday_from, weekday_to, inside) { return function(date) {
2936 var ourweekday = date.getDay();
2937
2938 if (ourweekday < weekday_from || ourweekday > weekday_to) {
2939 return [!inside, dateAtNextWeekday(date, weekday_from)];
2940 } else {
2941 return [inside, dateAtNextWeekday(date, weekday_to + 1)];
2942 }
2943 }}(weekday_from, weekday_to, inside));
2944 }
2945
2946 at += is_range ? 3 : 1;
2947 } else if (matchTokens(tokens, at, 'holiday')) {
2948 week_stable = false;
2949 return parseHoliday(tokens, at, selectors, true);
2950 } else {
2951 throw formatWarnErrorMessage(nblock, at, 'Unexpected token in weekday range: ' + tokens[at][1]);
2952 }
2953
2954 if (!matchTokens(tokens, at, ','))
2955 break;
2956 }
2957
2958 if (typeof used_subparsers['weekdays'] != 'object')
2959 used_subparsers['weekdays'] = [ at ];
2960 else
2961 used_subparsers['weekdays'].push(at);
2962
2963 return at;
2964 }
2965
2966 function getMoveDays(tokens, at, max_differ, name) {
2967 var add_days = [ 0, 0 ]; // [ 'add days', 'how many tokens' ]
2968 add_days[0] = matchTokens(tokens, at, '+') || (matchTokens(tokens, at, '-') ? -1 : 0);
2969 if (add_days[0] != 0 && matchTokens(tokens, at+1, 'number', 'calcday')) {
2970 // continues with '+ 5 days' or something like that
2971 if (tokens[at+1][0] > max_differ)
2972 throw formatWarnErrorMessage(nblock, at+2,
2973 'There should be no reason to differ more than ' + max_differ + ' days from a ' + name + '. If so tell us …');
2974 add_days[0] *= tokens[at+1][0];
2975 if (add_days[0] == 0 && !done_with_warnings)
2976 parsing_warnings.push([ nblock, at+2, 'Adding 0 does not change the date. Please omit this.' ]);
2977 add_days[1] = 3;
2978 } else {
2979 add_days[0] = 0;
2980 }
2981 return add_days;
2982 }
2983 // }}}
2984
2985 // Holiday parser for public and school holidays (PH,SH) {{{
2986 // push_to_weekday will push the selector into the weekday selector array which has the desired side effect of working in conjunction with the weekday selectors (either the holiday match or the weekday), which is the normal and expected behavior.
2987 function parseHoliday(tokens, at, selectors, push_to_weekday) {
2988 for (; at < tokens.length; at++) {
2989 if (matchTokens(tokens, at, 'holiday')) {
2990 if (tokens[at][0] == 'PH') {
2991 var applying_holidays = getMatchingHoliday(tokens[at][0]);
2992
2993 // Only allow moving one day in the past or in the future.
2994 // This makes implementation easier because only one holiday is assumed to be moved to the next year.
2995 var add_days = getMoveDays(tokens, at+1, 1, 'public holiday');
2996
2997 var selector = function(applying_holidays, add_days) { return function(date) {
2998
2999 var holidays = getApplyingHolidaysForYear(applying_holidays, date.getFullYear(), add_days);
3000 // Needs to be calculated each time because of movable days.
3001
3002 var date_num = getValueForDate(date, true);
3003
3004 for (var i = 0; i < holidays.length; i++) {
3005 var next_holiday_date_num = getValueForDate(holidays[i][0], true);
3006
3007 if (date_num < next_holiday_date_num) {
3008
3009 if (add_days[0] > 0) {
3010 // Calculate the last holiday from previous year to tested against it.
3011 var holidays_last_year = getApplyingHolidaysForYear(applying_holidays, date.getFullYear() - 1, add_days);
3012 var last_holiday_last_year = holidays_last_year[holidays_last_year.length - 1];
3013 var last_holiday_last_year_num = getValueForDate(last_holiday_last_year[0], true);
3014
3015 if (date_num < last_holiday_last_year_num ) {
3016 return [ false, last_holiday_last_year[0] ];
3017 } else if (date_num == last_holiday_last_year_num) {
3018 return [true, dateAtDayMinutes(last_holiday_last_year[0], minutes_in_day),
3019 'Day after ' +last_holiday_last_year[1] ];
3020 }
3021 }
3022
3023 return [ false, holidays[i][0] ];
3024 } else if (date_num == next_holiday_date_num) {
3025 return [true, new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1),
3026 (add_days[0] > 0 ? 'Day after ' : (add_days[0] < 0 ? 'Day before ' : '')) + holidays[i][1] ];
3027 }
3028 }
3029
3030 if (add_days[0] < 0) {
3031 // Calculate the first holiday from next year to tested against it.
3032 var holidays_next_year = getApplyingHolidaysForYear(applying_holidays, date.getFullYear() + 1, add_days);
3033 var first_holidays_next_year = holidays_next_year[0];
3034 var first_holidays_next_year_num = getValueForDate(first_holidays_next_year[0], true);
3035 if (date_num == first_holidays_next_year_num) {
3036 return [true, dateAtDayMinutes(first_holidays_next_year[0], minutes_in_day),
3037 'Day before ' + first_holidays_next_year[1] ];
3038 }
3039 }
3040
3041 // continue next year
3042 return [ false, new Date(holidays[0][0].getFullYear() + 1,
3043 holidays[0][0].getMonth(),
3044 holidays[0][0].getDate()) ];
3045
3046 }}(applying_holidays, add_days);
3047
3048 if (push_to_weekday)
3049 selectors.weekday.push(selector);
3050 else
3051 selectors.holiday.push(selector);
3052
3053 at += 1 + add_days[1];
3054 } else if (tokens[at][0] == 'SH') {
3055 var applying_holidays = getMatchingHoliday(tokens[at][0]);
3056
3057 var holidays = []; // needs to be sorted each time because of movable days
3058
3059 var selector = function(applying_holidays) { return function(date) {
3060 var date_num = getValueForDate(date);
3061
3062 // Iterate over holiday array containing the different holiday ranges.
3063 for (var i = 0; i < applying_holidays.length; i++) {
3064
3065 var holiday = getSHForYear(applying_holidays[i], date.getFullYear());
3066
3067 for (var h = 0; h < holiday.length; h+=4) {
3068 var holiday_to_plus = new Date(date.getFullYear(), holiday[2+h] - 1, holiday[3+h] + 1);
3069 var holiday_from = (holiday[0+h] - 1) * 100 + holiday[1+h];
3070 var holiday_to = (holiday[2+h] - 1) * 100 + holiday[3+h];
3071 holiday_to_plus = getValueForDate(holiday_to_plus);
3072
3073 var holiday_ends_next_year = holiday_to < holiday_from;
3074
3075 if (date_num < holiday_from) { // date is before selected holiday
3076
3077 // check if we are in the holidays from the last year spanning into this year
3078 var last_year_holiday = getSHForYear(applying_holidays[applying_holidays.length - 1], date.getFullYear() - 1, false);
3079 if (typeof last_year_holiday != 'undefined') {
3080 var last_year_holiday_from = (last_year_holiday[last_year_holiday.length - 4] - 1) * 100
3081 + last_year_holiday[last_year_holiday.length - 3]; // e.g. 1125
3082 var last_year_holiday_to = (last_year_holiday[last_year_holiday.length - 2] - 1) * 100
3083 + last_year_holiday[last_year_holiday.length - 1]; // e.g. 0005
3084
3085 if (last_year_holiday_to < last_year_holiday_from && date_num < last_year_holiday_to)
3086 return [ true, new Date(date.getFullYear(),
3087 last_year_holiday[last_year_holiday.length - 2] - 1,
3088 last_year_holiday[last_year_holiday.length - 1] + 1),
3089 applying_holidays[applying_holidays.length - 1].name ];
3090 else
3091 return [ false, new Date(date.getFullYear(), holiday[0+h] - 1, holiday[1+h]) ];
3092 } else { // school holidays for last year are not defined.
3093 return [ false, new Date(date.getFullYear(), holiday[0+h] - 1, holiday[1+h]) ];
3094 }
3095 } else if (holiday_from <= date_num && (date_num < holiday_to_plus || holiday_ends_next_year)) {
3096 return [ true, new Date(date.getFullYear() + holiday_ends_next_year, holiday[2+h] - 1, holiday[3+h] + 1),
3097 applying_holidays[i].name ];
3098 } else if (holiday_to_plus == date_num) { // selected holiday end is equal to month and day
3099 if (h + 4 < holiday.length) { // next holiday is next date range of the same holidays
3100 h += 4;
3101 return [ false, new Date(date.getFullYear(), holiday[0+h] - 1, holiday[1+h]) ];
3102 } else {
3103 if (i + 1 == applying_holidays.length) { // last holidays are handled, continue all over again
3104 var holiday = getSHForYear(applying_holidays[0], date.getFullYear() + 1);
3105 return [ false, new Date(date.getFullYear() + !holiday_ends_next_year, holiday[0+h] - 1, holiday[1+h]) ];
3106 } else { // return the start of the next holidays
3107 var holiday = getSHForYear(applying_holidays[i+1], date.getFullYear());
3108 return [ false, new Date(date.getFullYear(), holiday[0] - 1, holiday[1]) ];
3109 }
3110 }
3111 }
3112 }
3113 }
3114 return [ false ];
3115 }}(applying_holidays);
3116
3117 if (push_to_weekday)
3118 selectors.weekday.push(selector);
3119 else
3120 selectors.holiday.push(selector);
3121 at += 1;
3122 }
3123 } else if (matchTokens(tokens, at, 'weekday')) {
3124 return parseWeekdayRange(tokens, at, selectors);
3125 } else {
3126 throw formatWarnErrorMessage(nblock, at, 'Unexpected token (school holiday parser): ' + tokens[at][1]);
3127 }
3128
3129 if (!matchTokens(tokens, at, ','))
3130 break;
3131 }
3132
3133 return at;
3134 }
3135
3136 // Helpers for holiday parsers {{{
3137 // Returns a number for a date which can then be used to compare just the dates (without the time).
3138 // This is necessary because a selector could be called for the middle of the day and we need to tell if it matches that day.
3139 // Example: Returns 20150015 for Jan 01 2015
3140 function getValueForDate(date, include_year) {
3141 // Implicit because undefined evaluates to false
3142 // include_year = typeof include_year != 'undefined' ? include_year : false;
3143
3144 return (include_year ? date.getFullYear() * 10000 : 0) + date.getMonth() * 100 + date.getDate();
3145 }
3146
3147 // return the school holiday definition e.g. [ 5, 25, /* to */ 6, 5 ],
3148 // for the specified year
3149 function getSHForYear(SH_hash, year, fatal) {
3150 if (typeof fatal == 'undefined')
3151 fatal = true;
3152
3153 var holiday = SH_hash[year];
3154 if (typeof holiday == 'undefined') {
3155 holiday = SH_hash['default']; // applies for any year without explicit definition
3156 if (typeof holiday == 'undefined') {
3157 if (fatal) {
3158 throw 'School holiday ' + SH_hash.name + ' has no definition for the year ' + year + '.';
3159 } else {
3160 return undefined;
3161 }
3162 }
3163 }
3164 return holiday;
3165 }
3166
3167 // Return closed holiday definition available.
3168 // First try to get the state, if missing get the country wide holidays
3169 // (which can be limited to some states).
3170 function getMatchingHoliday(type_of_holidays) {
3171 if (typeof location_cc != 'undefined') {
3172 if (holidays.hasOwnProperty(location_cc)) {
3173 if (typeof location_state != 'undefined') {
3174 if (holidays[location_cc][location_state]
3175 && holidays[location_cc][location_state][type_of_holidays]) {
3176 // if holidays for the state are specified use it
3177 // and ignore lesser specific ones (for the country)
3178 return holidays[location_cc][location_state][type_of_holidays];
3179 } else if (holidays[location_cc][type_of_holidays]) {
3180 // holidays are only defined country wide
3181 matching_holiday = {}; // holidays in the country wide scope can be limited to certain states
3182 for (var holiday_name in holidays[location_cc][type_of_holidays]) {
3183 if (typeof holidays[location_cc][type_of_holidays][holiday_name][2] === 'object') {
3184 if (-1 != indexOf.call(holidays[location_cc][type_of_holidays][holiday_name][2], location_state))
3185 matching_holiday[holiday_name] = holidays[location_cc][type_of_holidays][holiday_name];
3186 } else {
3187 matching_holiday[holiday_name] = holidays[location_cc][type_of_holidays][holiday_name];
3188 }
3189 }
3190 if (Object.keys(matching_holiday).length == 0)
3191 throw 'There are no holidays ' + type_of_holidays + ' defined for country ' + location_cc + '.'
3192 + ' Please add them: https://github.com/ypid/opening_hours.js ';
3193 return matching_holiday;
3194 } else {
3195 throw 'Holidays ' + type_of_holidays + ' are not defined for country ' + location_cc
3196 + ' and state ' + location_state + '.'
3197 + ' Please add them.';
3198 }
3199 }
3200 } else {
3201 throw 'No holidays are defined for country ' + location_cc + '. Please add them: https://github.com/ypid/opening_hours.js ';
3202 }
3203 } else { // we have no idea which holidays do apply because the country code was not provided
3204 throw 'Country code missing which is needed to select the correct holidays (see README how to provide it)'
3205 }
3206 }
3207
3208 function getMovableEventsForYear(Y) {
3209 // calculate easter
3210 var C = Math.floor(Y/100);
3211 var N = Y - 19*Math.floor(Y/19);
3212 var K = Math.floor((C - 17)/25);
3213 var I = C - Math.floor(C/4) - Math.floor((C - K)/3) + 19*N + 15;
3214 I = I - 30*Math.floor((I/30));
3215 I = I - Math.floor(I/28)*(1 - Math.floor(I/28)*Math.floor(29/(I + 1))*Math.floor((21 - N)/11));
3216 var J = Y + Math.floor(Y/4) + I + 2 - C + Math.floor(C/4);
3217 J = J - 7*Math.floor(J/7);
3218 var L = I - J;
3219 var M = 3 + Math.floor((L + 40)/44);
3220 var D = L + 28 - 31*Math.floor(M/4);
3221
3222 // calculate orthodox easter
3223 var oA = Y % 4;
3224 var oB = Y % 7;
3225 var oC = Y % 19;
3226 var oD = (19*oC + 15) % 30;
3227 var oE = (2*oA+4*oB - oD + 34) % 7;
3228 var oF = oD+oE
3229
3230 if (oF < 9) {oDate = new Date(Y, 4-1, oF+4);}
3231 else {if ((oF+4)<31) {oDate = new Date(Y, 4-1, oF+4);}
3232 else {oDate = new Date(Y, 5-1, oF-26);}}
3233
3234 // calculate last Sunday in February
3235 var lastFebruaryDay = new Date(Y, 2, 0);
3236 var lastFebruarySunday = lastFebruaryDay.getDate() - lastFebruaryDay.getDay();
3237
3238 // calculate Victoria Day. last Monday before or on May 24
3239 var may_24 = new Date(Y, 4, 24);
3240 var victoriaDay = 24 - ((6 + may_24.getDay()) % 7);
3241
3242 // calculate Canada Day. July 1st unless 1st is on Sunday, then July 2.
3243 var july_1 = new Date(Y, 6, 1);
3244 var canadaDay = july_1.getDay() === 0 ? 2 : 1;
3245
3246 // calculate first Monday for each month
3247 var firstMondays = {};
3248 for (var i = 0; i < 12; i++) {
3249 var first = new Date(Y, i, 1);
3250 var firstMonday = 1 + ((8 - first.getDay()) % 7);
3251 firstMondays[i] = firstMonday;
3252 };
3253
3254 return {
3255 'firstFebruaryMonday': new Date(Y, 1, firstMondays[1]),
3256 'lastFebruarySunday': new Date(Y, 1, lastFebruarySunday),
3257 'easter': new Date(Y, M - 1, D),
3258 'victoriaDay': new Date(Y, 4, victoriaDay),
3259 'canadaDay': new Date(Y, 6, canadaDay),
3260 'firstAugustMonday': new Date(Y, 7, firstMondays[7]),
3261 'firstSeptemberMonday': new Date(Y, 8, firstMondays[8]),
3262 'firstOctoberMonday': new Date(Y, 9, firstMondays[9]),
3263 'orthodox easter' : oDate,
3264 };
3265 }
3266
3267 function getApplyingHolidaysForYear(applying_holidays, year, add_days) {
3268 var movableDays = getMovableEventsForYear(year);
3269
3270 var sorted_holidays = [];
3271
3272 for (var holiday_name in applying_holidays) {
3273 if (typeof applying_holidays[holiday_name][0] == 'string') {
3274 var selected_movableDay = movableDays[applying_holidays[holiday_name][0]];
3275 if (!selected_movableDay)
3276 throw 'Movable day ' + applying_holidays[holiday_name][0] + ' can not not be calculated.'
3277 + ' Please add the formula how to calculate it.';
3278 var next_holiday = new Date(selected_movableDay.getFullYear(),
3279 selected_movableDay.getMonth(),
3280 selected_movableDay.getDate()
3281 + applying_holidays[holiday_name][1]
3282 );
3283 if (year != next_holiday.getFullYear())
3284 throw 'The movable day ' + applying_holidays[holiday_name][0] + ' plus '
3285 + applying_holidays[holiday_name][1]
3286 + ' days is not in the year of the movable day anymore. Currently not supported.';
3287 } else {
3288 var next_holiday = new Date(year,
3289 applying_holidays[holiday_name][0] - 1,
3290 applying_holidays[holiday_name][1]
3291 );
3292 }
3293 if (add_days[0])
3294 next_holiday.setDate(next_holiday.getDate() + add_days[0]);
3295
3296 sorted_holidays.push([ next_holiday, holiday_name ]);
3297 }
3298
3299 sorted_holidays = sorted_holidays.sort(function(a,b){
3300 if (a[0].getTime() < b[0].getTime()) return -1;
3301 if (a[0].getTime() > b[0].getTime()) return 1;
3302 return 0;
3303 });
3304
3305 return sorted_holidays;
3306 }
3307 // }}}
3308 // }}}
3309
3310 // Year range parser (2013,2016-2018,2020/2) {{{
3311 function parseYearRange(tokens, at) {
3312 for (; at < tokens.length; at++) {
3313 if (matchTokens(tokens, at, 'year')) {
3314 var is_range = false, has_period = false;
3315 if (matchTokens(tokens, at+1, '-', 'year', '/', 'number')) {
3316 var is_range = true;
3317 var has_period = true;
3318 var period = parseInt(tokens[at+4][0]);
3319 checkPeriod(at+4, period, 'year');
3320 } else {
3321 var is_range = matchTokens(tokens, at+1, '-', 'year');
3322 var has_period = matchTokens(tokens, at+1, '/', 'number');
3323 if (has_period) {
3324 var period = parseInt(tokens[at+2][0]);
3325 checkPeriod(at+2, period, 'year', 'no_end_year');
3326 } else if (matchTokens(tokens, at+1, '+')) {
3327 var period = 1;
3328 has_period = 2;
3329 }
3330 }
3331
3332 var year_from = parseInt(tokens[at][0]);
3333 // error checking {{{
3334 if (is_range && tokens[at+2][0] <= year_from) {
3335 // handle reversed range
3336 if (tokens[at+2][0] == year_from)
3337 throw formatWarnErrorMessage(nblock, at,
3338 'A year range in which the start year is equal to the end year does not make sense.'
3339 + ' Please remove the end year. E.g. "' + year_from + ' May 23"');
3340 else
3341 throw formatWarnErrorMessage(nblock, at,
3342 'A year range in which the start year is greater than the end year does not make sense.'
3343 + ' Please turn it over.');
3344 }
3345 // }}}
3346
3347 selectors.year.push(function(tokens, at, year_from, is_range, has_period, period) { return function(date) {
3348 var ouryear = date.getFullYear();
3349 var year_to = is_range ? parseInt(tokens[at+2][0]) : year_from;
3350
3351 if (ouryear < year_from ){
3352 return [false, new Date(year_from, 0, 1)];
3353 } else if (has_period) {
3354 if (year_from <= ouryear) {
3355 if (is_range && year_to < ouryear)
3356 return [false];
3357 if (period > 0) {
3358 if ((ouryear - year_from) % period == 0) {
3359 return [true, new Date(ouryear + 1, 0, 1)];
3360 } else {
3361 return [false, new Date(ouryear + period - 1, 0, 1)];
3362 }
3363 }
3364 }
3365 } else if (is_range) {
3366 if (ouryear <= year_to)
3367 return [true, new Date(year_to + 1, 0, 1)];
3368 } else if (ouryear == year_from) {
3369 return [true];
3370 }
3371
3372 return [false];
3373
3374 }}(tokens, at, year_from, is_range, has_period, period));
3375
3376 at += 1 + (is_range ? 2 : 0) + (has_period ? (has_period == 2 ? 1 : 2) : 0);
3377 } else {
3378 throw formatWarnErrorMessage(nblock, at, 'Unexpected token in year range: ' + tokens[at][1]);
3379 }
3380
3381 if (!matchTokens(tokens, at, ','))
3382 break;
3383 }
3384
3385 if (typeof used_subparsers['year ranges'] != 'object')
3386 used_subparsers['year ranges'] = [ at ];
3387 else
3388 used_subparsers['year ranges'].push(at);
3389
3390 return at;
3391 }
3392 // }}}
3393
3394 // Week range parser (week 11-20, week 1-53/2) {{{
3395 function parseWeekRange(tokens, at) {
3396 for (; at < tokens.length; at++) {
3397 if (matchTokens(tokens, at, 'number')) {
3398 var is_range = matchTokens(tokens, at+1, '-', 'number'), has_period = false;
3399 if (is_range) {
3400 has_period = matchTokens(tokens, at+3, '/', 'number');
3401 // if (week_stable) {
3402 // if (tokens[at][0] == 1 && tokens[at+2][0] >) // Maximum?
3403 // week_stable = true;
3404 // else
3405 // week_stable = false;
3406 // } else {
3407 // week_stable = false;
3408 // }
3409 }
3410
3411 selectors.week.push(function(tokens, at, is_range, has_period) { return function(date) {
3412 var ourweek = Math.floor((date - dateAtWeek(date, 0)) / msec_in_week);
3413
3414 var week_from = tokens[at][0] - 1;
3415 var week_to = is_range ? tokens[at+2][0] - 1 : week_from;
3416
3417 var start_of_next_year = new Date(date.getFullYear() + 1, 0, 1);
3418
3419 // before range
3420 if (ourweek < week_from)
3421 return [false, getMinDate(dateAtWeek(date, week_from), start_of_next_year)];
3422
3423 // we're after range, set check date to next year
3424 if (ourweek > week_to)
3425 return [false, start_of_next_year];
3426
3427 // we're in range
3428 var period;
3429 if (has_period) {
3430 var period = tokens[at+4][0];
3431 if (period > 1) {
3432 var in_period = (ourweek - week_from) % period == 0;
3433 if (in_period)
3434 return [true, getMinDate(dateAtWeek(date, ourweek + 1), start_of_next_year)];
3435 else
3436 return [false, getMinDate(dateAtWeek(date, ourweek + period - 1), start_of_next_year)];
3437 }
3438 }
3439
3440 return [true, getMinDate(dateAtWeek(date, week_to + 1), start_of_next_year)];
3441 }}(tokens, at, is_range, has_period));
3442
3443 at += 1 + (is_range ? 2 : 0) + (has_period ? 2 : 0);
3444 } else {
3445 throw formatWarnErrorMessage(nblock, at, 'Unexpected token in week range: ' + tokens[at][1]);
3446 }
3447
3448 if (!matchTokens(tokens, at, ','))
3449 break;
3450
3451 if (!matchTokens(tokens, at+1, 'number')) {
3452 at++; // we don‘t need the comma in parseGroup
3453 break;
3454 }
3455 }
3456
3457 if (typeof used_subparsers['week ranges'] != 'object')
3458 used_subparsers['week ranges'] = [ at ];
3459 else
3460 used_subparsers['week ranges'].push;
3461
3462 return at;
3463 }
3464
3465 function dateAtWeek(date, week) {
3466 var tmpdate = new Date(date.getFullYear(), 0, 1);
3467 tmpdate.setDate(1 - (tmpdate.getDay() + 6) % 7 + week * 7); // start of week n where week starts on Monday
3468 return tmpdate;
3469 }
3470
3471 function getMinDate(date /*, ...*/) {
3472 for (var i = 1; i < arguments.length; i++)
3473 if (arguments[i].getTime() < date.getTime())
3474 date = arguments[i];
3475 return date;
3476 }
3477 // }}}
3478
3479 // Month range parser (Jan,Feb-Mar) {{{
3480 // push_to_monthday will push the selector into the monthday selector array which has the desired side effect of working in conjunction with the monthday selectors (either the month match or the monthday).
3481 function parseMonthRange(tokens, at, push_to_monthday) {
3482 for (; at < tokens.length; at++) {
3483 // Use parseMonthdayRange if '<month> <daynum>' and not '<month> <hour>:<minute>'
3484 if (matchTokens(tokens, at, 'month', 'number') && !matchTokens(tokens, at+2, 'timesep', 'number')) {
3485 return parseMonthdayRange(tokens, at, nblock, true);
3486 } else if (matchTokens(tokens, at, 'month')) {
3487 // Single month (Jan) or month range (Feb-Mar)
3488 var is_range = matchTokens(tokens, at+1, '-', 'month');
3489
3490 if (is_range && week_stable) {
3491 var month_from = tokens[at][0];
3492 var month_to = tokens[at+2][0];
3493 if (month_from == (month_to + 1) % 12)
3494 week_stable = true;
3495 else
3496 week_stable = false;
3497 } else {
3498 week_stable = false;
3499 }
3500
3501 var selector = function(tokens, at, is_range) { return function(date) {
3502 var ourmonth = date.getMonth();
3503 var month_from = tokens[at][0];
3504 var month_to = is_range ? tokens[at+2][0] : month_from;
3505
3506 var inside = true;
3507
3508 // handle reversed range
3509 if (month_to < month_from) {
3510 var tmp = month_to;
3511 month_to = month_from - 1;
3512 month_from = tmp + 1;
3513 inside = false;
3514 }
3515
3516 // handle full range
3517 if (month_to < month_from)
3518 return [!inside];
3519
3520 if (ourmonth < month_from || ourmonth > month_to) {
3521 return [!inside, dateAtNextMonth(date, month_from)];
3522 } else {
3523 return [inside, dateAtNextMonth(date, month_to + 1)];
3524 }
3525 }}(tokens, at, is_range);
3526
3527 if (push_to_monthday === true)
3528 selectors.monthday.push(selector);
3529 else
3530 selectors.month.push(selector);
3531
3532 at += is_range ? 3 : 1;
3533 } else {
3534 throw formatWarnErrorMessage(nblock, at, 'Unexpected token in month range: ' + tokens[at][1]);
3535 }
3536
3537 if (!matchTokens(tokens, at, ','))
3538 break;
3539 }
3540
3541 if (typeof used_subparsers['months'] != 'object')
3542 used_subparsers['months'] = [ at ];
3543 else
3544 used_subparsers['months'].push(at);
3545
3546 return at;
3547 }
3548
3549 function dateAtNextMonth(date, month) {
3550 return new Date(date.getFullYear(), month < date.getMonth() ? month + 12 : month);
3551 }
3552 // }}}
3553
3554 // Month day range parser (Jan 26-31; Jan 26-Feb 26) {{{
3555 // push_to_month will push the selector into the month selector array which has the desired side effect of working in conjunction with the month selectors (either the month match or the monthday).
3556 function parseMonthdayRange(tokens, at, nblock, push_to_month) {
3557 for (; at < tokens.length; at++) {
3558 var has_year = [], has_month = [], has_event = [], has_calc = [], has_constrained_weekday = [], has_calc = [];
3559 has_year[0] = matchTokens(tokens, at, 'year');
3560 has_month[0] = matchTokens(tokens, at+has_year[0], 'month', 'number');
3561 has_event[0] = matchTokens(tokens, at+has_year[0], 'event');
3562 if (has_event[0])
3563 has_calc[0] = getMoveDays(tokens, at+has_year[0]+1, 200, 'event like easter');
3564
3565 if (matchTokens(tokens, at+has_year[0], 'month', 'weekday', '[')) {
3566 has_constrained_weekday[0] = getConstrainedWeekday(tokens, at+has_year[0]+3);
3567 has_calc[0] = getMoveDays(tokens, has_constrained_weekday[0][1], 6, 'constrained weekdays');
3568 var at_range_sep = has_constrained_weekday[0][1] + (typeof has_calc[0] != 'undefined' && has_calc[0][1] ? 3 : 0);
3569 } else {
3570 var at_range_sep = at+has_year[0]
3571 + (has_event[0]
3572 ? (typeof has_calc[0] != 'undefined' && has_calc[0][1] ? 4 : 1)
3573 : 2);
3574 }
3575
3576 if ((has_month[0] || has_event[0] || has_constrained_weekday[0]) && matchTokens(tokens, at_range_sep, '-')) {
3577 has_year[1] = matchTokens(tokens, at_range_sep+1, 'year');
3578 var at_sec_event_or_month = at_range_sep+1+has_year[1];
3579 has_month[1] = matchTokens(tokens, at_sec_event_or_month, 'month', 'number');
3580 if (!has_month[1]) {
3581 has_event[1] = matchTokens(tokens, at_sec_event_or_month, 'event');
3582 if (has_event[1]) {
3583 has_calc[1] = getMoveDays(tokens, at_sec_event_or_month+1, 366, 'event like easter');
3584 } else if (matchTokens(tokens, at_sec_event_or_month, 'month', 'weekday', '[')) {
3585 has_constrained_weekday[1] = getConstrainedWeekday(tokens, at_sec_event_or_month+3);
3586 has_calc[1] = getMoveDays(tokens, has_constrained_weekday[1][1], 6, 'constrained weekdays');
3587 }
3588 }
3589 }
3590
3591 // monthday range like Jan 26-Feb 26 {{{
3592 if (has_year[0] == has_year[1] && (has_month[1] || has_event[1] || has_constrained_weekday[1])) {
3593
3594 if (has_month[0])
3595 isValidDate(tokens[at+has_year[0]][0], tokens[at+has_year[0]+1][0], nblock, at+has_year[0]+1);
3596 if (has_month[1])
3597 isValidDate(tokens[at_sec_event_or_month][0], tokens[at_sec_event_or_month+1][0], nblock, at+has_year[0]+1);
3598
3599 var selector = function(tokens, at, nblock, has_year, has_event, has_calc, at_sec_event_or_month, has_constrained_weekday) { return function(date) {
3600 var start_of_next_year = new Date(date.getFullYear() + 1, 0, 1);
3601
3602 if (has_event[0]) {
3603 var movableDays = getMovableEventsForYear(has_year[0] ? parseInt(tokens[at][0]) : date.getFullYear());
3604 var from_date = movableDays[tokens[at+has_year[0]][0]];
3605
3606 if (typeof has_calc[0] != 'undefined' && has_calc[0][1]) {
3607 var from_year_before_calc = from_date.getFullYear();
3608 from_date.setDate(from_date.getDate() + has_calc[0][0]);
3609 if (from_year_before_calc != from_date.getFullYear())
3610 throw formatWarnErrorMessage(nblock, at+has_year[0]+has_calc[0][1]*3,
3611 'The movable day ' + tokens[at+has_year[0]][0] + ' plus ' + has_calc[0][0]
3612 + ' days is not in the year of the movable day anymore. Currently not supported.');
3613 }
3614 } else if (has_constrained_weekday[0]) {
3615 var from_date = getDateForConstrainedWeekday((has_year[0] ? tokens[at][0] : date.getFullYear()), // year
3616 tokens[at+has_year[0]][0], // month
3617 tokens[at+has_year[0]+1][0], // weekday
3618 has_constrained_weekday[0],
3619 has_calc[0]);
3620 // var from_date_without_calc = getDateForConstrainedWeekday((has_year[0] ? tokens[at][0] : date.getFullYear()), // year
3621 // tokens[at+has_year[0]][0], // month
3622 // tokens[at+has_year[0]+1][0], // weekday
3623 // has_constrained_weekday[0],
3624 // [ 0, 0 ]);
3625 // if (from_date_without_calc.getFullYear() != from_date.getFullYear())
3626 // throw formatWarnErrorMessage(nblock, at+has_year[0]+has_calc[0][1],
3627 // 'The constrained ' + weekdays[tokens[at+has_year[0]+1][0]] + ' plus ' + has_calc[0][0]
3628 // + ' days is not in the year of the movable day anymore. Currently not supported.');
3629 } else {
3630 var from_date = new Date((has_year[0] ? tokens[at][0] : date.getFullYear()),
3631 tokens[at+has_year[0]][0], tokens[at+has_year[0]+1][0]);
3632 }
3633
3634 if (has_event[1]) {
3635 var movableDays = getMovableEventsForYear(has_year[1]
3636 ? parseInt(tokens[at_sec_event_or_month-1][0])
3637 : date.getFullYear());
3638 var to_date = movableDays[tokens[at_sec_event_or_month][0]];
3639
3640 if (typeof has_calc[1] != 'undefined' && has_calc[1][1]) {
3641 var to_year_before_calc = to_date.getFullYear();
3642 to_date.setDate(to_date.getDate() + has_calc[1][0]);
3643 if (to_year_before_calc != to_date.getFullYear())
3644 throw formatWarnErrorMessage(nblock, at_sec_event_or_month+has_calc[1][1],
3645 'The movable day ' + tokens[at_sec_event_or_month][0] + ' plus ' + has_calc[1][0]
3646 + ' days is not in the year of the movable day anymore. Currently not supported.');
3647 }
3648 } else if (has_constrained_weekday[1]) {
3649 var to_date = getDateForConstrainedWeekday((has_year[1] ? tokens[at_sec_event_or_month-1][0] : date.getFullYear()), // year
3650 tokens[at_sec_event_or_month][0], // month
3651 tokens[at_sec_event_or_month+1][0], // weekday
3652 has_constrained_weekday[1],
3653 has_calc[1]);
3654 } else {
3655 var to_date = new Date((has_year[1] ? tokens[at_sec_event_or_month-1][0] : date.getFullYear()),
3656 tokens[at_sec_event_or_month][0], tokens[at_sec_event_or_month+1][0] + 1);
3657 }
3658
3659 var inside = true;
3660
3661 if (to_date < from_date) {
3662 var tmp = to_date;
3663 to_date = from_date;
3664 from_date = tmp;
3665 inside = false;
3666 }
3667
3668 if (date.getTime() < from_date.getTime()) {
3669 return [!inside, from_date];
3670 } else if (date.getTime() < to_date.getTime()) {
3671 return [inside, to_date];
3672 } else {
3673 if (has_year[0]) {
3674 return [!inside];
3675 } else {
3676 // // back matching, if from_date is moved to last year
3677 // var from_date_next_year = getDateForConstrainedWeekday(date.getFullYear() + 1, // year
3678 // tokens[at+has_year[0]][0], // month
3679 // tokens[at+has_year[0]+1][0], // weekday
3680 // has_constrained_weekday[0],
3681 // has_calc[0]);
3682 // if (date.getFullYear() == from_date_next_year.getFullYear()) {
3683 // if (date.getTime() < from_date_next_year.getTime()) {
3684 // return [!inside, from_date_next_year];
3685 // }
3686 // }
3687
3688 return [!inside, start_of_next_year];
3689 }
3690 }
3691 }}(tokens, at, nblock, has_year, has_event, has_calc, at_sec_event_or_month, has_constrained_weekday);
3692
3693 if (push_to_month === true)
3694 selectors.month.push(selector);
3695 else
3696 selectors.monthday.push(selector);
3697
3698 at = (has_constrained_weekday[1]
3699 ? has_constrained_weekday[1][1]
3700 : at_sec_event_or_month + (has_event[1] ? 1 : 2))
3701 + (typeof has_calc[1] != 'undefined' ? has_calc[1][1] : 0);
3702
3703 // }}}
3704 // Monthday range like Jan 26-31 {{{
3705 } else if (has_month[0]) {
3706
3707 has_year = has_year[0];
3708 var year = tokens[at][0]; // Could be month if has no year. Tested later.
3709 var month = tokens[at+has_year][0];
3710
3711 var first_round = true;
3712
3713 do {
3714 var range_from = tokens[at+1 + has_year][0];
3715 var is_range = matchTokens(tokens, at+2+has_year, '-', 'number');
3716 var period = undefined;
3717 var range_to = tokens[at+has_year+(is_range ? 3 : 1)][0] + 1;
3718 if (is_range && matchTokens(tokens, at+has_year+4, '/', 'number')) {
3719 period = tokens[at+has_year+5][0];
3720 checkPeriod(at+has_year+5, period, 'day');
3721 }
3722
3723 if (first_round) {
3724 var at_timesep_if_monthRange = at + has_year + 1 // at month number
3725 + (is_range ? 2 : 0) + (period ? 2 : 0)
3726 + !(is_range || period); // if not range nor has period, add one
3727
3728 // Check for '<month> <timespan>'
3729 if (matchTokens(tokens, at_timesep_if_monthRange, 'timesep', 'number')
3730 && (matchTokens(tokens, at_timesep_if_monthRange+2, '+')
3731 || matchTokens(tokens, at_timesep_if_monthRange+2, '-')
3732 || oh_mode != 0))
3733 return parseMonthRange(tokens, at);
3734 }
3735
3736 // error checking {{{
3737 if (range_to < range_from)
3738 throw formatWarnErrorMessage(nblock, at+has_year+3,
3739 'Range in wrong order. From day is greater than to day.');
3740 isValidDate(month, range_from, nblock, at+1 + has_year);
3741 isValidDate(month, range_to - 1 /* added previously */,
3742 nblock, at+has_year+(is_range ? 3 : 1));
3743 // }}}
3744
3745 var selector = function(year, has_year, month, range_from, range_to, period) { return function(date) {
3746 var start_of_next_year = new Date(date.getFullYear() + 1, 0, 1);
3747
3748 var from_date = new Date(has_year ? year : date.getFullYear(),
3749 month, range_from);
3750 if (month == 1 && range_from != from_date.getDate()) // Only on leap years does this day exist.
3751 return [false]; // If day 29 does not exist,
3752 // then the date object adds one day to date
3753 // and this selector should not match.
3754 var to_date = new Date(from_date.getFullYear(),
3755 month, range_to);
3756 if (month == 1 && is_range && range_to != to_date.getDate()) // Only on leap years does this day exist.
3757 return [false];
3758
3759 if (date.getTime() < from_date.getTime())
3760 return [false, from_date];
3761 else if (date.getTime() >= to_date.getTime())
3762 return [false, start_of_next_year];
3763 else if (!period)
3764 return [true, to_date];
3765
3766 var nday = Math.floor((date.getTime() - from_date.getTime()) / msec_in_day);
3767 var in_period = nday % period;
3768
3769 if (in_period == 0)
3770 return [true, new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1)];
3771 else
3772 return [false, new Date(date.getFullYear(), date.getMonth(), date.getDate() + period - in_period)];
3773
3774 }}(year, has_year, month, range_from, range_to, period);
3775
3776 if (push_to_month === true)
3777 selectors.month.push(selector);
3778 else
3779 selectors.monthday.push(selector);
3780
3781 at += 2 + has_year + (is_range ? 2 : 0) + (period ? 2 : 0);
3782
3783 first_round = false;
3784 }
3785 while (matchTokens(tokens, at, ',', 'number'))
3786
3787
3788 // }}}
3789 // Only event like easter {{{
3790 } else if (has_event[0]) {
3791
3792 var selector = function(tokens, at, nblock, has_year, add_days) { return function(date) {
3793
3794 // console.log('enter selector with date: ' + date);
3795 var movableDays = getMovableEventsForYear((has_year ? tokens[at][0] : date.getFullYear()));
3796 var event_date = movableDays[tokens[at+has_year][0]];
3797 if (!event_date)
3798 throw 'Movable day ' + tokens[at+has_year][0] + ' can not not be calculated.'
3799 + ' Please add the formula how to calculate it.';
3800
3801 if (add_days[0]) {
3802 event_date.setDate(event_date.getDate() + add_days[0]);
3803 if (date.getFullYear() != event_date.getFullYear())
3804 throw formatWarnErrorMessage(nblock, at+has_year+add_days[1], 'The movable day ' + tokens[at+has_year][0] + ' plus '
3805 + add_days[0]
3806 + ' days is not in the year of the movable day anymore. Currently not supported.');
3807 }
3808
3809 if (date.getTime() < event_date.getTime())
3810 return [false, event_date];
3811 // else if (date.getTime() < event_date.getTime() + msec_in_day) // does not work because of daylight saving times
3812 else if (event_date.getMonth() * 100 + event_date.getDate() == date.getMonth() * 100 + date.getDate())
3813 return [true, new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1)];
3814 else
3815 return [false, new Date(date.getFullYear() + 1, 0, 1)];
3816
3817 }}(tokens, at, nblock, has_year[0], has_calc[0]);
3818
3819 if (push_to_month === true)
3820 selectors.month.push(selector);
3821 else
3822 selectors.monthday.push(selector);
3823
3824 at += has_year[0] + has_event[0] + (typeof has_calc[0][1] != 'undefined' && has_calc[0][1] ? 3 : 0);
3825 // }}}
3826 } else if (has_constrained_weekday[0]) {
3827 at = parseMonthRange(tokens, at);
3828 } else if (matchTokens(tokens, at, 'month')) {
3829 return parseMonthRange(tokens, at, true);
3830 } else {
3831 // throw 'Unexpected token in monthday range: "' + tokens[at] + '"';
3832 return at;
3833 }
3834
3835 if (!matchTokens(tokens, at, ','))
3836 break;
3837 }
3838
3839 if (typeof used_subparsers['monthday ranges'] != 'object')
3840 used_subparsers['monhday ranges'] = [ at ];
3841 else
3842 used_subparsers['monhday ranges'].push(at);
3843
3844 return at;
3845 }
3846 // }}}
3847
3848 // Main selector traversal function (return state array for date) {{{
3849 this.getStatePair = function(date) {
3850 var resultstate = false;
3851 var changedate;
3852 var unknown = false;
3853 var comment;
3854 var match_block;
3855
3856 var date_matching_blocks = [];
3857
3858 for (var nblock = 0; nblock < blocks.length; nblock++) {
3859 var matching_date_block = true;
3860 // console.log(nblock, 'length', blocks[nblock].date.length);
3861
3862 // Try each date selector type
3863 for (var ndateselector = 0; ndateselector < blocks[nblock].date.length; ndateselector++) {
3864 var dateselectors = blocks[nblock].date[ndateselector];
3865 // console.log(nblock, ndateselector);
3866
3867 var has_matching_selector = false;
3868 for (var datesel = 0; datesel < dateselectors.length; datesel++) {
3869 var res = dateselectors[datesel](date);
3870 if (res[0]) {
3871 has_matching_selector = true;
3872
3873 if (typeof res[2] == 'string') { // holiday name
3874 comment = [ res[2] ];
3875 }
3876
3877 }
3878 if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1].getTime() < changedate.getTime()))
3879 changedate = res[1];
3880 }
3881
3882 if (!has_matching_selector) {
3883 matching_date_block = false;
3884 // We can ignore other date selectors, as the state won't change
3885 // anyway until THIS selector matches (due to conjunction of date
3886 // selectors of different types).
3887 // This is also an optimization, if widest date selector types
3888 // are checked first.
3889 break;
3890 }
3891
3892 }
3893
3894 if (matching_date_block) {
3895 // The following lines implement date overwriting logic (e.g. for
3896 // "Mo-Fr 10:00-20:00; We 10:00-16:00", We block overrides Mo-Fr block.
3897 //
3898 // This is the only way to be consistent. I thought about ("22:00-02:00; Tu 12:00-14:00") letting Th override 22:00-02:00 partly:
3899 // Like: Th 00:00-02:00,12:00-14:00 but this would result in including 22:00-00:00 for Th which is probably not what you want.
3900 if (blocks[nblock].date.length > 0 && (blocks[nblock].meaning || blocks[nblock].unknown)
3901 && !blocks[nblock].wrapped && !blocks[nblock].additional && !blocks[nblock].fallback) {
3902 // var old_date_matching_blocks = date_matching_blocks;
3903 date_matching_blocks = [];
3904 // for (var nblock = 0; nblock < old_date_matching_blocks.length; nblock++) {
3905 // if (!blocks[old_date_matching_blocks[nblock]].wrapped)
3906 // date_matching_blocks.push(nblock);
3907 // }
3908 }
3909 date_matching_blocks.push(nblock);
3910 }
3911 }
3912
3913 block:
3914 for (var nblock = 0; nblock < date_matching_blocks.length; nblock++) {
3915 var block = date_matching_blocks[nblock];
3916
3917 // console.log('Processing block ' + block + ':\t' + blocks[block].comment + ' with date', date,
3918 // 'and', blocks[block].time.length, 'time selectors');
3919
3920 // there is no time specified, state applies to the whole day
3921 if (blocks[block].time.length == 0) {
3922 // console.log('there is no time', date);
3923 if (!blocks[block].fallback || (blocks[block].fallback && !(resultstate || unknown))) {
3924 resultstate = blocks[block].meaning;
3925 unknown = blocks[block].unknown;
3926 match_block = block;
3927
3928 if (typeof blocks[block].comment != 'undefined')
3929 comment = blocks[block].comment;
3930 else if (typeof comment == 'object') // holiday name
3931 comment = comment[0];
3932
3933 if (blocks[block].fallback)
3934 break block; // fallback block matched, no need for checking the rest
3935 }
3936 }
3937
3938 for (var timesel = 0; timesel < blocks[block].time.length; timesel++) {
3939 var res = blocks[block].time[timesel](date);
3940
3941 // console.log('res:', res);
3942 if (res[0]) {
3943 if (!blocks[block].fallback || (blocks[block].fallback && !(resultstate || unknown))) {
3944 resultstate = blocks[block].meaning;
3945 unknown = blocks[block].unknown;
3946 match_block = block;
3947
3948 if (typeof blocks[block].comment == 'string') // only use comment if one is specified
3949 comment = blocks[block].comment;
3950 else if (typeof comment == 'object') // holiday name
3951 comment = comment[0];
3952 else if (comment === 'Specified as open end. Closing time was guessed.')
3953 comment = blocks[block].comment;
3954
3955 // open end
3956 if (typeof res[2] == 'boolean' && res[2] && (resultstate || unknown)) {
3957 if (typeof comment == 'undefined')
3958 comment = 'Specified as open end. Closing time was guessed.';
3959 resultstate = false;
3960 unknown = true;
3961 }
3962
3963 if (blocks[block].fallback) {
3964 if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1] < changedate))
3965 changedate = res[1];
3966
3967 // break block; // Fallback block matched, no need for checking the rest.
3968 // WRONG: What if 'off' is used after fallback block.
3969 }
3970 }
3971 }
3972 if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1] < changedate))
3973 changedate = res[1];
3974 }
3975 }
3976
3977 // console.log('changedate', changedate, resultstate, comment, match_block);
3978 return [ resultstate, changedate, unknown, comment, match_block ];
3979 }
3980 // }}}
3981
3982 // generate prettified value based on tokens {{{
3983 function prettifySelector(tokens, at, last_at, conf, used_parseTimeRange) {
3984 var value = '';
3985 var start_at = at;
3986 while (at < last_at) {
3987 if (matchTokens(tokens, at, 'weekday')) { // FIXME
3988 if (!conf.leave_weekday_sep_one_day_betw
3989 && at - start_at > 1 && (matchTokens(tokens, at-1, ',') || matchTokens(tokens, at-1, '-'))
3990 && matchTokens(tokens, at-2, 'weekday')
3991 && tokens[at][0] == (tokens[at-2][0] + 1) % 7) {
3992 value = value.substring(0, value.length - 1) + conf.sep_one_day_between;
3993 }
3994 value += weekdays[tokens[at][0]];
3995 } else if (at - start_at > 0 && used_parseTimeRange > 0 && matchTokens(tokens, at-1, 'timesep')
3996 && matchTokens(tokens, at, 'number')) { // '09:0' -> '09:00'
3997 value += (tokens[at][0] < 10 ? '0' : '') + tokens[at][0].toString();
3998 } else if (used_parseTimeRange > 0 && conf.leading_zero_hour && at != tokens.length
3999 && matchTokens(tokens, at, 'number')
4000 && matchTokens(tokens, at+1, 'timesep')) { // '9:00' -> '19:00'
4001 value += (tokens[at][0] < 10 ? (tokens[at][0] == 0 && conf.one_zero_if_hour_zero ? '' : '0') : '') + tokens[at][0].toString();
4002 } else if (used_parseTimeRange > 0 && at + 2 < last_at
4003 && matchTokens(tokens, at, 'number')
4004 && matchTokens(tokens, at+1, '-')
4005 && matchTokens(tokens, at+2, 'number')) { // '9-18' -> '09:00-18:00'
4006 value += (tokens[at][0] < 10 ? (tokens[at][0] == 0 && conf.one_zero_if_hour_zero ? '' : '0') : '') + tokens[at][0].toString();
4007 value += ':00-';
4008 value += (tokens[at+2][0] < 10 ? '0' : '') + tokens[at+2][0].toString();
4009 value += ':00';
4010 at += 2;
4011 } else if (matchTokens(tokens, at, 'comment')) {
4012 value += '"' + tokens[at][0].toString() + '"';
4013 } else if (matchTokens(tokens, at, 'closed')) {
4014 value += (conf.leave_off_closed ? tokens[at][0] : conf.keyword_for_off_closed);
4015 } else if (at - start_at > 0 && matchTokens(tokens, at, 'number')
4016 && (matchTokens(tokens, at-1, 'month')
4017 || matchTokens(tokens, at-1, 'week')
4018 )) {
4019 value += ' ' + tokens[at][0];
4020 } else if (at - start_at > 0 && matchTokens(tokens, at, 'month')
4021 && matchTokens(tokens, at-1, 'year')) {
4022 value += ' ' + months[[tokens[at][0]]];
4023 } else if (at - start_at > 0 && matchTokens(tokens, at, 'event')
4024 && matchTokens(tokens, at-1, 'year')) {
4025 value += ' ' + tokens[at][0];
4026 } else if (matchTokens(tokens, at, 'month')) {
4027 value += months[[tokens[at][0]]];
4028 if (at + 1 < last_at && matchTokens(tokens, at+1, 'weekday'))
4029 value += ' ';
4030 } else if (at + 2 < last_at
4031 && (matchTokens(tokens, at, '-') || matchTokens(tokens, at, '+'))
4032 && matchTokens(tokens, at+1, 'number', 'calcday')) {
4033 value += ' ' + tokens[at][0] + tokens[at+1][0] + ' day' + (Math.abs(tokens[at+1][0]) == 1 ? '' : 's');
4034 at += 2;
4035 } else {
4036 // if (matchTokens(tokens, at, 'open') || matchTokens(tokens, at, 'unknown'))
4037 // value += ' ';
4038
4039 value += tokens[at][0].toString();
4040 }
4041 at++;
4042 }
4043 return value + ' ';
4044 }
4045 // }}}
4046
4047 //======================================================================
4048 // Public interface {{{
4049 // All functions below are considered public.
4050 //======================================================================
4051
4052 //======================================================================
4053 // Iterator interface {{{
4054 //======================================================================
4055 this.getIterator = function(date) {
4056 return new function(oh) {
4057 if (typeof date === 'undefined')
4058 date = new Date();
4059
4060 var prevstate = [ undefined, date, undefined, undefined, undefined ];
4061 var state = oh.getStatePair(date);
4062
4063 this.setDate = function(date) {
4064 if (typeof date != 'object')
4065 throw 'Date as parameter needed.';
4066
4067 prevstate = [ undefined, date, undefined, undefined, undefined ];
4068 state = oh.getStatePair(date);
4069 }
4070
4071 this.getDate = function() {
4072 return prevstate[1];
4073 }
4074
4075 this.getState = function() {
4076 return state[0];
4077 }
4078
4079 this.getUnknown = function() {
4080 return state[2];
4081 }
4082
4083 this.getStateString = function(past) {
4084 return (state[0] ? 'open' : (state[2] ? 'unknown' : (past ? 'closed' : 'close')));
4085 }
4086
4087 this.getComment = function() {
4088 return state[3];
4089 }
4090
4091 this.getMatchingRule = function(user_conf) {
4092 if (typeof state[4] == 'undefined')
4093 return undefined;
4094
4095 if (typeof user_conf != 'object')
4096 var user_conf = {};
4097 for (key in default_prettify_conf) {
4098 if (typeof user_conf[key] == 'undefined')
4099 user_conf[key] = default_prettify_conf[key];
4100 }
4101
4102 var really_done_with_warnings = done_with_warnings; // getWarnings can be called later.
4103 done_with_warnings = true;
4104 prettified_value = '';
4105 var selectors = { // Not really needed. This whole thing is only necessary because of the token used for additional blocks.
4106 time: [], weekday: [], holiday: [], week: [], month: [], monthday: [], year: [], wraptime: [],
4107
4108 fallback: false, // does not matter
4109 additional: false,
4110 meaning: true,
4111 unknown: false,
4112 comment: undefined,
4113 };
4114
4115 // token block index used to build the selectors for this block.
4116 var token_block = blocks[state[4]].build_from_token_block;
4117 parseGroup(tokens[token_block[0]][0], token_block[1], selectors, state[4], user_conf);
4118
4119 if (prettified_value[prettified_value.length - 1] == ',')
4120 prettified_value = prettified_value.substr(0, prettified_value.length - 1);
4121
4122 done_with_warnings = really_done_with_warnings;
4123
4124 return prettified_value;
4125 }
4126
4127 this.advance = function(datelimit) {
4128 if (typeof datelimit === 'undefined')
4129 datelimit = new Date(prevstate[1].getTime() + msec_in_day * 366 * 5);
4130 else if (datelimit.getTime() <= prevstate[1].getTime())
4131 return false; // The limit for advance needs to be after the current time.
4132
4133 do {
4134 // open range, we won't be able to advance
4135 if (typeof state[1] === 'undefined')
4136 return false;
4137
4138 // console.log('\n' + 'previous check time:', prevstate[1]
4139 // + ', current check time:',
4140 // // (state[1].getHours() < 10 ? '0' : '') + state[1].getHours() +
4141 // // ':'+(state[1].getMinutes() < 10 ? '0' : '')+ state[1].getMinutes(), state[1].getDate(),
4142 // state[1],
4143 // (state[0] ? 'open' : (state[2] ? 'unknown' : 'closed')) + ', comment:', state[3]);
4144
4145 // We're going backwards or staying at place.
4146 // This always indicates coding error in a selector code.
4147 if (state[1].getTime() <= prevstate[1].getTime())
4148 throw 'Fatal: infinite loop in nextChange';
4149
4150 // don't advance beyond limits (same as open range)
4151 if (state[1].getTime() >= datelimit.getTime())
4152 return false;
4153
4154 // do advance
4155 prevstate = state;
4156 state = oh.getStatePair(prevstate[1]);
4157 } while (state[0] === prevstate[0] && state[2] === prevstate[2] && state[3] === prevstate[3]);
4158 return true;
4159 }
4160 }(this);
4161 }
4162 // }}}
4163
4164 // Simple API {{{
4165
4166 // Get parse warnings.
4167 // Returns an empty string if there are no warnings.
4168 this.getWarnings = function() {
4169 var it = this.getIterator();
4170 return getWarnings(it);
4171 }
4172
4173 // Get a nicely formated value.
4174 this.prettifyValue = function(user_conf) {
4175 if (typeof user_conf != 'object')
4176 var user_conf = {};
4177
4178 for (key in default_prettify_conf) {
4179 if (typeof user_conf[key] == 'undefined')
4180 user_conf[key] = default_prettify_conf[key];
4181 }
4182
4183 var really_done_with_warnings = done_with_warnings; // getWarnings can be called later.
4184 done_with_warnings = true;
4185
4186 prettified_value = '';
4187 for (var nblock = 0; nblock < tokens.length; nblock++) {
4188 if (tokens[nblock][0].length == 0) continue;
4189 // Block does contain nothing useful e.g. second block of '10:00-12:00;' (empty) which needs to be handled.
4190
4191 if (nblock != 0)
4192 prettified_value += (tokens[nblock][1]
4193 ? user_conf.block_sep_string + '|| '
4194 : (user_conf.print_semicolon ? ';' : '') + user_conf.block_sep_string);
4195
4196 var continue_at = 0;
4197 do {
4198 if (continue_at == tokens[nblock][0].length) break;
4199 // Block does contain nothing useful e.g. second block of '10:00-12:00,' (empty) which needs to be handled.
4200
4201 var selectors = { // Not really needed. This whole thing is only necessary because of the token used for additional blocks.
4202 time: [], weekday: [], holiday: [], week: [], month: [], monthday: [], year: [], wraptime: [],
4203
4204 fallback: tokens[nblock][1],
4205 additional: continue_at ? true : false,
4206 meaning: true,
4207 unknown: false,
4208 comment: undefined,
4209 };
4210
4211 continue_at = parseGroup(tokens[nblock][0], continue_at, selectors, nblock, user_conf);
4212
4213 if (typeof continue_at == 'object') {
4214 continue_at = continue_at[0];
4215 prettified_value += user_conf.block_sep_string;
4216 } else {
4217 continue_at = 0;
4218 }
4219
4220 } while (continue_at)
4221 }
4222
4223 done_with_warnings = really_done_with_warnings;
4224
4225 return prettified_value;
4226 }
4227
4228 // Check whether facility is `open' on the given date (or now).
4229 this.getState = function(date) {
4230 var it = this.getIterator(date);
4231 return it.getState();
4232 }
4233
4234 // If the state of a amenity is conditional. Conditions can be expressed in comments.
4235 // True will only be returned if the state is false as the getState only
4236 // returns true if the amenity is really open. So you may want to check
4237 // the result of getUnknown if getState returned false.
4238 this.getUnknown = function(date) {
4239 var it = this.getIterator(date);
4240 return it.getUnknown();
4241 }
4242
4243 // Return state string. Either 'open', 'unknown' or 'closed'.
4244 this.getStateString = function(date, past) {
4245 var it = this.getIterator(date);
4246 return it.getStateString(past);
4247 }
4248
4249 // Returns the comment.
4250 // If no comment is specified this function will return undefined.
4251 this.getComment = function(date) {
4252 var it = this.getIterator(date);
4253 return it.getComment();
4254 }
4255
4256 // Return the block which matched thus deterrents the current state.
4257 this.getMatchingRule = function(date) {
4258 var it = this.getIterator(date);
4259 return it.getMatchingRule();
4260 }
4261
4262 // Returns time of next status change.
4263 this.getNextChange = function(date, maxdate) {
4264 var it = this.getIterator(date);
4265 if (!it.advance(maxdate))
4266 return undefined;
4267 return it.getDate();
4268 }
4269
4270 // Checks whether open intervals are same for every week.
4271 this.isWeekStable = function() {
4272 return week_stable;
4273 }
4274 // }}}
4275
4276 // High-level API {{{
4277
4278 // return array of open intervals between two dates
4279 this.getOpenIntervals = function(from, to) {
4280 var res = [];
4281
4282 var it = this.getIterator(from);
4283
4284 if (it.getState() || it.getUnknown())
4285 res.push([from, undefined, it.getUnknown(), it.getComment()]);
4286
4287 while (it.advance(to)) {
4288 if (it.getState() || it.getUnknown()) {
4289 if (res.length != 0 && typeof res[res.length - 1][1] == 'undefined') {
4290 // last state was also open or unknown
4291 res[res.length - 1][1] = it.getDate();
4292 }
4293 res.push([it.getDate(), undefined, it.getUnknown(), it.getComment()]);
4294 } else {
4295 if (res.length != 0 && typeof res[res.length - 1][1] == 'undefined') {
4296 // only use the first time as closing/change time and ignore closing times which might follow
4297 res[res.length - 1][1] = it.getDate();
4298 }
4299 }
4300 }
4301
4302 if (res.length > 0 && typeof res[res.length - 1][1] === 'undefined')
4303 res[res.length - 1][1] = to;
4304
4305 return res;
4306 }
4307
4308 // return total number of milliseconds a facility is open within a given date range
4309 this.getOpenDuration = function(from, to) {
4310 // console.log('-----------');
4311
4312 var open = 0;
4313 var unknown = 0;
4314
4315 var it = this.getIterator(from);
4316 var prevdate = (it.getState() || it.getUnknown()) ? from : undefined;
4317 var prevstate = it.getState();
4318 var prevunknown = it.getUnknown();
4319
4320 while (it.advance(to)) {
4321 if (it.getState() || it.getUnknown()) {
4322
4323 if (typeof prevdate !== 'undefined') {
4324 // last state was also open or unknown
4325 if (prevunknown) //
4326 unknown += it.getDate().getTime() - prevdate.getTime();
4327 else if (prevstate)
4328 open += it.getDate().getTime() - prevdate.getTime();
4329 }
4330
4331 prevdate = it.getDate();
4332 prevstate = it.getState();
4333 prevunknown = it.getUnknown();
4334 // console.log('if', prevdate, open / (1000 * 60 * 60), unknown / (1000 * 60 * 60));
4335 } else {
4336 // console.log('else', prevdate);
4337 if (typeof prevdate !== 'undefined') {
4338 if (prevunknown)
4339 unknown += it.getDate().getTime() - prevdate.getTime();
4340 else
4341 open += it.getDate().getTime() - prevdate.getTime();
4342 prevdate = undefined;
4343 }
4344 }
4345 }
4346
4347 if (typeof prevdate !== 'undefined') {
4348 if (prevunknown)
4349 unknown += to.getTime() - prevdate.getTime();
4350 else
4351 open += to.getTime() - prevdate.getTime();
4352 }
4353
4354 return [ open, unknown ];
4355 }
4356 // }}}
4357 // }}}
4358 }
4359}));
Note: See TracBrowser for help on using the repository browser.