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

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

update opening_hours.js

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