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

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

update opening_hours.js

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