source: josm/trunk/data/opening_hours.js@ 6480

Last change on this file since 6480 was 6420, checked in by simon04, 11 years ago

fix #9367 - opening_hours - upgrade validator so that it will can fix missing minutes in automatic way

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