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

Last change on this file since 8591 was 8223, checked in by simon04, 9 years ago

fix #11337 - Update opening_hours.js to v3.1.1

https://github.com/ypid/opening_hours.js/raw/v3.1.1/opening_hours.js

File size: 264.3 KB
Line 
1/*
2 * For information see https://github.com/ypid/opening_hours.js
3 * and the doc directory which contains internal documentation and design.
4 */
5/* jshint laxbreak: true */
6/* jshint boss: true */
7/* jshint loopfunc: true */
8
9(function (root, factory) {
10 /* constants (holidays, error correction) {{{ */
11 /* holidays {{{ */
12 /*
13 * The country code keys and the PH, SH keys are surrounded by '':
14 * :%s/^\s\+\zs"\([^"]\+\)"\(: {\)/'\1'\2/
15 * Fixed the indention with Vim Marco:
16 * /'PH'<cr>f{jVk%k,a:
17 * Fixed spacing in parenthesis:
18 * :%s/\[\zs\([^ ]\)/ \1/e | %s/\([^ ]\)\]/\1 \]/e | %s/,\([^ ]\)/, \1/e
19 */
20 var holidays = {
21 'fr': { // {{{
22 'PH': { // http://fr.wikipedia.org/wiki/F%C3%AAtes_et_jours_f%C3%A9ri%C3%A9s_en_France
23 "Jour de l'an" : [ 1, 1 ],
24 "Vendredi saint" : [ 'easter', -2, [ 'Moselle', 'Bas-Rhin', 'Haut-Rhin', 'Guadeloupe', 'Martinique', 'Polynésie française' ] ],
25 "Lundi de Pâques" : [ 'easter', 1 ],
26 "Saint-Pierre-Chanel" : [ 4, 28, [ 'Wallis-et-Futuna' ] ],
27 "Fête du Travail" : [ 5, 1 ],
28 "Fête de la Victoire" : [ 5, 8 ],
29 "Abolition de l'esclavage (Martinique)" : [ 5, 22, [ 'Martinique' ] ],
30 "Abolition de l'esclavage (Guadeloupe)" : [ 5, 27, [ 'Guadeloupe' ] ],
31 "Jeudi de l'Ascension" : [ 'easter', 39 ],
32 "Lundi de Pentecôte" : [ 'easter', 50 ],
33 "Abolition de l'esclavage (Guyane)" : [ 6, 10, [ 'Guyane' ] ],
34 "Fête de l'autonomie" : [ 6, 29, [ 'Polynésie française' ] ],
35 "Fête nationale" : [ 7, 14 ],
36 "Fête Victor Schoelcher" : [ 7, 21, [ 'Guadeloupe', 'Martinique' ] ],
37 "Fête du Territoire" : [ 7, 29, [ 'Wallis-et-Futuna' ] ],
38 "Assomption" : [ 8, 15 ],
39 "Fête de la citoyenneté" : [ 9, 24, [ 'Nouvelle-Calédonie' ] ],
40 "Toussaint" : [ 11, 1 ],
41 "Armistice" : [ 11, 11 ],
42 "Abolition de l'esclavage (Réunion)" : [ 12, 20, [ 'Réunion' ] ],
43 "Noël" : [ 12, 25 ],
44 "Saint-Étienne " : [ 12, 26, [ 'Moselle', 'Bas-Rhin', 'Haut-Rhin' ] ]
45 }
46 }, // }}}
47 'de': { // {{{
48 'PH': { // http://de.wikipedia.org/wiki/Feiertage_in_Deutschland
49 'Neujahrstag' : [ 1, 1 ], // month 1, day 1, whole Germany
50 'Heilige Drei Könige' : [ 1, 6, [ 'Baden-Württemberg', 'Bayern', 'Sachsen-Anhalt'] ], // only in the specified states
51 'Tag der Arbeit' : [ 5, 1 ], // whole Germany
52 'Karfreitag' : [ 'easter', -2 ], // two days before easter
53 'Ostersonntag' : [ 'easter', 0, [ 'Brandenburg'] ],
54 'Ostermontag' : [ 'easter', 1 ],
55 'Christi Himmelfahrt' : [ 'easter', 39 ],
56 'Pfingstsonntag' : [ 'easter', 49, [ 'Brandenburg'] ],
57 'Pfingstmontag' : [ 'easter', 50 ],
58 'Fronleichnam' : [ 'easter', 60, [ 'Baden-Württemberg', 'Bayern', 'Hessen', 'Nordrhein-Westfalen', 'Rheinland-Pfalz', 'Saarland' ] ],
59 'Mariä Himmelfahrt' : [ 8, 15, [ 'Saarland'] ],
60 'Tag der Deutschen Einheit' : [ 10, 3 ],
61 'Reformationstag' : [ 10, 31, [ 'Brandenburg', 'Mecklenburg-Vorpommern', 'Sachsen', 'Sachsen-Anhalt', 'Thüringen'] ],
62 'Allerheiligen' : [ 11, 1, [ 'Baden-Württemberg', 'Bayern', 'Nordrhein-Westfalen', 'Rheinland-Pfalz', 'Saarland' ] ],
63 '1. Weihnachtstag' : [ 12, 25 ],
64 '2. Weihnachtstag' : [ 12, 26 ],
65 // 'Silvester' : [ 12, 31 ], // for testing
66 },
67 'Baden-Württemberg': { // does only apply in Baden-Württemberg
68 // This more specific rule set overwrites the country wide one (they are just ignored).
69 // You may use this instead of the country wide with some
70 // additional holidays for some states, if one state
71 // totally disagrees about how to do holidays …
72 // 'PH': {
73 // '2. Weihnachtstag' : [ 12, 26 ],
74 // },
75
76 // school holiday normally variate between states
77 'SH': [ // generated by convert_ical_to_json
78 // You may can adjust this script to use other resources (for other countries) too.
79 {
80 name: 'Osterferien',
81 2005: [ 3, 24, /* to */ 3, 24, 3, 29, /* to */ 4, 2 ],
82 2006: [ 4, 18, /* to */ 4, 22 ],
83 2007: [ 4, 2, /* to */ 4, 14 ],
84 2008: [ 3, 17, /* to */ 3, 28 ],
85 2009: [ 4, 9, /* to */ 4, 9, 4, 14, /* to */ 4, 17 ],
86 2010: [ 4, 1, /* to */ 4, 1, 4, 6, /* to */ 4, 10 ],
87 2011: [ 4, 21, /* to */ 4, 21, 4, 26, /* to */ 4, 30 ],
88 2012: [ 4, 2, /* to */ 4, 13 ],
89 2013: [ 3, 25, /* to */ 4, 5 ],
90 2014: [ 4, 14, /* to */ 4, 25 ],
91 2015: [ 3, 30, /* to */ 4, 10 ],
92 2016: [ 3, 29, /* to */ 4, 2 ],
93 2017: [ 4, 10, /* to */ 4, 21 ],
94 },
95 {
96 name: 'Pfingstferien',
97 2005: [ 5, 17, /* to */ 5, 28 ],
98 2006: [ 5, 29, /* to */ 6, 10 ],
99 2007: [ 5, 29, /* to */ 6, 9 ],
100 2008: [ 5, 13, /* to */ 5, 23 ],
101 2009: [ 5, 25, /* to */ 6, 6 ],
102 2010: [ 5, 25, /* to */ 6, 5 ],
103 2011: [ 6, 14, /* to */ 6, 25 ],
104 2012: [ 5, 29, /* to */ 6, 9 ],
105 2013: [ 5, 21, /* to */ 6, 1 ],
106 2014: [ 6, 10, /* to */ 6, 21 ],
107 2015: [ 5, 26, /* to */ 6, 6 ],
108 2016: [ 5, 17, /* to */ 5, 28 ],
109 2017: [ 6, 6, /* to */ 6, 16 ],
110 },
111 {
112 name: 'Sommerferien',
113 2005: [ 7, 28, /* to */ 9, 10 ],
114 2006: [ 8, 3, /* to */ 9, 16 ],
115 2007: [ 7, 26, /* to */ 9, 8 ],
116 2008: [ 7, 24, /* to */ 9, 6 ],
117 2009: [ 7, 30, /* to */ 9, 12 ],
118 2010: [ 7, 29, /* to */ 9, 11 ],
119 2011: [ 7, 28, /* to */ 9, 10 ],
120 2012: [ 7, 26, /* to */ 9, 8 ],
121 2013: [ 7, 25, /* to */ 9, 7 ],
122 2014: [ 7, 31, /* to */ 9, 13 ],
123 2015: [ 7, 30, /* to */ 9, 12 ],
124 2016: [ 7, 28, /* to */ 9, 10 ],
125 2017: [ 7, 27, /* to */ 9, 9 ],
126 },
127 {
128 name: 'Herbstferien',
129 2005: [ 11, 2, /* to */ 11, 4 ],
130 2006: [ 10, 30, /* to */ 11, 3 ],
131 2007: [ 10, 29, /* to */ 11, 3 ],
132 2008: [ 10, 27, /* to */ 10, 31 ],
133 2009: [ 10, 26, /* to */ 10, 31 ],
134 2010: [ 11, 2, /* to */ 11, 6 ],
135 2011: [ 10, 31, /* to */ 10, 31, 11, 2, /* to */ 11, 4 ],
136 2012: [ 10, 29, /* to */ 11, 2 ],
137 2013: [ 10, 28, /* to */ 10, 30 ],
138 2014: [ 10, 27, /* to */ 10, 30 ],
139 2015: [ 11, 2, /* to */ 11, 6 ],
140 2016: [ 11, 2, /* to */ 11, 4 ],
141 },
142 {
143 name: 'Weihnachtsferien',
144 2005: [ 12, 22, /* to */ 1, 5 ],
145 2006: [ 12, 27, /* to */ 1, 5 ],
146 2007: [ 12, 24, /* to */ 1, 5 ],
147 2008: [ 12, 22, /* to */ 1, 10 ],
148 2009: [ 12, 23, /* to */ 1, 9 ],
149 2010: [ 12, 23, /* to */ 1, 8 ],
150 2011: [ 12, 23, /* to */ 1, 5 ],
151 2012: [ 12, 24, /* to */ 1, 5 ],
152 2013: [ 12, 23, /* to */ 1, 4 ],
153 2014: [ 12, 22, /* to */ 1, 5 ],
154 2015: [ 12, 23, /* to */ 1, 9 ],
155 2016: [ 12, 23, /* to */ 1, 7 ],
156 },
157 ],
158 },
159 'Mecklenburg-Vorpommern': {
160 'SH': [
161 {
162 name: 'Winterferien',
163 2010: [ 2, 6, /* to */ 2, 20 ],
164 2011: [ 2, 7, /* to */ 2, 19 ],
165 2012: [ 2, 6, /* to */ 2, 17 ],
166 2013: [ 2, 4, /* to */ 2, 15 ],
167 2014: [ 2, 3, /* to */ 2, 15 ],
168 2015: [ 2, 2, /* to */ 2, 14 ],
169 2016: [ 2, 1, /* to */ 2, 13 ],
170 2017: [ 2, 6, /* to */ 2, 18 ],
171 },
172 {
173 name: 'Osterferien',
174 2010: [ 3, 29, /* to */ 4, 7 ],
175 2011: [ 4, 16, /* to */ 4, 27 ],
176 2012: [ 4, 2, /* to */ 4, 11 ],
177 2013: [ 3, 25, /* to */ 4, 3 ],
178 2014: [ 4, 14, /* to */ 4, 23 ],
179 2015: [ 3, 30, /* to */ 4, 8 ],
180 2016: [ 3, 21, /* to */ 3, 30 ],
181 2017: [ 4, 10, /* to */ 4, 19 ],
182 },
183 {
184 name: 'Pfingstferien',
185 2010: [ 5, 21, /* to */ 5, 22 ],
186 2011: [ 6, 10, /* to */ 6, 14 ],
187 2012: [ 5, 25, /* to */ 5, 29 ],
188 2013: [ 5, 17, /* to */ 5, 21 ],
189 2014: [ 6, 6, /* to */ 6, 10 ],
190 2015: [ 5, 22, /* to */ 5, 26 ],
191 2016: [ 5, 14, /* to */ 5, 17 ],
192 2017: [ 6, 2, /* to */ 6, 6 ],
193 },
194 {
195 name: 'Sommerferien',
196 2010: [ 7, 12, /* to */ 8, 21 ],
197 2011: [ 7, 4, /* to */ 8, 13 ],
198 2012: [ 6, 23, /* to */ 8, 4 ],
199 2013: [ 6, 22, /* to */ 8, 3 ],
200 2014: [ 7, 14, /* to */ 8, 23 ],
201 2015: [ 7, 20, /* to */ 8, 29 ],
202 2016: [ 7, 25, /* to */ 9, 3 ],
203 2017: [ 7, 24, /* to */ 9, 2 ],
204 },
205 {
206 name: 'Herbstferien',
207 2010: [ 10, 18, /* to */ 10, 23 ],
208 2011: [ 10, 17, /* to */ 10, 21 ],
209 2012: [ 10, 1, /* to */ 10, 5 ],
210 2013: [ 10, 14, /* to */ 10, 19 ],
211 2014: [ 10, 20, /* to */ 10, 25 ],
212 2015: [ 10, 24, /* to */ 10, 30 ],
213 2016: [ 10, 24, /* to */ 10, 28 ],
214 },
215 {
216 name: 'Weihnachtsferien',
217 2010: [ 12, 23, /* to */ 12, 31 ],
218 2011: [ 12, 23, /* to */ 1, 3 ],
219 2012: [ 12, 21, /* to */ 1, 4 ],
220 2013: [ 12, 23, /* to */ 1, 3 ],
221 2014: [ 12, 22, /* to */ 1, 2 ],
222 2015: [ 12, 21, /* to */ 1, 2 ],
223 2016: [ 12, 22, /* to */ 1, 2 ],
224 },
225 ],
226 },
227 'Hessen': {
228 'SH': [
229 {
230 name: 'Osterferien',
231 2010: [ 3, 29, /* to */ 4, 10 ],
232 2011: [ 4, 18, /* to */ 4, 30 ],
233 2012: [ 4, 2, /* to */ 4, 14 ],
234 2013: [ 3, 25, /* to */ 4, 6 ],
235 2014: [ 4, 14, /* to */ 4, 26 ],
236 2015: [ 3, 30, /* to */ 4, 11 ],
237 2016: [ 3, 29, /* to */ 4, 9 ],
238 2017: [ 4, 3, /* to */ 4, 15 ],
239 2018: [ 3, 26, /* to */ 4, 7 ],
240 },
241 {
242 name: 'Sommerferien',
243 2010: [ 7, 5, /* to */ 8, 14 ],
244 2011: [ 6, 27, /* to */ 8, 5 ],
245 2012: [ 7, 2, /* to */ 8, 10 ],
246 2013: [ 7, 8, /* to */ 8, 16 ],
247 2014: [ 7, 28, /* to */ 9, 5 ],
248 2015: [ 7, 27, /* to */ 9, 5 ],
249 2016: [ 7, 18, /* to */ 8, 26 ],
250 2017: [ 7, 3, /* to */ 8, 11 ],
251 },
252 {
253 name: 'Herbstferien',
254 2010: [ 10, 11, /* to */ 10, 22 ],
255 2011: [ 10, 10, /* to */ 10, 22 ],
256 2012: [ 10, 15, /* to */ 10, 27 ],
257 2013: [ 10, 14, /* to */ 10, 26 ],
258 2014: [ 10, 20, /* to */ 11, 1 ],
259 2015: [ 10, 19, /* to */ 10, 31 ],
260 2016: [ 10, 17, /* to */ 10, 29 ],
261 2017: [ 10, 9, /* to */ 10, 21 ],
262 },
263 {
264 name: 'Weihnachtsferien',
265 2010: [ 12, 20, /* to */ 1, 7 ],
266 2011: [ 12, 21, /* to */ 1, 6 ],
267 2012: [ 12, 24, /* to */ 1, 12 ],
268 2013: [ 12, 23, /* to */ 1, 11 ],
269 2014: [ 12, 22, /* to */ 1, 10 ],
270 2015: [ 12, 23, /* to */ 1, 9 ],
271 2016: [ 12, 22, /* to */ 1, 7 ],
272 2017: [ 12, 24, /* to */ 1, 13 ],
273 },
274 ],
275 },
276 'Schleswig-Holstein': {
277 'SH': [
278 {
279 name: 'Osterferien',
280 2010: [ 4, 3, /* to */ 4, 17 ],
281 2011: [ 4, 15, /* to */ 4, 30 ],
282 2012: [ 3, 30, /* to */ 4, 13 ],
283 2013: [ 3, 25, /* to */ 4, 9 ],
284 2014: [ 4, 16, /* to */ 5, 2 ],
285 2015: [ 4, 1, /* to */ 4, 17 ],
286 2016: [ 3, 24, /* to */ 4, 9 ],
287 2017: [ 4, 7, /* to */ 4, 21 ],
288 },
289 {
290 name: 'Sommerferien',
291 2010: [ 7, 12, /* to */ 8, 21 ],
292 2011: [ 7, 4, /* to */ 8, 13 ],
293 2012: [ 6, 25, /* to */ 8, 4 ],
294 2013: [ 6, 24, /* to */ 8, 3 ],
295 2014: [ 7, 14, /* to */ 8, 23 ],
296 2015: [ 7, 20, /* to */ 8, 29 ],
297 2016: [ 7, 25, /* to */ 9, 3 ],
298 2017: [ 7, 24, /* to */ 9, 2 ],
299 },
300 {
301 name: 'Pfingstferien',
302 2011: [ 6, 3, /* to */ 6, 4 ],
303 2012: [ 5, 18, /* to */ 5, 18 ],
304 2013: [ 5, 10, /* to */ 5, 10 ],
305 2014: [ 5, 30, /* to */ 5, 30 ],
306 2015: [ 5, 15, /* to */ 5, 15 ],
307 2016: [ 5, 6, /* to */ 5, 6 ],
308 2017: [ 5, 26, /* to */ 5, 26 ],
309 },
310 {
311 name: 'Herbstferien',
312 2010: [ 10, 11, /* to */ 10, 23 ],
313 2011: [ 10, 10, /* to */ 10, 22 ],
314 2012: [ 10, 4, /* to */ 10, 19 ],
315 2013: [ 10, 4, /* to */ 10, 18 ],
316 2014: [ 10, 13, /* to */ 10, 25 ],
317 2015: [ 10, 19, /* to */ 10, 31 ],
318 2016: [ 10, 17, /* to */ 10, 29 ],
319 },
320 {
321 name: 'Weihnachtsferien',
322 2010: [ 12, 23, /* to */ 1, 7 ],
323 2011: [ 12, 23, /* to */ 1, 6 ],
324 2012: [ 12, 24, /* to */ 1, 5 ],
325 2013: [ 12, 23, /* to */ 1, 6 ],
326 2014: [ 12, 22, /* to */ 1, 6 ],
327 2015: [ 12, 21, /* to */ 1, 6 ],
328 2016: [ 12, 23, /* to */ 1, 6 ],
329 },
330 ],
331 },
332 'Berlin': {
333 'SH': [
334 {
335 name: 'Winterferien',
336 2010: [ 2, 1, /* to */ 2, 6 ],
337 2011: [ 1, 31, /* to */ 2, 5 ],
338 2012: [ 1, 30, /* to */ 2, 4 ],
339 2013: [ 2, 4, /* to */ 2, 9 ],
340 2014: [ 2, 3, /* to */ 2, 8 ],
341 2015: [ 2, 2, /* to */ 2, 7 ],
342 2016: [ 2, 1, /* to */ 2, 6 ],
343 2017: [ 1, 30, /* to */ 2, 4 ],
344 },
345 {
346 name: 'Osterferien',
347 2010: [ 3, 31, /* to */ 4, 10 ],
348 2011: [ 4, 18, /* to */ 4, 30 ],
349 2012: [ 4, 2, /* to */ 4, 14, 4, 30, /* to */ 4, 30 ],
350 2013: [ 3, 25, /* to */ 4, 6 ],
351 2014: [ 4, 14, /* to */ 4, 26, 5, 2, /* to */ 5, 2 ],
352 2015: [ 3, 30, /* to */ 4, 11 ],
353 2016: [ 3, 21, /* to */ 4, 2 ],
354 2017: [ 4, 10, /* to */ 4, 22 ],
355 },
356 {
357 name: 'Pfingstferien',
358 2010: [ 5, 14, /* to */ 5, 14, 5, 25, /* to */ 5, 25 ],
359 2011: [ 6, 3, /* to */ 6, 3 ],
360 2012: [ 5, 18, /* to */ 5, 18 ],
361 2013: [ 5, 10, /* to */ 5, 10, 5, 21, /* to */ 5, 21 ],
362 2014: [ 5, 30, /* to */ 5, 30 ],
363 2015: [ 5, 15, /* to */ 5, 15 ],
364 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 17 ],
365 2017: [ 5, 26, /* to */ 5, 26 ],
366 },
367 {
368 name: 'Sommerferien',
369 2010: [ 7, 7, /* to */ 8, 21 ],
370 2011: [ 6, 29, /* to */ 8, 12 ],
371 2012: [ 6, 20, /* to */ 8, 3 ],
372 2013: [ 6, 19, /* to */ 8, 2 ],
373 2014: [ 7, 9, /* to */ 8, 22 ],
374 2015: [ 7, 15, /* to */ 8, 28 ],
375 2016: [ 7, 20, /* to */ 9, 2 ],
376 2017: [ 7, 19, /* to */ 9, 1 ],
377 },
378 {
379 name: 'Herbstferien',
380 2010: [ 10, 11, /* to */ 10, 23 ],
381 2011: [ 10, 4, /* to */ 10, 14 ],
382 2012: [ 10, 1, /* to */ 10, 13 ],
383 2013: [ 9, 30, /* to */ 10, 12 ],
384 2014: [ 10, 20, /* to */ 11, 1 ],
385 2015: [ 10, 19, /* to */ 10, 31 ],
386 2016: [ 10, 17, /* to */ 10, 28 ],
387 },
388 {
389 name: 'Weihnachtsferien',
390 2010: [ 12, 23, /* to */ 1, 1 ],
391 2011: [ 12, 23, /* to */ 1, 3 ],
392 2012: [ 12, 24, /* to */ 1, 4 ],
393 2013: [ 12, 23, /* to */ 1, 3 ],
394 2014: [ 12, 22, /* to */ 1, 2 ],
395 2015: [ 12, 23, /* to */ 1, 2 ],
396 2016: [ 12, 23, /* to */ 1, 3 ],
397 },
398 ],
399 },
400 'Saarland': {
401 'SH': [
402 {
403 name: 'Winterferien',
404 2010: [ 2, 15, /* to */ 2, 20 ],
405 2011: [ 3, 7, /* to */ 3, 12 ],
406 2012: [ 2, 20, /* to */ 2, 25 ],
407 2013: [ 2, 11, /* to */ 2, 16 ],
408 2014: [ 3, 3, /* to */ 3, 8 ],
409 2015: [ 2, 16, /* to */ 2, 21 ],
410 },
411 {
412 name: 'Osterferien',
413 2010: [ 3, 29, /* to */ 4, 10 ],
414 2011: [ 4, 18, /* to */ 4, 30 ],
415 2012: [ 4, 2, /* to */ 4, 14 ],
416 2013: [ 3, 25, /* to */ 4, 6 ],
417 2014: [ 4, 14, /* to */ 4, 26 ],
418 2015: [ 3, 30, /* to */ 4, 11 ],
419 },
420 {
421 name: 'Sommerferien',
422 2010: [ 7, 5, /* to */ 8, 14 ],
423 2011: [ 6, 24, /* to */ 8, 6 ],
424 2012: [ 7, 2, /* to */ 8, 14 ],
425 2013: [ 7, 8, /* to */ 8, 17 ],
426 2014: [ 7, 28, /* to */ 9, 6 ],
427 2015: [ 7, 27, /* to */ 9, 4 ],
428 2016: [ 7, 18, /* to */ 8, 26 ],
429 2017: [ 7, 3, /* to */ 8, 14 ],
430 },
431 {
432 name: 'Herbstferien',
433 2010: [ 10, 11, /* to */ 10, 23 ],
434 2011: [ 10, 4, /* to */ 10, 15 ],
435 2012: [ 10, 22, /* to */ 11, 3 ],
436 2013: [ 10, 21, /* to */ 11, 2 ],
437 2014: [ 10, 20, /* to */ 10, 31 ],
438 },
439 {
440 name: 'Weihnachtsferien',
441 2010: [ 12, 20, /* to */ 1, 1 ],
442 2011: [ 12, 23, /* to */ 1, 4 ],
443 2012: [ 12, 24, /* to */ 1, 5 ],
444 2013: [ 12, 20, /* to */ 1, 4 ],
445 2014: [ 12, 22, /* to */ 1, 7 ],
446 },
447 ],
448 },
449 'Bremen': {
450 'SH': [
451 {
452 name: 'Winterferien',
453 2010: [ 2, 1, /* to */ 2, 2 ],
454 2011: [ 1, 31, /* to */ 2, 1 ],
455 2012: [ 1, 30, /* to */ 1, 31 ],
456 2013: [ 1, 31, /* to */ 2, 1 ],
457 2014: [ 1, 30, /* to */ 1, 31 ],
458 2015: [ 2, 2, /* to */ 2, 3 ],
459 2016: [ 1, 28, /* to */ 1, 29 ],
460 2017: [ 1, 30, /* to */ 1, 31 ],
461 },
462 {
463 name: 'Osterferien',
464 2010: [ 3, 19, /* to */ 4, 6 ],
465 2011: [ 4, 16, /* to */ 4, 30 ],
466 2012: [ 3, 26, /* to */ 4, 11, 4, 30, /* to */ 4, 30 ],
467 2013: [ 3, 16, /* to */ 4, 2 ],
468 2014: [ 4, 3, /* to */ 4, 22, 5, 2, /* to */ 5, 2 ],
469 2015: [ 3, 25, /* to */ 4, 10 ],
470 2016: [ 3, 18, /* to */ 4, 2 ],
471 2017: [ 4, 10, /* to */ 4, 22 ],
472 },
473 {
474 name: 'Pfingstferien',
475 2010: [ 5, 14, /* to */ 5, 14, 5, 25, /* to */ 5, 25 ],
476 2011: [ 6, 3, /* to */ 6, 3, 6, 14, /* to */ 6, 14 ],
477 2012: [ 5, 18, /* to */ 5, 18, 5, 29, /* to */ 5, 29 ],
478 2013: [ 5, 10, /* to */ 5, 10, 5, 21, /* to */ 5, 21 ],
479 2014: [ 5, 30, /* to */ 5, 30, 6, 10, /* to */ 6, 10 ],
480 2015: [ 5, 15, /* to */ 5, 15, 5, 26, /* to */ 5, 26 ],
481 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 17 ],
482 2017: [ 5, 26, /* to */ 5, 26, 6, 6, /* to */ 6, 6 ],
483 },
484 {
485 name: 'Sommerferien',
486 2010: [ 6, 24, /* to */ 8, 4 ],
487 2011: [ 7, 7, /* to */ 8, 17 ],
488 2012: [ 7, 23, /* to */ 8, 31 ],
489 2013: [ 6, 27, /* to */ 8, 7 ],
490 2014: [ 7, 31, /* to */ 9, 10 ],
491 2015: [ 7, 23, /* to */ 9, 2 ],
492 2016: [ 6, 23, /* to */ 8, 3 ],
493 2017: [ 6, 22, /* to */ 8, 2 ],
494 },
495 {
496 name: 'Herbstferien',
497 2010: [ 10, 9, /* to */ 10, 23 ],
498 2011: [ 10, 17, /* to */ 10, 29 ],
499 2012: [ 10, 22, /* to */ 11, 3 ],
500 2013: [ 10, 4, /* to */ 10, 18 ],
501 2014: [ 10, 27, /* to */ 11, 8 ],
502 2015: [ 10, 19, /* to */ 10, 31 ],
503 2016: [ 10, 4, /* to */ 10, 15 ],
504 },
505 {
506 name: 'Weihnachtsferien',
507 2010: [ 12, 22, /* to */ 1, 5 ],
508 2011: [ 12, 23, /* to */ 1, 4 ],
509 2012: [ 12, 24, /* to */ 1, 5 ],
510 2013: [ 12, 23, /* to */ 1, 3 ],
511 2014: [ 12, 22, /* to */ 1, 5 ],
512 2015: [ 12, 23, /* to */ 1, 6 ],
513 2016: [ 12, 21, /* to */ 1, 6 ],
514 },
515 ],
516 },
517 'Bayern': {
518 'SH': [
519 {
520 name: 'Winterferien',
521 2010: [ 2, 15, /* to */ 2, 20 ],
522 2011: [ 3, 7, /* to */ 3, 11 ],
523 2012: [ 2, 20, /* to */ 2, 24 ],
524 2013: [ 2, 11, /* to */ 2, 15 ],
525 2014: [ 3, 3, /* to */ 3, 7 ],
526 2015: [ 2, 16, /* to */ 2, 20 ],
527 2016: [ 2, 8, /* to */ 2, 12 ],
528 2017: [ 2, 27, /* to */ 3, 3 ],
529 },
530 {
531 name: 'Osterferien',
532 2010: [ 3, 29, /* to */ 4, 10 ],
533 2011: [ 4, 18, /* to */ 4, 30 ],
534 2012: [ 4, 2, /* to */ 4, 14 ],
535 2013: [ 3, 25, /* to */ 4, 6 ],
536 2014: [ 4, 14, /* to */ 4, 26 ],
537 2015: [ 3, 30, /* to */ 4, 11 ],
538 2016: [ 3, 21, /* to */ 4, 1 ],
539 2017: [ 4, 10, /* to */ 4, 22 ],
540 },
541 {
542 name: 'Pfingstferien',
543 2010: [ 5, 25, /* to */ 6, 5 ],
544 2011: [ 6, 14, /* to */ 6, 25 ],
545 2012: [ 5, 29, /* to */ 6, 9 ],
546 2013: [ 5, 21, /* to */ 5, 31 ],
547 2014: [ 6, 10, /* to */ 6, 21 ],
548 2015: [ 5, 26, /* to */ 6, 5 ],
549 2016: [ 5, 17, /* to */ 5, 28 ],
550 2017: [ 6, 6, /* to */ 6, 16 ],
551 },
552 {
553 name: 'Sommerferien',
554 2010: [ 8, 2, /* to */ 9, 13 ],
555 2011: [ 7, 30, /* to */ 9, 12 ],
556 2012: [ 8, 1, /* to */ 9, 12 ],
557 2013: [ 7, 31, /* to */ 9, 11 ],
558 2014: [ 7, 30, /* to */ 9, 15 ],
559 2015: [ 8, 1, /* to */ 9, 14 ],
560 2016: [ 7, 30, /* to */ 9, 12 ],
561 2017: [ 7, 29, /* to */ 9, 11 ],
562 },
563 {
564 name: 'Herbstferien',
565 2010: [ 11, 2, /* to */ 11, 5 ],
566 2011: [ 10, 31, /* to */ 11, 5 ],
567 2012: [ 10, 29, /* to */ 11, 3 ],
568 2013: [ 10, 28, /* to */ 10, 31 ],
569 2014: [ 10, 27, /* to */ 10, 31 ],
570 2015: [ 11, 2, /* to */ 11, 7 ],
571 2016: [ 10, 31, /* to */ 11, 4 ],
572 },
573 {
574 name: 'Weihnachtsferien',
575 2010: [ 12, 24, /* to */ 1, 7 ],
576 2011: [ 12, 27, /* to */ 1, 5 ],
577 2012: [ 12, 24, /* to */ 1, 5 ],
578 2013: [ 12, 23, /* to */ 1, 4 ],
579 2014: [ 12, 24, /* to */ 1, 5 ],
580 2015: [ 12, 24, /* to */ 1, 5 ],
581 2016: [ 12, 24, /* to */ 1, 5 ],
582 },
583 ],
584 },
585 'Niedersachsen': {
586 'SH': [
587 {
588 name: 'Winterferien',
589 2010: [ 2, 1, /* to */ 2, 2 ],
590 2011: [ 1, 31, /* to */ 2, 1 ],
591 2012: [ 1, 30, /* to */ 1, 31 ],
592 2013: [ 1, 31, /* to */ 2, 1 ],
593 2014: [ 1, 30, /* to */ 1, 31 ],
594 2015: [ 2, 2, /* to */ 2, 3 ],
595 2016: [ 1, 28, /* to */ 1, 29 ],
596 2017: [ 1, 30, /* to */ 1, 31 ],
597 },
598 {
599 name: 'Osterferien',
600 2010: [ 3, 19, /* to */ 4, 6 ],
601 2011: [ 4, 16, /* to */ 4, 30 ],
602 2012: [ 3, 26, /* to */ 4, 11, 4, 30, /* to */ 4, 30 ],
603 2013: [ 3, 16, /* to */ 4, 2 ],
604 2014: [ 4, 3, /* to */ 4, 22, 5, 2, /* to */ 5, 2 ],
605 2015: [ 3, 25, /* to */ 4, 10 ],
606 2016: [ 3, 18, /* to */ 4, 2 ],
607 2017: [ 4, 10, /* to */ 4, 22 ],
608 },
609 {
610 name: 'Pfingstferien',
611 2010: [ 5, 14, /* to */ 5, 14, 5, 25, /* to */ 5, 25 ],
612 2011: [ 6, 3, /* to */ 6, 3, 6, 14, /* to */ 6, 14 ],
613 2012: [ 5, 18, /* to */ 5, 18, 5, 29, /* to */ 5, 29 ],
614 2013: [ 5, 10, /* to */ 5, 10, 5, 21, /* to */ 5, 21 ],
615 2014: [ 5, 30, /* to */ 5, 30, 6, 10, /* to */ 6, 10 ],
616 2015: [ 5, 15, /* to */ 5, 15, 5, 26, /* to */ 5, 26 ],
617 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 17 ],
618 2017: [ 5, 26, /* to */ 5, 26, 6, 6, /* to */ 6, 6 ],
619 },
620 {
621 name: 'Sommerferien',
622 2010: [ 6, 24, /* to */ 8, 4 ],
623 2011: [ 7, 7, /* to */ 8, 17 ],
624 2012: [ 7, 23, /* to */ 8, 31 ],
625 2013: [ 6, 27, /* to */ 8, 7 ],
626 2014: [ 7, 31, /* to */ 9, 10 ],
627 2015: [ 7, 23, /* to */ 9, 2 ],
628 2016: [ 6, 23, /* to */ 8, 3 ],
629 2017: [ 6, 22, /* to */ 8, 2 ],
630 },
631 {
632 name: 'Herbstferien',
633 2010: [ 10, 9, /* to */ 10, 23 ],
634 2011: [ 10, 17, /* to */ 10, 29 ],
635 2012: [ 10, 22, /* to */ 11, 3 ],
636 2013: [ 10, 4, /* to */ 10, 18 ],
637 2014: [ 10, 27, /* to */ 11, 8 ],
638 2015: [ 10, 19, /* to */ 10, 31 ],
639 2016: [ 10, 4, /* to */ 10, 15 ],
640 },
641 {
642 name: 'Weihnachtsferien',
643 2010: [ 12, 22, /* to */ 1, 5 ],
644 2011: [ 12, 23, /* to */ 1, 4 ],
645 2012: [ 12, 24, /* to */ 1, 5 ],
646 2013: [ 12, 23, /* to */ 1, 3 ],
647 2014: [ 12, 22, /* to */ 1, 5 ],
648 2015: [ 12, 23, /* to */ 1, 6 ],
649 2016: [ 12, 21, /* to */ 1, 6 ],
650 },
651 ],
652 },
653 'Nordrhein-Westfalen': {
654 'SH': [
655 {
656 name: 'Osterferien',
657 2010: [ 3, 27, /* to */ 4, 10 ],
658 2011: [ 4, 18, /* to */ 4, 30 ],
659 2012: [ 4, 2, /* to */ 4, 14 ],
660 2013: [ 3, 25, /* to */ 4, 6 ],
661 2014: [ 4, 14, /* to */ 4, 26 ],
662 2015: [ 3, 30, /* to */ 4, 11 ],
663 2016: [ 3, 21, /* to */ 4, 2 ],
664 2017: [ 4, 10, /* to */ 4, 22 ],
665 },
666 {
667 name: 'Pfingstferien',
668 2010: [ 5, 25, /* to */ 5, 25 ],
669 2012: [ 5, 29, /* to */ 5, 29 ],
670 2013: [ 5, 21, /* to */ 5, 21 ],
671 2014: [ 6, 10, /* to */ 6, 10 ],
672 2015: [ 5, 26, /* to */ 5, 26 ],
673 2016: [ 5, 17, /* to */ 5, 17 ],
674 2017: [ 6, 6, /* to */ 6, 6 ],
675 },
676 {
677 name: 'Sommerferien',
678 2010: [ 7, 15, /* to */ 8, 27 ],
679 2011: [ 7, 25, /* to */ 9, 6 ],
680 2012: [ 7, 9, /* to */ 8, 21 ],
681 2013: [ 7, 22, /* to */ 9, 3 ],
682 2014: [ 7, 7, /* to */ 8, 19 ],
683 2015: [ 6, 29, /* to */ 8, 11 ],
684 2016: [ 7, 11, /* to */ 8, 23 ],
685 2017: [ 7, 17, /* to */ 8, 29 ],
686 },
687 {
688 name: 'Herbstferien',
689 2010: [ 10, 11, /* to */ 10, 23 ],
690 2011: [ 10, 24, /* to */ 11, 5 ],
691 2012: [ 10, 8, /* to */ 10, 20 ],
692 2013: [ 10, 21, /* to */ 11, 2 ],
693 2014: [ 10, 6, /* to */ 10, 18 ],
694 2015: [ 10, 5, /* to */ 10, 17 ],
695 2016: [ 10, 10, /* to */ 10, 21 ],
696 },
697 {
698 name: 'Weihnachtsferien',
699 2010: [ 12, 24, /* to */ 1, 8 ],
700 2011: [ 12, 23, /* to */ 1, 6 ],
701 2012: [ 12, 21, /* to */ 1, 4 ],
702 2013: [ 12, 23, /* to */ 1, 7 ],
703 2014: [ 12, 22, /* to */ 1, 6 ],
704 2015: [ 12, 23, /* to */ 1, 6 ],
705 2016: [ 12, 23, /* to */ 1, 6 ],
706 },
707 ],
708 },
709 'Sachsen': {
710 'SH': [
711 {
712 name: 'Winterferien',
713 2010: [ 2, 8, /* to */ 2, 20 ],
714 2011: [ 2, 12, /* to */ 2, 26 ],
715 2012: [ 2, 13, /* to */ 2, 25 ],
716 2013: [ 2, 4, /* to */ 2, 15 ],
717 2014: [ 2, 17, /* to */ 3, 1 ],
718 2015: [ 2, 9, /* to */ 2, 21 ],
719 2016: [ 2, 8, /* to */ 2, 20 ],
720 2017: [ 2, 13, /* to */ 2, 24 ],
721 },
722 {
723 name: 'Osterferien',
724 2010: [ 4, 1, /* to */ 4, 10 ],
725 2011: [ 4, 22, /* to */ 4, 30 ],
726 2012: [ 4, 6, /* to */ 4, 14 ],
727 2013: [ 3, 29, /* to */ 4, 6 ],
728 2014: [ 4, 18, /* to */ 4, 26 ],
729 2015: [ 4, 2, /* to */ 4, 11 ],
730 2016: [ 3, 25, /* to */ 4, 2 ],
731 2017: [ 4, 13, /* to */ 4, 22 ],
732 },
733 {
734 name: 'Pfingstferien',
735 2010: [ 5, 14, /* to */ 5, 14 ],
736 2011: [ 6, 3, /* to */ 6, 3 ],
737 2012: [ 5, 18, /* to */ 5, 18 ],
738 2013: [ 5, 10, /* to */ 5, 10, 5, 18, /* to */ 5, 22 ],
739 2014: [ 5, 30, /* to */ 5, 30 ],
740 2015: [ 5, 15, /* to */ 5, 15 ],
741 2016: [ 5, 6, /* to */ 5, 6 ],
742 2017: [ 5, 26, /* to */ 5, 26 ],
743 },
744 {
745 name: 'Sommerferien',
746 2010: [ 6, 28, /* to */ 8, 6 ],
747 2011: [ 7, 11, /* to */ 8, 19 ],
748 2012: [ 7, 23, /* to */ 8, 31 ],
749 2013: [ 7, 15, /* to */ 8, 23 ],
750 2014: [ 7, 21, /* to */ 8, 29 ],
751 2015: [ 7, 13, /* to */ 8, 21 ],
752 2016: [ 6, 27, /* to */ 8, 5 ],
753 2017: [ 6, 26, /* to */ 8, 4 ],
754 },
755 {
756 name: 'Herbstferien',
757 2010: [ 10, 4, /* to */ 10, 16 ],
758 2011: [ 10, 17, /* to */ 10, 28 ],
759 2012: [ 10, 22, /* to */ 11, 2 ],
760 2013: [ 10, 21, /* to */ 11, 1 ],
761 2014: [ 10, 20, /* to */ 10, 31 ],
762 2015: [ 10, 12, /* to */ 10, 24 ],
763 2016: [ 10, 3, /* to */ 10, 15 ],
764 },
765 {
766 name: 'Weihnachtsferien',
767 2010: [ 12, 23, /* to */ 1, 1 ],
768 2011: [ 12, 23, /* to */ 1, 2 ],
769 2012: [ 12, 22, /* to */ 1, 2 ],
770 2013: [ 12, 21, /* to */ 1, 3 ],
771 2014: [ 12, 22, /* to */ 1, 3 ],
772 2015: [ 12, 21, /* to */ 1, 2 ],
773 2016: [ 12, 23, /* to */ 1, 2 ],
774 },
775 ],
776 },
777 'Thüringen': {
778 'SH': [
779 {
780 name: 'Winterferien',
781 2010: [ 2, 1, /* to */ 2, 6 ],
782 2011: [ 1, 31, /* to */ 2, 5 ],
783 2012: [ 2, 6, /* to */ 2, 11 ],
784 2013: [ 2, 18, /* to */ 2, 23 ],
785 2014: [ 2, 17, /* to */ 2, 22 ],
786 2015: [ 2, 2, /* to */ 2, 7 ],
787 2016: [ 2, 1, /* to */ 2, 6 ],
788 2017: [ 2, 6, /* to */ 2, 11 ],
789 },
790 {
791 name: 'Osterferien',
792 2010: [ 3, 29, /* to */ 4, 9 ],
793 2011: [ 4, 18, /* to */ 4, 30 ],
794 2012: [ 4, 2, /* to */ 4, 13 ],
795 2013: [ 3, 25, /* to */ 4, 6 ],
796 2014: [ 4, 19, /* to */ 5, 2 ],
797 2015: [ 3, 30, /* to */ 4, 11 ],
798 2016: [ 3, 24, /* to */ 4, 2 ],
799 2017: [ 4, 10, /* to */ 4, 21 ],
800 },
801 {
802 name: 'Sommerferien',
803 2010: [ 6, 24, /* to */ 8, 4 ],
804 2011: [ 7, 11, /* to */ 8, 19 ],
805 2012: [ 7, 23, /* to */ 8, 31 ],
806 2013: [ 7, 15, /* to */ 8, 23 ],
807 2014: [ 7, 21, /* to */ 8, 29 ],
808 2015: [ 7, 13, /* to */ 8, 21 ],
809 2016: [ 6, 27, /* to */ 8, 10 ],
810 2017: [ 6, 26, /* to */ 8, 9 ],
811 },
812 {
813 name: 'Pfingstferien',
814 2011: [ 6, 11, /* to */ 6, 14 ],
815 2012: [ 5, 25, /* to */ 5, 29 ],
816 2013: [ 5, 10, /* to */ 5, 10 ],
817 2014: [ 5, 30, /* to */ 5, 30 ],
818 2015: [ 5, 15, /* to */ 5, 15 ],
819 2016: [ 5, 6, /* to */ 5, 6 ],
820 2017: [ 5, 26, /* to */ 5, 26 ],
821 },
822 {
823 name: 'Herbstferien',
824 2010: [ 10, 9, /* to */ 10, 23 ],
825 2011: [ 10, 17, /* to */ 10, 28 ],
826 2012: [ 10, 22, /* to */ 11, 3 ],
827 2013: [ 10, 21, /* to */ 11, 2 ],
828 2014: [ 10, 6, /* to */ 10, 18 ],
829 2015: [ 10, 5, /* to */ 10, 17 ],
830 2016: [ 10, 10, /* to */ 10, 22 ],
831 },
832 {
833 name: 'Weihnachtsferien',
834 2010: [ 12, 23, /* to */ 1, 1 ],
835 2011: [ 12, 23, /* to */ 1, 1 ],
836 2012: [ 12, 24, /* to */ 1, 5 ],
837 2013: [ 12, 23, /* to */ 1, 4 ],
838 2014: [ 12, 22, /* to */ 1, 3 ],
839 2015: [ 12, 23, /* to */ 1, 2 ],
840 2016: [ 12, 23, /* to */ 12, 31 ],
841 },
842 ],
843 },
844 'Hamburg': {
845 'SH': [
846 {
847 name: 'Winterferien',
848 2010: [ 1, 29, /* to */ 1, 29 ],
849 2011: [ 1, 31, /* to */ 1, 31 ],
850 2012: [ 1, 30, /* to */ 1, 30 ],
851 2013: [ 2, 1, /* to */ 2, 1 ],
852 2014: [ 1, 31, /* to */ 1, 31 ],
853 2015: [ 1, 30, /* to */ 1, 30 ],
854 2016: [ 1, 29, /* to */ 1, 29 ],
855 2017: [ 1, 30, /* to */ 1, 30 ],
856 },
857 {
858 name: 'Osterferien',
859 2010: [ 3, 8, /* to */ 3, 20 ],
860 2011: [ 3, 7, /* to */ 3, 18 ],
861 2012: [ 3, 5, /* to */ 3, 16 ],
862 2013: [ 3, 4, /* to */ 3, 15 ],
863 2014: [ 3, 3, /* to */ 3, 14 ],
864 2015: [ 3, 2, /* to */ 3, 13 ],
865 2016: [ 3, 7, /* to */ 3, 18 ],
866 2017: [ 3, 6, /* to */ 3, 17 ],
867 },
868 {
869 name: 'Pfingstferien',
870 2010: [ 5, 14, /* to */ 5, 22 ],
871 2011: [ 4, 26, /* to */ 4, 29, 6, 3, /* to */ 6, 3 ],
872 2012: [ 4, 30, /* to */ 5, 4, 5, 18, /* to */ 5, 18 ],
873 2013: [ 5, 2, /* to */ 5, 10 ],
874 2014: [ 4, 28, /* to */ 5, 2, 5, 30, /* to */ 5, 30 ],
875 2015: [ 5, 11, /* to */ 5, 15 ],
876 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 20 ],
877 2017: [ 5, 22, /* to */ 5, 26 ],
878 },
879 {
880 name: 'Sommerferien',
881 2010: [ 7, 8, /* to */ 8, 18 ],
882 2011: [ 6, 30, /* to */ 8, 10 ],
883 2012: [ 6, 21, /* to */ 8, 1 ],
884 2013: [ 6, 20, /* to */ 7, 31 ],
885 2014: [ 7, 10, /* to */ 8, 20 ],
886 2015: [ 7, 16, /* to */ 8, 26 ],
887 2016: [ 7, 21, /* to */ 8, 31 ],
888 2017: [ 7, 20, /* to */ 8, 30 ],
889 },
890 {
891 name: 'Herbstferien',
892 2010: [ 10, 4, /* to */ 10, 15 ],
893 2011: [ 10, 4, /* to */ 10, 14 ],
894 2012: [ 10, 1, /* to */ 10, 12 ],
895 2013: [ 9, 30, /* to */ 10, 11 ],
896 2014: [ 10, 13, /* to */ 10, 24 ],
897 2015: [ 10, 19, /* to */ 10, 30 ],
898 2016: [ 10, 17, /* to */ 10, 28 ],
899 },
900 {
901 name: 'Weihnachtsferien',
902 2010: [ 12, 23, /* to */ 1, 3 ],
903 2011: [ 12, 27, /* to */ 1, 6 ],
904 2012: [ 12, 21, /* to */ 1, 4 ],
905 2013: [ 12, 19, /* to */ 1, 3 ],
906 2014: [ 12, 22, /* to */ 1, 6 ],
907 2015: [ 12, 21, /* to */ 1, 1 ],
908 2016: [ 12, 27, /* to */ 1, 6 ],
909 },
910 ],
911 },
912 'Sachsen-Anhalt': {
913 'SH': [
914 {
915 name: 'Winterferien',
916 2010: [ 2, 8, /* to */ 2, 13 ],
917 2011: [ 2, 5, /* to */ 2, 12 ],
918 2012: [ 2, 4, /* to */ 2, 11 ],
919 2013: [ 2, 1, /* to */ 2, 8 ],
920 2014: [ 2, 1, /* to */ 2, 12 ],
921 2015: [ 2, 2, /* to */ 2, 14 ],
922 2016: [ 2, 1, /* to */ 2, 10 ],
923 2017: [ 2, 4, /* to */ 2, 11 ],
924 },
925 {
926 name: 'Osterferien',
927 2010: [ 3, 29, /* to */ 4, 9 ],
928 2011: [ 4, 18, /* to */ 4, 27 ],
929 2012: [ 4, 2, /* to */ 4, 7 ],
930 2013: [ 3, 25, /* to */ 3, 30 ],
931 2014: [ 4, 14, /* to */ 4, 17 ],
932 2015: [ 4, 2, /* to */ 4, 2 ],
933 2016: [ 3, 24, /* to */ 3, 24 ],
934 2017: [ 4, 10, /* to */ 4, 13 ],
935 },
936 {
937 name: 'Pfingstferien',
938 2010: [ 5, 14, /* to */ 5, 22 ],
939 2011: [ 6, 14, /* to */ 6, 18 ],
940 2012: [ 5, 18, /* to */ 5, 25 ],
941 2013: [ 5, 10, /* to */ 5, 18 ],
942 2014: [ 5, 30, /* to */ 6, 7 ],
943 2015: [ 5, 15, /* to */ 5, 23 ],
944 2016: [ 5, 6, /* to */ 5, 14 ],
945 2017: [ 5, 26, /* to */ 5, 26 ],
946 },
947 {
948 name: 'Sommerferien',
949 2010: [ 6, 24, /* to */ 8, 4 ],
950 2011: [ 7, 11, /* to */ 8, 24 ],
951 2012: [ 7, 23, /* to */ 9, 5 ],
952 2013: [ 7, 15, /* to */ 8, 28 ],
953 2014: [ 7, 21, /* to */ 9, 3 ],
954 2015: [ 7, 13, /* to */ 8, 26 ],
955 2016: [ 6, 27, /* to */ 8, 10 ],
956 2017: [ 6, 26, /* to */ 8, 9 ],
957 },
958 {
959 name: 'Herbstferien',
960 2010: [ 10, 18, /* to */ 10, 23 ],
961 2011: [ 10, 17, /* to */ 10, 22 ],
962 2012: [ 10, 29, /* to */ 11, 2 ],
963 2013: [ 10, 21, /* to */ 10, 25 ],
964 2014: [ 10, 27, /* to */ 10, 30 ],
965 2015: [ 10, 17, /* to */ 10, 24 ],
966 2016: [ 10, 4, /* to */ 10, 15 ],
967 },
968 {
969 name: 'Weihnachtsferien',
970 2010: [ 12, 22, /* to */ 1, 5 ],
971 2011: [ 12, 22, /* to */ 1, 7 ],
972 2012: [ 12, 19, /* to */ 1, 4 ],
973 2013: [ 12, 21, /* to */ 1, 3 ],
974 2014: [ 12, 22, /* to */ 1, 5 ],
975 2015: [ 12, 21, /* to */ 1, 5 ],
976 2016: [ 12, 19, /* to */ 1, 2 ],
977 },
978 ],
979 },
980 'Rheinland-Pfalz': {
981 'SH': [
982 {
983 name: 'Osterferien',
984 2010: [ 3, 26, /* to */ 4, 9 ],
985 2011: [ 4, 18, /* to */ 4, 29 ],
986 2012: [ 3, 29, /* to */ 4, 13 ],
987 2013: [ 3, 20, /* to */ 4, 5 ],
988 2014: [ 4, 11, /* to */ 4, 25 ],
989 2015: [ 3, 26, /* to */ 4, 10 ],
990 2016: [ 3, 18, /* to */ 4, 1 ],
991 2017: [ 4, 10, /* to */ 4, 21 ],
992 },
993 {
994 name: 'Sommerferien',
995 2010: [ 7, 5, /* to */ 8, 13 ],
996 2011: [ 6, 27, /* to */ 8, 5 ],
997 2012: [ 7, 2, /* to */ 8, 10 ],
998 2013: [ 7, 8, /* to */ 8, 16 ],
999 2014: [ 7, 28, /* to */ 9, 5 ],
1000 2015: [ 7, 27, /* to */ 9, 4 ],
1001 2016: [ 7, 18, /* to */ 8, 26 ],
1002 2017: [ 7, 3, /* to */ 8, 11 ],
1003 },
1004 {
1005 name: 'Herbstferien',
1006 2010: [ 10, 11, /* to */ 10, 22 ],
1007 2011: [ 10, 4, /* to */ 10, 14 ],
1008 2012: [ 10, 1, /* to */ 10, 12 ],
1009 2013: [ 10, 4, /* to */ 10, 18 ],
1010 2014: [ 10, 20, /* to */ 10, 31 ],
1011 2015: [ 10, 19, /* to */ 10, 30 ],
1012 2016: [ 10, 10, /* to */ 10, 21 ],
1013 },
1014 {
1015 name: 'Weihnachtsferien',
1016 2010: [ 12, 23, /* to */ 1, 7 ],
1017 2011: [ 12, 22, /* to */ 1, 6 ],
1018 2012: [ 12, 20, /* to */ 1, 4 ],
1019 2013: [ 12, 23, /* to */ 1, 7 ],
1020 2014: [ 12, 22, /* to */ 1, 7 ],
1021 2015: [ 12, 23, /* to */ 1, 8 ],
1022 2016: [ 12, 22, /* to */ 1, 6 ],
1023 },
1024 ],
1025 },
1026 'Brandenburg': {
1027 'SH': [
1028 {
1029 name: 'Winterferien',
1030 2010: [ 2, 1, /* to */ 2, 6 ],
1031 2011: [ 1, 31, /* to */ 2, 5 ],
1032 2012: [ 1, 30, /* to */ 2, 4 ],
1033 2013: [ 2, 4, /* to */ 2, 9 ],
1034 2014: [ 2, 3, /* to */ 2, 8 ],
1035 2015: [ 2, 2, /* to */ 2, 7 ],
1036 2016: [ 2, 1, /* to */ 2, 6 ],
1037 2017: [ 1, 30, /* to */ 2, 4 ],
1038 },
1039 {
1040 name: 'Osterferien',
1041 2010: [ 3, 31, /* to */ 4, 10 ],
1042 2011: [ 4, 20, /* to */ 4, 30 ],
1043 2012: [ 4, 4, /* to */ 4, 14, 4, 30, /* to */ 4, 30 ],
1044 2013: [ 3, 27, /* to */ 4, 6 ],
1045 2014: [ 4, 16, /* to */ 4, 26, 5, 2, /* to */ 5, 2 ],
1046 2015: [ 4, 1, /* to */ 4, 11 ],
1047 2016: [ 3, 23, /* to */ 4, 2 ],
1048 2017: [ 4, 12, /* to */ 4, 22 ],
1049 },
1050 {
1051 name: 'Pfingstferien',
1052 2010: [ 5, 14, /* to */ 5, 14 ],
1053 2011: [ 6, 3, /* to */ 6, 3 ],
1054 2012: [ 5, 18, /* to */ 5, 18 ],
1055 2013: [ 5, 10, /* to */ 5, 10 ],
1056 2014: [ 5, 30, /* to */ 5, 30 ],
1057 2015: [ 5, 15, /* to */ 5, 15 ],
1058 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 17 ],
1059 2017: [ 5, 26, /* to */ 5, 26 ],
1060 },
1061 {
1062 name: 'Sommerferien',
1063 2010: [ 7, 8, /* to */ 8, 21 ],
1064 2011: [ 6, 30, /* to */ 8, 13 ],
1065 2012: [ 6, 21, /* to */ 8, 3 ],
1066 2013: [ 6, 20, /* to */ 8, 2 ],
1067 2014: [ 7, 10, /* to */ 8, 22 ],
1068 2015: [ 7, 16, /* to */ 8, 28 ],
1069 2016: [ 7, 21, /* to */ 9, 3 ],
1070 2017: [ 7, 20, /* to */ 9, 1 ],
1071 },
1072 {
1073 name: 'Herbstferien',
1074 2010: [ 10, 11, /* to */ 10, 23 ],
1075 2011: [ 10, 4, /* to */ 10, 14 ],
1076 2012: [ 10, 1, /* to */ 10, 13 ],
1077 2013: [ 9, 30, /* to */ 10, 12, 11, 1, /* to */ 11, 1 ],
1078 2014: [ 10, 20, /* to */ 11, 1 ],
1079 2015: [ 10, 19, /* to */ 10, 30 ],
1080 2016: [ 10, 17, /* to */ 10, 28 ],
1081 },
1082 {
1083 name: 'Weihnachtsferien',
1084 2010: [ 12, 23, /* to */ 1, 1 ],
1085 2011: [ 12, 23, /* to */ 1, 3 ],
1086 2012: [ 12, 24, /* to */ 1, 4 ],
1087 2013: [ 12, 23, /* to */ 1, 3 ],
1088 2014: [ 12, 22, /* to */ 1, 2 ],
1089 2015: [ 12, 23, /* to */ 1, 2 ],
1090 2016: [ 12, 23, /* to */ 1, 3 ],
1091 },
1092 ],
1093 },
1094 }, // }}}
1095 'at': { // {{{
1096 'PH': { // http://de.wikipedia.org/wiki/Feiertage_in_%C3%96sterreich
1097 'Neujahrstag' : [ 1, 1 ],
1098 'Heilige Drei Könige' : [ 1, 6 ],
1099 // 'Josef' : [ 3, 19, [ 'Kärnten', 'Steiermark', 'Tirol', 'Vorarlberg' ] ],
1100 // 'Karfreitag' : [ 'easter', -2 ],
1101 'Ostermontag' : [ 'easter', 1 ],
1102 'Staatsfeiertag' : [ 5, 1 ],
1103 // 'Florian' : [ 5, 4, [ 'Oberösterreich' ] ],
1104 'Christi Himmelfahrt' : [ 'easter', 39 ],
1105 'Pfingstmontag' : [ 'easter', 50 ],
1106 'Fronleichnam' : [ 'easter', 60 ],
1107 'Mariä Himmelfahrt' : [ 8, 15 ],
1108 // 'Rupert' : [ 9, 24, [ 'Salzburg' ] ],
1109 // 'Tag der Volksabstimmung' : [ 10, 10, [ 'Kärnten' ] ],
1110 'Nationalfeiertag' : [ 10, 26 ],
1111 'Allerheiligen' : [ 11, 1 ],
1112 // 'Martin' : [ 11, 11, [ 'Burgenland' ] ],
1113 // 'Leopold' : [ 11, 15, [ 'Niederösterreich', 'Wien' ] ],
1114 'Mariä Empfängnis' : [ 12, 8 ],
1115 // 'Heiliger Abend' : [ 12, 24 ],
1116 'Christtag' : [ 12, 25 ],
1117 'Stefanitag' : [ 12, 26 ],
1118 // 'Silvester' : [ 12, 31 ],
1119 },
1120 }, // }}}
1121 'ca': { // {{{
1122 'PH': { // https://en.wikipedia.org/wiki/Public_holidays_in_Canada
1123 "New Year's Day" : [ 1, 1 ],
1124 "Good Friday" : [ 'easter', -2 ],
1125 "Canada Day" : [ 'canadaDay', 0 ],
1126 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1127 "Christmas Day" : [ 12, 25 ]
1128 },
1129 'Alberta': {
1130 'PH': {
1131 "New Year's Day" : [ 1, 1 ],
1132 "Alberta Family Day" : [ 'firstFebruaryMonday', 14 ],
1133 "Good Friday" : [ 'easter', -2 ],
1134 "Easter Monday" : [ 'easter', 1 ],
1135 "Victoria Day" : [ 'victoriaDay', 0 ],
1136 "Canada Day" : [ 'canadaDay', 0 ],
1137 "Heritage Day" : [ 'firstAugustMonday', 0 ],
1138 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1139 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1140 "Remembrance Day" : [ 11, 11 ],
1141 "Christmas Day" : [ 12, 25 ],
1142 "Boxing Day" : [ 12, 26 ]
1143 },
1144 },
1145 'British Columbia': {
1146 'PH': {
1147 "New Year's Day" : [ 1, 1 ],
1148 "Family Day" : [ 'firstFebruaryMonday', 7 ],
1149 "Good Friday" : [ 'easter', -2 ],
1150 "Victoria Day" : [ 'victoriaDay', 0 ],
1151 "Canada Day" : [ 'canadaDay', 0 ],
1152 "British Columbia Day" : [ 'firstAugustMonday', 0 ],
1153 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1154 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1155 "Remembrance Day" : [ 11, 11 ],
1156 "Christmas Day" : [ 12, 25 ]
1157 },
1158 },
1159 'Manitoba': {
1160 'PH': {
1161 "New Year's Day" : [ 1, 1 ],
1162 "Louis Riel Day" : [ 'firstFebruaryMonday', 14 ],
1163 "Good Friday" : [ 'easter', -2 ],
1164 "Victoria Day" : [ 'victoriaDay', 0 ],
1165 "Canada Day" : [ 'canadaDay', 0 ],
1166 "Civic Holiday" : [ 'firstAugustMonday', 0 ],
1167 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1168 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1169 "Remembrance Day" : [ 11, 11 ],
1170 "Christmas Day" : [ 12, 25 ]
1171 },
1172 },
1173 'New Brunswick': {
1174 'PH': {
1175 "New Year's Day" : [ 1, 1 ],
1176 "Good Friday" : [ 'easter', -2 ],
1177 "Victoria Day" : [ 'victoriaDay', 0 ],
1178 "Canada Day" : [ 'canadaDay', 0 ],
1179 "New Brunswick Day" : [ 'firstAugustMonday', 0 ],
1180 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1181 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1182 "Remembrance Day" : [ 11, 11 ],
1183 "Christmas Day" : [ 12, 25 ],
1184 "Boxing Day" : [ 12, 26 ]
1185 },
1186 },
1187 'Newfoundland and Labrador': {
1188 'PH': {
1189 "New Year's Day" : [ 1, 1 ],
1190 "Saint Patrick's Day" : [ 3, 17 ],
1191 "Good Friday" : [ 'easter', -2 ],
1192 "Saint George's Day" : [ 4, 23 ],
1193 "Discovery Day" : [ 6, 24 ],
1194 "Memorial Day" : [ 7, 1 ],
1195 "Orangemen's Day" : [ 7, 12 ],
1196 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1197 "Armistice Day" : [ 11, 11 ],
1198 "Christmas Day" : [ 12, 25 ]
1199 },
1200 },
1201 'Northwest Territories': {
1202 'PH': {
1203 "New Year's Day" : [ 1, 1 ],
1204 "Good Friday" : [ 'easter', -2 ],
1205 "Victoria Day" : [ 'victoriaDay', 0 ],
1206 "National Aboriginal Day" : [ 6, 21 ],
1207 "Canada Day" : [ 'canadaDay', 0 ],
1208 "Civic Holiday" : [ 'firstAugustMonday', 0 ],
1209 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1210 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1211 "Remembrance Day" : [ 11, 11 ],
1212 "Christmas Day" : [ 12, 25 ]
1213 },
1214 },
1215 'Nova Scotia': {
1216 'PH': {
1217 "New Year's Day" : [ 1, 1 ],
1218 "Good Friday" : [ 'easter', -2 ],
1219 "Victoria Day" : [ 'victoriaDay', 0 ],
1220 "Canada Day" : [ 'canadaDay', 0 ],
1221 "Natal Day" : [ 'firstAugustMonday', 0 ],
1222 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1223 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1224 "Remembrance Day" : [ 11, 11 ],
1225 "Christmas Day" : [ 12, 25 ],
1226 "Boxing Day" : [ 12, 26 ]
1227 },
1228 },
1229 'Nunavut': {
1230 'PH': {
1231 "New Year's Day" : [ 1, 1 ],
1232 "Good Friday" : [ 'easter', -2 ],
1233 "Victoria Day" : [ 'victoriaDay', 0 ],
1234 "Canada Day" : [ 'canadaDay', 0 ],
1235 "Nunavut Day" : [ 7, 9 ],
1236 "Civic Holiday" : [ 'firstAugustMonday', 0 ],
1237 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1238 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1239 "Remembrance Day" : [ 11, 11 ],
1240 "Christmas Day" : [ 12, 25 ]
1241 },
1242 },
1243 'Ontario': {
1244 'PH': {
1245 "New Year's Day" : [ 1, 1 ],
1246 "Family Day" : [ 'firstFebruaryMonday', 14 ],
1247 "Good Friday" : [ 'easter', -2 ],
1248 "Victoria Day" : [ 'victoriaDay', 0 ],
1249 "Canada Day" : [ 'canadaDay', 0 ],
1250 "August Civic Public Holiday" : [ 'firstAugustMonday', 0 ],
1251 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1252 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1253 "Remembrance Day" : [ 11, 11 ],
1254 "Christmas Day" : [ 12, 25 ],
1255 "Boxing Day" : [ 12, 26 ]
1256 },
1257 },
1258 'Prince Edward Island': {
1259 'PH': {
1260 "New Year's Day" : [ 1, 1 ],
1261 "Islander Day" : [ 'firstFebruaryMonday', 14 ],
1262 "Good Friday" : [ 'easter', -2 ],
1263 "Easter Monday" : [ 'easter', 1 ],
1264 "Victoria Day" : [ 'victoriaDay', 0 ],
1265 "Canada Day" : [ 'canadaDay', 0 ],
1266 "Civic Holiday" : [ 'firstAugustMonday', 0 ],
1267 "Gold Cup Parade Day" : [ 'firstAugustMonday', 18 ],
1268 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1269 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1270 "Remembrance Day" : [ 11, 11 ],
1271 "Christmas Day" : [ 12, 25 ],
1272 "Boxing Day" : [ 12, 26 ]
1273 },
1274 },
1275 'Quebec': {
1276 'PH': {
1277 "Jour de l'an" : [ 1, 1 ],
1278 "Vendredi saint" : [ 'easter', -2 ],
1279 "Lundi de Pâques" : [ 'easter', 1 ],
1280 "Journée nationale des patriotes" : [ 'victoriaDay', 0 ],
1281 "Fête nationale du Québec" : [ 6, 24 ],
1282 "Fête du Canada" : [ 'canadaDay', 0 ],
1283 "Fête du Travail" : [ 'firstSeptemberMonday', 0 ],
1284 "Jour de l'Action de grâce" : [ 'firstOctoberMonday', 7 ],
1285 "Noël" : [ 12, 25 ]
1286 },
1287 },
1288 'Saskatchewan': {
1289 'PH': {
1290 "New Year's Day" : [ 1, 1 ],
1291 "Family Day" : [ 'firstFebruaryMonday', 14 ],
1292 "Good Friday" : [ 'easter', -2 ],
1293 "Victoria Day" : [ 'victoriaDay', 0 ],
1294 "Canada Day" : [ 'canadaDay', 0 ],
1295 "Saskatchewan Day" : [ 'firstAugustMonday', 0 ],
1296 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1297 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1298 "Remembrance Day" : [ 11, 11 ],
1299 "Christmas Day" : [ 12, 25 ]
1300 },
1301 },
1302 'Yukon': {
1303 'PH': {
1304 "New Year's Day" : [ 1, 1 ],
1305 "Heritage Day" : [ 'lastFebruarySunday', -2 ],
1306 "Good Friday" : [ 'easter', -2 ],
1307 "Easter Monday" : [ 'easter', 1 ],
1308 "Victoria Day" : [ 'victoriaDay', 0 ],
1309 "Canada Day" : [ 'canadaDay', 0 ],
1310 "Discovery Day" : [ 'firstAugustMonday', 14 ],
1311 "Labour Day" : [ 'firstSeptemberMonday', 0 ],
1312 "Thanksgiving" : [ 'firstOctoberMonday', 7 ],
1313 "Remembrance Day" : [ 11, 11 ],
1314 "Christmas Day" : [ 12, 25 ],
1315 "Boxing Day" : [ 12, 26 ]
1316 },
1317 },
1318 }, // }}}
1319 'ru': { // {{{
1320 'PH': { // https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B0%D0%B7%D0%B4%D0%BD%D0%B8%D0%BA%D0%B8_%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D0%B8
1321 "1. Новогодние каникулы": [ 1, 1 ],
1322 "2. Новогодние каникулы": [ 1, 2 ],
1323 "3. Новогодние каникулы": [ 1, 3 ],
1324 "4. Новогодние каникулы": [ 1, 4 ],
1325 "5. Новогодние каникулы": [ 1, 5 ],
1326 "6. Новогодние каникулы": [ 1, 6 ],
1327 "Рождество Христово": [ 1, 7 ],
1328 "8. Новогодние каникулы": [ 1, 8 ],
1329 "День защитника Отечества": [ 2, 23 ],
1330 "Международный женский день": [ 3, 8 ],
1331 "День Победы": [ 5, 9 ],
1332 "Праздник Весны и Труда": [ 5, 1 ],
1333 "День народного единства": [ 11, 4 ],
1334 "День России": [ 6, 12 ],
1335 },
1336 'Tatarstan': { // https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B0%D0%B7%D0%B4%D0%BD%D0%B8%D0%BA%D0%B8_%D0%A2%D0%B0%D1%82%D0%B0%D1%80%D1%81%D1%82%D0%B0%D0%BD%D0%B0
1337 'PH': {
1338 "1. Новогодние каникулы": [ 1, 1 ],
1339 "2. Новогодние каникулы": [ 1, 2 ],
1340 "3. Новогодние каникулы": [ 1, 3 ],
1341 "4. Новогодние каникулы": [ 1, 4 ],
1342 "5. Новогодние каникулы": [ 1, 5 ],
1343 "6. Новогодние каникулы": [ 1, 6 ],
1344 "Рождество Христово": [ 1, 7 ],
1345 "8. Новогодние каникулы": [ 1, 8 ],
1346 "День защитника Отечества": [ 2, 23 ],
1347 "Международный женский день": [ 3, 8 ],
1348 "День Победы": [ 5, 9 ],
1349 "Праздник Весны и Труда": [ 5, 1 ],
1350 "День народного единства": [ 11, 4 ],
1351 "День России": [ 6, 12 ],
1352 // local
1353 "Ураза-байрам": [ 7, 28 ],
1354 "День Республики Татарстан": [ 8, 30 ],
1355 "Курбан-байрам": [ 10, 4 ],
1356 "День Конституции Республики Татарстан": [ 11, 6 ],
1357 },
1358 },
1359 'Bashkortostan': { // https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B0%D0%B7%D0%B4%D0%BD%D0%B8%D0%BA%D0%B8_%D0%91%D0%B0%D1%88%D0%BA%D0%BE%D1%80%D1%82%D0%BE%D1%81%D1%82%D0%B0%D0%BD%D0%B0
1360 'PH': {
1361 "1. Новогодние каникулы": [ 1, 1 ],
1362 "2. Новогодние каникулы": [ 1, 2 ],
1363 "3. Новогодние каникулы": [ 1, 3 ],
1364 "4. Новогодние каникулы": [ 1, 4 ],
1365 "5. Новогодние каникулы": [ 1, 5 ],
1366 "6. Новогодние каникулы": [ 1, 6 ],
1367 "Рождество Христово": [ 1, 7 ],
1368 "8. Новогодние каникулы": [ 1, 8 ],
1369 "День защитника Отечества": [ 2, 23 ],
1370 "Международный женский день": [ 3, 8 ],
1371 "День Победы": [ 5, 9 ],
1372 "Праздник Весны и Труда": [ 5, 1 ],
1373 "День народного единства": [ 11, 4 ],
1374 "День России": [ 6, 12 ],
1375 // local
1376 "Ураза-байрам": [ 7, 28 ],
1377 "Курбан-байрам": [ 10, 4 ],
1378 "День Республики Башкирии": [ 10, 11 ],
1379 "День Конституции Башкортостана": [ 12, 24 ],
1380 },
1381 },
1382 'Chuvashia': {
1383 'PH': {
1384 "1. Новогодние каникулы": [ 1, 1 ],
1385 "2. Новогодние каникулы": [ 1, 2 ],
1386 "3. Новогодние каникулы": [ 1, 3 ],
1387 "4. Новогодние каникулы": [ 1, 4 ],
1388 "5. Новогодние каникулы": [ 1, 5 ],
1389 "6. Новогодние каникулы": [ 1, 6 ],
1390 "Рождество Христово": [ 1, 7 ],
1391 "8. Новогодние каникулы": [ 1, 8 ],
1392 "День защитника Отечества": [ 2, 23 ],
1393 "Международный женский день": [ 3, 8 ],
1394 "День Победы": [ 5, 9 ],
1395 "Праздник Весны и Труда": [ 5, 1 ],
1396 "День народного единства": [ 11, 4 ],
1397 "День России": [ 6, 12 ],
1398 // local
1399 "День Чувашской республики": [ 6, 24 ],
1400 },
1401 },
1402 'Sakha Republic': { // https://ru.wikipedia.org/wiki/%D0%AF%D0%BA%D1%83%D1%82%D0%B8%D1%8F#.D0.9F.D1.80.D0.B0.D0.B7.D0.B4.D0.BD.D0.B8.D0.BA.D0.B8_.D0.AF.D0.BA.D1.83.D1.82.D0.B8.D0.B8
1403 'PH': {
1404 "1. Новогодние каникулы": [ 1, 1 ],
1405 "2. Новогодние каникулы": [ 1, 2 ],
1406 "3. Новогодние каникулы": [ 1, 3 ],
1407 "4. Новогодние каникулы": [ 1, 4 ],
1408 "5. Новогодние каникулы": [ 1, 5 ],
1409 "6. Новогодние каникулы": [ 1, 6 ],
1410 "Рождество Христово": [ 1, 7 ],
1411 "8. Новогодние каникулы": [ 1, 8 ],
1412 "День защитника Отечества": [ 2, 23 ],
1413 "Международный женский день": [ 3, 8 ],
1414 "День Победы": [ 5, 9 ],
1415 "Праздник Весны и Труда": [ 5, 1 ],
1416 "День народного единства": [ 11, 4 ],
1417 "День России": [ 6, 12 ],
1418 // local
1419 "День Республики Саха": [ 4, 27 ],
1420 "Ысыах": [ 6, 23 ],
1421 "День государственности Республики Саха": [ 9, 27 ],
1422 },
1423 },
1424 'Republic of Kalmykia': { // https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B0%D0%B7%D0%B4%D0%BD%D0%B8%D0%BA%D0%B8_%D0%B8_%D0%BF%D0%B0%D0%BC%D1%8F%D1%82%D0%BD%D1%8B%D0%B5_%D0%B4%D0%B0%D1%82%D1%8B_%D0%9A%D0%B0%D0%BB%D0%BC%D1%8B%D0%BA%D0%B8%D0%B8
1425 'PH': {
1426 "1. Новогодние каникулы": [ 1, 1 ],
1427 "2. Новогодние каникулы": [ 1, 2 ],
1428 "3. Новогодние каникулы": [ 1, 3 ],
1429 "4. Новогодние каникулы": [ 1, 4 ],
1430 "5. Новогодние каникулы": [ 1, 5 ],
1431 "6. Новогодние каникулы": [ 1, 6 ],
1432 "Рождество Христово": [ 1, 7 ],
1433 "8. Новогодние каникулы": [ 1, 8 ],
1434 "День защитника Отечества": [ 2, 23 ],
1435 "Международный женский день": [ 3, 8 ],
1436 "День Победы": [ 5, 9 ],
1437 "Праздник Весны и Труда": [ 5, 1 ],
1438 "День народного единства": [ 11, 4 ],
1439 "День России": [ 6, 12 ],
1440 // local
1441 "Цаган Сар": [ 1, 14 ],
1442 "День принятия Степного Уложения (Конституции) Республики Калмыкия": [ 4, 5 ],
1443 "День рождения Будды Шакьямун": [ 6, 6 ],
1444 "Зул": [ 12, 15 ],
1445 "День памяти жертв депортации калмыцкого народа": [ 12, 28 ],
1446 },
1447 },
1448 'Buryatia': { // https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B0%D0%B7%D0%B4%D0%BD%D0%B8%D0%BA%D0%B8_%D0%91%D1%83%D1%80%D1%8F%D1%82%D0%B8%D0%B8
1449 'PH': {
1450 "1. Новогодние каникулы": [ 1, 1 ],
1451 "2. Новогодние каникулы": [ 1, 2 ],
1452 "3. Новогодние каникулы": [ 1, 3 ],
1453 "4. Новогодние каникулы": [ 1, 4 ],
1454 "5. Новогодние каникулы": [ 1, 5 ],
1455 "6. Новогодние каникулы": [ 1, 6 ],
1456 "Рождество Христово": [ 1, 7 ],
1457 "8. Новогодние каникулы": [ 1, 8 ],
1458 "День защитника Отечества": [ 2, 23 ],
1459 "Международный женский день": [ 3, 8 ],
1460 "День Победы": [ 5, 9 ],
1461 "Праздник Весны и Труда": [ 5, 1 ],
1462 "День народного единства": [ 11, 4 ],
1463 "День России": [ 6, 12 ],
1464 //
1465 "Сагаалган": [ 1, 14 ],
1466 },
1467 },
1468 'Republic of Karelia': { // https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B0%D0%B7%D0%B4%D0%BD%D0%B8%D0%BA%D0%B8_%D0%A0%D0%B5%D1%81%D0%BF%D1%83%D0%B1%D0%BB%D0%B8%D0%BA%D0%B8_%D0%9A%D0%B0%D1%80%D0%B5%D0%BB%D0%B8%D1%8F
1469 'PH': {
1470 "1. Новогодние каникулы": [ 1, 1 ],
1471 "2. Новогодние каникулы": [ 1, 2 ],
1472 "3. Новогодние каникулы": [ 1, 3 ],
1473 "4. Новогодние каникулы": [ 1, 4 ],
1474 "5. Новогодние каникулы": [ 1, 5 ],
1475 "6. Новогодние каникулы": [ 1, 6 ],
1476 "Рождество Христово": [ 1, 7 ],
1477 "8. Новогодние каникулы": [ 1, 8 ],
1478 "День защитника Отечества": [ 2, 23 ],
1479 "Международный женский день": [ 3, 8 ],
1480 "День Победы": [ 5, 9 ],
1481 "Праздник Весны и Труда": [ 5, 1 ],
1482 "День народного единства": [ 11, 4 ],
1483 "День России": [ 6, 12 ],
1484 // local
1485 "День Республики Карелия": [ 6, 8 ],
1486 "День освобождения Карелии от фашистских захватчиков": [ 9, 30 ],
1487 },
1488 },
1489 'Удмуртская республика': {
1490 'PH': {
1491 "1. Новогодние каникулы": [ 1, 1 ],
1492 "2. Новогодние каникулы": [ 1, 2 ],
1493 "3. Новогодние каникулы": [ 1, 3 ],
1494 "4. Новогодние каникулы": [ 1, 4 ],
1495 "5. Новогодние каникулы": [ 1, 5 ],
1496 "6. Новогодние каникулы": [ 1, 6 ],
1497 "Рождество Христово": [ 1, 7 ],
1498 "8. Новогодние каникулы": [ 1, 8 ],
1499 "День защитника Отечества": [ 2, 23 ],
1500 "Международный женский день": [ 3, 8 ],
1501 "День Победы": [ 5, 9 ],
1502 "Праздник Весны и Труда": [ 5, 1 ],
1503 "День народного единства": [ 11, 4 ],
1504 "День России": [ 6, 12 ],
1505 // local
1506 "День Государственности Удмуртской Республики": [ 5, 31 ],
1507 },
1508 },
1509 'Adygea': {
1510 'PH': {
1511 "1. Новогодние каникулы": [ 1, 1 ],
1512 "2. Новогодние каникулы": [ 1, 2 ],
1513 "3. Новогодние каникулы": [ 1, 3 ],
1514 "4. Новогодние каникулы": [ 1, 4 ],
1515 "5. Новогодние каникулы": [ 1, 5 ],
1516 "6. Новогодние каникулы": [ 1, 6 ],
1517 "Рождество Христово": [ 1, 7 ],
1518 "8. Новогодние каникулы": [ 1, 8 ],
1519 "День защитника Отечества": [ 2, 23 ],
1520 "Международный женский день": [ 3, 8 ],
1521 "День Победы": [ 5, 9 ],
1522 "Праздник Весны и Труда": [ 5, 1 ],
1523 "День народного единства": [ 11, 4 ],
1524 "День России": [ 6, 12 ],
1525 // local
1526 "Ураза-байрам": [ 7, 28 ],
1527 "Курбан-байрам": [ 10, 4 ],
1528 "День образования Республики Адыгея": [ 10, 5 ],
1529 },
1530 },
1531 'Republic of Dagestan': { // https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B0%D0%B7%D0%B4%D0%BD%D0%B8%D0%BA%D0%B8_%D0%94%D0%B0%D0%B3%D0%B5%D1%81%D1%82%D0%B0%D0%BD%D0%B0
1532 'PH': {
1533 "1. Новогодние каникулы": [ 1, 1 ],
1534 "2. Новогодние каникулы": [ 1, 2 ],
1535 "3. Новогодние каникулы": [ 1, 3 ],
1536 "4. Новогодние каникулы": [ 1, 4 ],
1537 "5. Новогодние каникулы": [ 1, 5 ],
1538 "6. Новогодние каникулы": [ 1, 6 ],
1539 "Рождество Христово": [ 1, 7 ],
1540 "8. Новогодние каникулы": [ 1, 8 ],
1541 "День защитника Отечества": [ 2, 23 ],
1542 "Международный женский день": [ 3, 8 ],
1543 "День Победы": [ 5, 9 ],
1544 "Праздник Весны и Труда": [ 5, 1 ],
1545 "День народного единства": [ 11, 4 ],
1546 "День России": [ 6, 12 ],
1547 // local
1548 "День Конституции Республики Дагестан": [ 7, 26 ],
1549 "Ураза-байрам": [ 7, 28 ],
1550 "День единства народов Дагестана": [ 9, 15 ],
1551 "Курбан-байрам": [ 10, 4 ],
1552 },
1553 },
1554 'Ingushetia': { // https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B0%D0%B7%D0%B4%D0%BD%D0%B8%D0%BA%D0%B8_%D0%98%D0%BD%D0%B3%D1%83%D1%88%D0%B5%D1%82%D0%B8%D0%B8
1555 'PH': {
1556 "1. Новогодние каникулы": [ 1, 1 ],
1557 "2. Новогодние каникулы": [ 1, 2 ],
1558 "3. Новогодние каникулы": [ 1, 3 ],
1559 "4. Новогодние каникулы": [ 1, 4 ],
1560 "5. Новогодние каникулы": [ 1, 5 ],
1561 "6. Новогодние каникулы": [ 1, 6 ],
1562 "Рождество Христово": [ 1, 7 ],
1563 "8. Новогодние каникулы": [ 1, 8 ],
1564 "День защитника Отечества": [ 2, 23 ],
1565 "Международный женский день": [ 3, 8 ],
1566 "День Победы": [ 5, 9 ],
1567 "Праздник Весны и Труда": [ 5, 1 ],
1568 "День народного единства": [ 11, 4 ],
1569 "День России": [ 6, 12 ],
1570 // local
1571 "День образования Республики Ингушетия": [ 6, 4 ],
1572 "Ураза-байрам": [ 7, 28 ],
1573 "Курбан-байрам": [ 10, 4 ],
1574 },
1575 },
1576 'Карачаево-Черкесская республика': {
1577 'PH': {
1578 "1. Новогодние каникулы": [ 1, 1 ],
1579 "2. Новогодние каникулы": [ 1, 2 ],
1580 "3. Новогодние каникулы": [ 1, 3 ],
1581 "4. Новогодние каникулы": [ 1, 4 ],
1582 "5. Новогодние каникулы": [ 1, 5 ],
1583 "6. Новогодние каникулы": [ 1, 6 ],
1584 "Рождество Христово": [ 1, 7 ],
1585 "8. Новогодние каникулы": [ 1, 8 ],
1586 "День защитника Отечества": [ 2, 23 ],
1587 "Международный женский день": [ 3, 8 ],
1588 "День Победы": [ 5, 9 ],
1589 "Праздник Весны и Труда": [ 5, 1 ],
1590 "День народного единства": [ 11, 4 ],
1591 "День России": [ 6, 12 ],
1592 // local
1593 "День возрождения карачаевского народа": [ 5, 3 ],
1594 "Ураза-байрам": [ 7, 28 ],
1595 "Курбан-байрам": [ 10, 4 ],
1596 },
1597 },
1598 'Chechen Republic': { // https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B0%D0%B7%D0%B4%D0%BD%D0%B8%D0%BA%D0%B8_%D0%A7%D0%B5%D1%87%D0%BD%D0%B8
1599 'PH': {
1600 "1. Новогодние каникулы": [ 1, 1 ],
1601 "2. Новогодние каникулы": [ 1, 2 ],
1602 "3. Новогодние каникулы": [ 1, 3 ],
1603 "4. Новогодние каникулы": [ 1, 4 ],
1604 "5. Новогодние каникулы": [ 1, 5 ],
1605 "6. Новогодние каникулы": [ 1, 6 ],
1606 "Рождество Христово": [ 1, 7 ],
1607 "8. Новогодние каникулы": [ 1, 8 ],
1608 "День защитника Отечества": [ 2, 23 ],
1609 "Международный женский день": [ 3, 8 ],
1610 "День Победы": [ 5, 9 ],
1611 "Праздник Весны и Труда": [ 5, 1 ],
1612 "День народного единства": [ 11, 4 ],
1613 "День России": [ 6, 12 ],
1614 // local
1615 "День мира в Чеченской Республике": [ 4, 16 ],
1616 "Ураза-байрам": [ 7, 28 ],
1617 "Курбан-байрам": [ 10, 4 ],
1618 },
1619 },
1620 'Кабардино-Балкарская республика': {
1621 'PH': {
1622 "1. Новогодние каникулы": [ 1, 1 ],
1623 "2. Новогодние каникулы": [ 1, 2 ],
1624 "3. Новогодние каникулы": [ 1, 3 ],
1625 "4. Новогодние каникулы": [ 1, 4 ],
1626 "5. Новогодние каникулы": [ 1, 5 ],
1627 "6. Новогодние каникулы": [ 1, 6 ],
1628 "Рождество Христово": [ 1, 7 ],
1629 "8. Новогодние каникулы": [ 1, 8 ],
1630 "День защитника Отечества": [ 2, 23 ],
1631 "Международный женский день": [ 3, 8 ],
1632 "День Победы": [ 5, 9 ],
1633 "Праздник Весны и Труда": [ 5, 1 ],
1634 "День народного единства": [ 11, 4 ],
1635 "День России": [ 6, 12 ],
1636 // local
1637 "День возрождения балкарского народа": [ 3, 28 ],
1638 "Черкесский день траура": [ 5, 21 ],
1639 "Ураза-байрам": [ 7, 28 ],
1640 "День государственности Кабардино-Балкарской Республики": [ 9, 1 ],
1641 "Курбан-байрам": [ 10, 4 ],
1642 },
1643 },
1644 'Altai Republic': {
1645 'PH': {
1646 "1. Новогодние каникулы": [ 1, 1 ],
1647 "2. Новогодние каникулы": [ 1, 2 ],
1648 "3. Новогодние каникулы": [ 1, 3 ],
1649 "4. Новогодние каникулы": [ 1, 4 ],
1650 "5. Новогодние каникулы": [ 1, 5 ],
1651 "6. Новогодние каникулы": [ 1, 6 ],
1652 "Рождество Христово": [ 1, 7 ],
1653 "8. Новогодние каникулы": [ 1, 8 ],
1654 "День защитника Отечества": [ 2, 23 ],
1655 "Международный женский день": [ 3, 8 ],
1656 "День Победы": [ 5, 9 ],
1657 "Праздник Весны и Труда": [ 5, 1 ],
1658 "День народного единства": [ 11, 4 ],
1659 "День России": [ 6, 12 ],
1660 // local
1661 "Чага-Байрам": [ 1, 14 ],
1662 },
1663 },
1664 'Tuva': {
1665 'PH': {
1666 "1. Новогодние каникулы": [ 1, 1 ],
1667 "2. Новогодние каникулы": [ 1, 2 ],
1668 "3. Новогодние каникулы": [ 1, 3 ],
1669 "4. Новогодние каникулы": [ 1, 4 ],
1670 "5. Новогодние каникулы": [ 1, 5 ],
1671 "6. Новогодние каникулы": [ 1, 6 ],
1672 "Рождество Христово": [ 1, 7 ],
1673 "8. Новогодние каникулы": [ 1, 8 ],
1674 "День защитника Отечества": [ 2, 23 ],
1675 "Международный женский день": [ 3, 8 ],
1676 "День Победы": [ 5, 9 ],
1677 "Праздник Весны и Труда": [ 5, 1 ],
1678 "День народного единства": [ 11, 4 ],
1679 "День России": [ 6, 12 ],
1680 // local
1681 "Народный праздник Шагаа": [ 1, 14 ],
1682 "День Республики Тыва": [ 8, 15 ],
1683 },
1684 },
1685 'Saratov Oblast': {
1686 'PH': {
1687 "1. Новогодние каникулы": [ 1, 1 ],
1688 "2. Новогодние каникулы": [ 1, 2 ],
1689 "3. Новогодние каникулы": [ 1, 3 ],
1690 "4. Новогодние каникулы": [ 1, 4 ],
1691 "5. Новогодние каникулы": [ 1, 5 ],
1692 "6. Новогодние каникулы": [ 1, 6 ],
1693 "Рождество Христово": [ 1, 7 ],
1694 "8. Новогодние каникулы": [ 1, 8 ],
1695 "День защитника Отечества": [ 2, 23 ],
1696 "Международный женский день": [ 3, 8 ],
1697 "День Победы": [ 5, 9 ],
1698 "Праздник Весны и Труда": [ 5, 1 ],
1699 "День народного единства": [ 11, 4 ],
1700 "День России": [ 6, 12 ],
1701 // local
1702 "Радоница": [ 4, 29 ],
1703 },
1704 },
1705 'Bryansk Oblast': {
1706 'PH': {
1707 "1. Новогодние каникулы": [ 1, 1 ],
1708 "2. Новогодние каникулы": [ 1, 2 ],
1709 "3. Новогодние каникулы": [ 1, 3 ],
1710 "4. Новогодние каникулы": [ 1, 4 ],
1711 "5. Новогодние каникулы": [ 1, 5 ],
1712 "6. Новогодние каникулы": [ 1, 6 ],
1713 "Рождество Христово": [ 1, 7 ],
1714 "8. Новогодние каникулы": [ 1, 8 ],
1715 "День защитника Отечества": [ 2, 23 ],
1716 "Международный женский день": [ 3, 8 ],
1717 "День Победы": [ 5, 9 ],
1718 "Праздник Весны и Труда": [ 5, 1 ],
1719 "День народного единства": [ 11, 4 ],
1720 "День России": [ 6, 12 ],
1721 // local
1722 "Радоница": [ 4, 29 ],
1723 "День освобождения города Брянска": [ 9, 17 ],
1724 },
1725 },
1726 'Komi Republic': {
1727 'PH': {
1728 "1. Новогодние каникулы": [ 1, 1 ],
1729 "2. Новогодние каникулы": [ 1, 2 ],
1730 "3. Новогодние каникулы": [ 1, 3 ],
1731 "4. Новогодние каникулы": [ 1, 4 ],
1732 "5. Новогодние каникулы": [ 1, 5 ],
1733 "6. Новогодние каникулы": [ 1, 6 ],
1734 "Рождество Христово": [ 1, 7 ],
1735 "8. Новогодние каникулы": [ 1, 8 ],
1736 "День защитника Отечества": [ 2, 23 ],
1737 "Международный женский день": [ 3, 8 ],
1738 "День Победы": [ 5, 9 ],
1739 "Праздник Весны и Труда": [ 5, 1 ],
1740 "День народного единства": [ 11, 4 ],
1741 "День России": [ 6, 12 ],
1742 // local
1743 "День Республики Коми": [ 8, 22 ],
1744 },
1745 },
1746 }, // }}}
1747 'ua': { // {{{
1748 '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
1749 "Новий рік" : [ 1, 1 ],
1750 "Різдво" : [ 1, 7 ],
1751 "Міжнародний жіночий день" : [ 3, 8 ],
1752 "Великдень" : [ 'orthodox easter', 1 ],
1753 "День Праці 1" : [ 5, 1 ],
1754 "День Праці 2" : [ 5, 2 ],
1755 "День Перемоги" : [ 5, 9 ],
1756 "День Конституції України" : [ 6, 28 ],
1757 "День Незалежності України" : [ 8, 24 ],
1758 }
1759 }, // }}}
1760 'us': { // {{{
1761 'PH': { // https://en.wikipedia.org/wiki/Public_holidays_in_the_United_States
1762 "New Year's Day" : [ 1, 1 ],
1763 "Memorial Day" : [ "lastMayMonday", 0 ],
1764 "Independence Day" : [ 7, 4 ],
1765 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1766 "Veterans Day" : [ 11, 11 ],
1767 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1768 "Christmas Day" : [ 12, 25 ]
1769 },
1770 'Alabama': {
1771 'PH': { // http://www.archives.alabama.gov/intro/holidays.html
1772 "New Year's Day" : [ 1, 1 ],
1773 "Robert E. Lee/Martin Luther King Birthday" : [ "firstJanuaryMonday", 14 ],
1774 "George Washington/Thomas Jefferson Birthday" : [ "firstFebruaryMonday", 14 ],
1775 "Memorial Day" : [ "lastMayMonday", 0 ],
1776 "Independence Day" : [ 7, 4 ],
1777 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1778 "Columbus Day" : [ "firstOctoberMonday", 7 ],
1779 "Veterans Day" : [ 11, 11 ],
1780 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1781 "Christmas Day" : [ 12, 25 ],
1782 "Confederate Memorial Day" : [ "firstAprilMonday", 21 ],
1783 "Jefferson Davis' Birthday" : [ "firstJuneMonday", 0 ]
1784 }
1785 },
1786 'Alaska': {
1787 'PH': {
1788 "New Year's Day" : [ 1, 1 ],
1789 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
1790 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
1791 "Memorial Day" : [ "lastMayMonday", 0 ],
1792 "Independence Day" : [ 7, 4 ],
1793 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1794 "Veterans Day" : [ 11, 11 ],
1795 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1796 "Christmas Day" : [ 12, 25 ],
1797 "Seward's Day" : [ "lastMarchMonday", 0 ],
1798 "Alaska Day" : [ 10, 18 ]
1799 }
1800 },
1801 'Arizona': {
1802 'PH': {
1803 "New Year's Day" : [ 1, 1 ],
1804 "Dr. Martin Luther King Jr./Civil Rights Day" : [ "firstJanuaryMonday", 14 ],
1805 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
1806 "Memorial Day" : [ "lastMayMonday", 0 ],
1807 "Independence Day" : [ 7, 4 ],
1808 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1809 "Columbus Day" : [ "firstOctoberMonday", 7 ],
1810 "Veterans Day" : [ 11, 11 ],
1811 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1812 "Christmas Day" : [ 12, 25 ]
1813 }
1814 },
1815 'Arkansas': {
1816 'PH': {
1817 "New Year's Day" : [ 1, 1 ],
1818 "Dr. Martin Luther King Jr. and Robert E. Lee's Birthdays" : [ "firstJanuaryMonday", 14 ],
1819 "George Washington's Birthday and Daisy Gatson Bates Day" : [ "firstFebruaryMonday", 14 ],
1820 "Memorial Day" : [ "lastMayMonday", 0 ],
1821 "Independence Day" : [ 7, 4 ],
1822 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1823 "Columbus Day" : [ "firstOctoberMonday", 7 ],
1824 "Veterans Day" : [ 11, 11 ],
1825 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1826 "Christmas Eve" : [ 12, 24 ],
1827 "Christmas Day" : [ 12, 25 ]
1828 }
1829 },
1830 'California': {
1831 'PH': {
1832 "New Year's Day" : [ 1, 1 ],
1833 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
1834 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
1835 "Memorial Day" : [ "lastMayMonday", 0 ],
1836 "Independence Day" : [ 7, 4 ],
1837 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1838 "Columbus Day" : [ "firstOctoberMonday", 7 ],
1839 "Veterans Day" : [ 11, 11 ],
1840 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1841 "Christmas Day" : [ 12, 25 ],
1842 "César Chávez Day" : [ 3, 31 ]
1843 }
1844 },
1845 'Colorado': {
1846 'PH': {
1847 "New Year's Day" : [ 1, 1 ],
1848 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
1849 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
1850 "Memorial Day" : [ "lastMayMonday", 0 ],
1851 "Independence Day" : [ 7, 4 ],
1852 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1853 "Columbus Day" : [ "firstOctoberMonday", 7 ],
1854 "Veterans Day" : [ 11, 11 ],
1855 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1856 "Christmas Day" : [ 12, 25 ]
1857 }
1858 },
1859 'Connecticut': {
1860 'PH': {
1861 "New Year's Day" : [ 1, 1 ],
1862 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
1863 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
1864 "Memorial Day" : [ "lastMayMonday", 0 ],
1865 "Independence Day" : [ 7, 4 ],
1866 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1867 "Columbus Day" : [ "firstOctoberMonday", 7 ],
1868 "Veterans Day" : [ 11, 11 ],
1869 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1870 "Christmas Day" : [ 12, 25 ],
1871 "Lincoln's Birthday" : [ 2, 12 ],
1872 "Good Friday" : [ "easter", -2 ]
1873 }
1874 },
1875 'Delaware': {
1876 'PH': {
1877 "New Year's Day" : [ 1, 1 ],
1878 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
1879 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
1880 "Memorial Day" : [ "lastMayMonday", 0 ],
1881 "Independence Day" : [ 7, 4 ],
1882 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1883 "Columbus Day" : [ "firstOctoberMonday", 7 ],
1884 "Veterans Day" : [ 11, 11 ],
1885 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1886 "Day After Thanksgiving" : [ "firstNovemberThursday", 22 ],
1887 "Christmas Day" : [ 12, 25 ],
1888 "Good Friday" : [ "easter", -2 ]
1889 }
1890 },
1891 'District of Columbia': {
1892 'PH': {
1893 "New Year's Day" : [ 1, 1 ],
1894 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
1895 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
1896 "Memorial Day" : [ "lastMayMonday", 0 ],
1897 "Independence Day" : [ 7, 4 ],
1898 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1899 "Columbus Day" : [ "firstOctoberMonday", 7 ],
1900 "Veterans Day" : [ 11, 11 ],
1901 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1902 "Christmas Day" : [ 12, 25 ],
1903 "Emancipation Day" : [ 4, 16 ]
1904 }
1905 },
1906 'Florida': {
1907 'PH': { // http://www.leg.state.fl.us/Statutes/index.cfm?App_mode=Display_Statute&Search_String=&URL=0100-0199/0110/Sections/0110.117.html
1908 "New Year's Day" : [ 1, 1 ],
1909 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
1910 "Memorial Day" : [ "lastMayMonday", 0 ],
1911 "Independence Day" : [ 7, 4 ],
1912 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1913 "Veterans Day" : [ 11, 11 ],
1914 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1915 "Friday after Thanksgiving" : [ "firstNovemberThursday", 22 ],
1916 "Christmas Day" : [ 12, 25 ]
1917 }
1918 },
1919 'Georgia': {
1920 'PH': {
1921 "New Year's Day" : [ 1, 1 ],
1922 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
1923 "Memorial Day" : [ "lastMayMonday", 0 ],
1924 "Independence Day" : [ 7, 4 ],
1925 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1926 "Columbus Day" : [ "firstOctoberMonday", 7 ],
1927 "Veterans Day" : [ 11, 11 ],
1928 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1929 "Robert E. Lee's Birthday" : [ "firstNovemberThursday", 22 ],
1930 "Washington's Birthday" : [ 12, 24 ],
1931 "Christmas Day" : [ 12, 25 ],
1932 "Confederate Memorial Day" : [ "lastAprilMonday", 0 ]
1933 }
1934 },
1935 'Guam': {
1936 'PH': {
1937 "New Year's Day" : [ 1, 1 ],
1938 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
1939 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
1940 "Memorial Day" : [ "lastMayMonday", 0 ],
1941 "Independence Day" : [ 7, 4 ],
1942 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1943 "Columbus Day" : [ "firstOctoberMonday", 7 ],
1944 "Veterans Day" : [ 11, 11 ],
1945 "Guam Discovery Day" : [ 3, 5 ],
1946 "Good Friday" : [ "easter", -2 ],
1947 "Liberation Day" : [ 7, 21 ],
1948 "All Souls' Day" : [ 11, 2 ],
1949 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1950 "Lady of Camarin Day" : [ 12, 8 ],
1951 "Christmas Day" : [ 12, 25 ],
1952 }
1953 },
1954 'Hawaii': {
1955 'PH': {
1956 "New Year's Day" : [ 1, 1 ],
1957 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
1958 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
1959 "Memorial Day" : [ "lastMayMonday", 0 ],
1960 "Independence Day" : [ 7, 4 ],
1961 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1962 "Veterans Day" : [ 11, 11 ],
1963 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1964 "Christmas Day" : [ 12, 25 ],
1965 "Prince Jonah Kuhio Kalanianaole Day" : [ 3, 26 ],
1966 "Kamehameha Day" : [ 6, 11 ],
1967 "Statehood Day" : [ "firstAugustFriday", 14 ],
1968 "Election Day" : [ "firstNovemberMonday", 1 ]
1969 }
1970 },
1971 'Idaho': {
1972 'PH': {
1973 "New Year's Day" : [ 1, 1 ],
1974 "Martin Luther King, Jr.-Idaho Human Rights Day" : [ "firstJanuaryMonday", 14 ],
1975 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
1976 "Memorial Day" : [ "lastMayMonday", 0 ],
1977 "Independence Day" : [ 7, 4 ],
1978 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1979 "Columbus Day" : [ "firstOctoberMonday", 7 ],
1980 "Veterans Day" : [ 11, 11 ],
1981 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1982 "Christmas Day" : [ 12, 25 ]
1983 }
1984 },
1985 'Illinois': {
1986 'PH': {
1987 "New Year's Day" : [ 1, 1 ],
1988 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
1989 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
1990 "Memorial Day" : [ "lastMayMonday", 0 ],
1991 "Independence Day" : [ 7, 4 ],
1992 "Labor Day" : [ "firstSeptemberMonday", 0 ],
1993 "Columbus Day" : [ "firstOctoberMonday", 7 ],
1994 "Veterans Day" : [ 11, 11 ],
1995 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
1996 "Christmas Day" : [ 12, 25 ],
1997 "Lincoln's Birthday" : [ 2, 12 ],
1998 "Casimir Pulaski Day" : [ "firstMarchMonday", 0 ],
1999 "Election Day" : [ "firstNovemberMonday", 1 ]
2000 }
2001 },
2002 'Indiana': {
2003 'PH': {
2004 "New Year's Day" : [ 1, 1 ],
2005 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2006 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2007 "Memorial Day" : [ "lastMayMonday", 0 ],
2008 "Independence Day" : [ 7, 4 ],
2009 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2010 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2011 "Veterans Day" : [ 11, 11 ],
2012 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2013 "Lincoln's Birthday" : [ "firstNovemberThursday", 22 ],
2014 "Christmas Day" : [ 12, 25 ],
2015 "Good Friday" : [ "easter", -2 ],
2016 "Primary Election Day" : [ "firstMayMonday", 1 ],
2017 "Election Day" : [ "firstNovemberMonday", 1 ]
2018 }
2019 },
2020 'Iowa': {
2021 'PH': {
2022 "New Year's Day" : [ 1, 1 ],
2023 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2024 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2025 "Memorial Day" : [ "lastMayMonday", 0 ],
2026 "Independence Day" : [ 7, 4 ],
2027 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2028 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2029 "Veterans Day" : [ 11, 11 ],
2030 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2031 "Christmas Day" : [ 12, 25 ],
2032 "Lincoln's Birthday" : [ 2, 12 ]
2033 }
2034 },
2035 'Kansas': {
2036 'PH': {
2037 "New Year's Day" : [ 1, 1 ],
2038 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2039 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2040 "Memorial Day" : [ "lastMayMonday", 0 ],
2041 "Independence Day" : [ 7, 4 ],
2042 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2043 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2044 "Veterans Day" : [ 11, 11 ],
2045 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2046 "Christmas Day" : [ 12, 25 ]
2047 }
2048 },
2049 'Kentucky': {
2050 'PH': {
2051 "New Year's Day" : [ 1, 1 ],
2052 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2053 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2054 "Memorial Day" : [ "lastMayMonday", 0 ],
2055 "Independence Day" : [ 7, 4 ],
2056 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2057 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2058 "Veterans Day" : [ 11, 11 ],
2059 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2060 "Christmas Eve" : [ 12, 24 ],
2061 "Christmas Day" : [ 12, 25 ],
2062 "New Year's Eve" : [ 12, 31 ],
2063 "Good Friday" : [ "easter", -2 ]
2064 }
2065 },
2066 'Louisiana': {
2067 'PH': {
2068 "New Year's Day" : [ 1, 1 ],
2069 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2070 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2071 "Memorial Day" : [ "lastMayMonday", 0 ],
2072 "Independence Day" : [ 7, 4 ],
2073 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2074 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2075 "Veterans Day" : [ 11, 11 ],
2076 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2077 "Christmas Day" : [ 12, 25 ],
2078 "Mardi Gras" : [ "easter", -47 ],
2079 "Good Friday" : [ "easter", -2 ],
2080 "Election Day" : [ "firstNovemberMonday", 1 ]
2081 }
2082 },
2083 'Maine': {
2084 'PH': {
2085 "New Year's Day" : [ 1, 1 ],
2086 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2087 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2088 "Memorial Day" : [ "lastMayMonday", 0 ],
2089 "Independence Day" : [ 7, 4 ],
2090 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2091 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2092 "Veterans Day" : [ 11, 11 ],
2093 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2094 "Christmas Day" : [ 12, 25 ],
2095 "Patriots' Day" : [ "firstAprilMonday", 14 ]
2096 }
2097 },
2098 'Maryland': {
2099 'PH': {
2100 "New Year's Day" : [ 1, 1 ],
2101 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2102 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2103 "Memorial Day" : [ "lastMayMonday", 0 ],
2104 "Independence Day" : [ 7, 4 ],
2105 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2106 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2107 "Veterans Day" : [ 11, 11 ],
2108 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2109 "Native American Heritage Day" : [ "firstNovemberThursday", 22 ],
2110 "Christmas Day" : [ 12, 25 ]
2111 }
2112 },
2113 'Massachusetts': {
2114 'PH': {
2115 "New Year's Day" : [ 1, 1 ],
2116 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2117 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2118 "Memorial Day" : [ "lastMayMonday", 0 ],
2119 "Independence Day" : [ 7, 4 ],
2120 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2121 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2122 "Veterans Day" : [ 11, 11 ],
2123 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2124 "Christmas Day" : [ 12, 25 ],
2125 "Patriots' Day" : [ "firstAprilMonday", 14 ]
2126 }
2127 },
2128 'Michigan': {
2129 'PH': {
2130 "New Year's Day" : [ 1, 1 ],
2131 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2132 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2133 "Memorial Day" : [ "lastMayMonday", 0 ],
2134 "Independence Day" : [ 7, 4 ],
2135 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2136 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2137 "Veterans Day" : [ 11, 11 ],
2138 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2139 "Christmas Eve" : [ 12, 24 ],
2140 "Christmas Day" : [ 12, 25 ],
2141 "New Year's Eve" : [ 12, 31 ]
2142 }
2143 },
2144 'Minnesota': {
2145 'PH': {
2146 "New Year's Day" : [ 1, 1 ],
2147 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2148 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2149 "Memorial Day" : [ "lastMayMonday", 0 ],
2150 "Independence Day" : [ 7, 4 ],
2151 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2152 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2153 "Veterans Day" : [ 11, 11 ],
2154 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2155 "Christmas Day" : [ 12, 25 ]
2156 }
2157 },
2158 'Mississippi': {
2159 'PH': {
2160 "New Year's Day" : [ 1, 1 ],
2161 "Martin Luther King's and Robert E. Lee's Birthdays" : [ "firstJanuaryMonday", 14 ],
2162 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2163 "Memorial Day" : [ "lastMayMonday", 0 ],
2164 "Independence Day" : [ 7, 4 ],
2165 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2166 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2167 "Veterans Day" : [ 11, 11 ],
2168 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2169 "Christmas Day" : [ 12, 25 ],
2170 "Confederate Memorial Day" : [ "lastAprilMonday", 0 ]
2171 }
2172 },
2173 'Missouri': {
2174 'PH': {
2175 "New Year's Day" : [ 1, 1 ],
2176 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2177 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2178 "Memorial Day" : [ "lastMayMonday", 0 ],
2179 "Independence Day" : [ 7, 4 ],
2180 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2181 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2182 "Veterans Day" : [ 11, 11 ],
2183 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2184 "Christmas Day" : [ 12, 25 ],
2185 "Truman Day" : [ 5, 8 ]
2186 }
2187 },
2188 'Montana': {
2189 'PH': {
2190 "New Year's Day" : [ 1, 1 ],
2191 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2192 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2193 "Memorial Day" : [ "lastMayMonday", 0 ],
2194 "Independence Day" : [ 7, 4 ],
2195 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2196 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2197 "Veterans Day" : [ 11, 11 ],
2198 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2199 "Christmas Day" : [ 12, 25 ],
2200 "Election Day" : [ "firstNovemberMonday", 1 ],
2201 "Christmas Eve" : [ 12, 24 ],
2202 "New Year's Eve" : [ 12, 31 ]
2203 }
2204 },
2205 'Nebraska': {
2206 'PH': {
2207 "New Year's Day" : [ 1, 1 ],
2208 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2209 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2210 "Memorial Day" : [ "lastMayMonday", 0 ],
2211 "Independence Day" : [ 7, 4 ],
2212 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2213 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2214 "Veterans Day" : [ 11, 11 ],
2215 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2216 "Christmas Day" : [ 12, 25 ],
2217 "Arbor Day" : [ "lastAprilFriday", 0 ]
2218 }
2219 },
2220 'Nevada': {
2221 'PH': {
2222 "New Year's Day" : [ 1, 1 ],
2223 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2224 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2225 "Memorial Day" : [ "lastMayMonday", 0 ],
2226 "Independence Day" : [ 7, 4 ],
2227 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2228 "Veterans Day" : [ 11, 11 ],
2229 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2230 "Christmas Day" : [ 12, 25 ],
2231 "Nevada Day" : [ "lastOctoberFriday", 0 ],
2232 "Family Day" : [ "firstNovemberThursday", 22 ]
2233 }
2234 },
2235 'New Hampshire': {
2236 'PH': {
2237 "New Year's Day" : [ 1, 1 ],
2238 "Martin Luther King, Jr. Civil Rights Day" : [ "firstJanuaryMonday", 14 ],
2239 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2240 "Memorial Day" : [ "lastMayMonday", 0 ],
2241 "Independence Day" : [ 7, 4 ],
2242 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2243 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2244 "Veterans Day" : [ 11, 11 ],
2245 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2246 "Day after Thanksgiving" : [ "firstNovemberThursday", 22 ],
2247 "Christmas Day" : [ 12, 25 ],
2248 "Election Day" : [ "firstNovemberMonday", 1 ]
2249 }
2250 },
2251 'New Jersey': {
2252 'PH': {
2253 "New Year's Day" : [ 1, 1 ],
2254 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2255 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2256 "Memorial Day" : [ "lastMayMonday", 0 ],
2257 "Independence Day" : [ 7, 4 ],
2258 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2259 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2260 "Veterans Day" : [ 11, 11 ],
2261 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2262 "Christmas Day" : [ 12, 25 ],
2263 "Lincoln's Birthday" : [ 2, 12 ],
2264 "Good Friday" : [ "easter", -2 ],
2265 "Election Day" : [ "firstNovemberMonday", 1 ]
2266 }
2267 },
2268 'New Mexico': {
2269 'PH': {
2270 "New Year's Day" : [ 1, 1 ],
2271 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2272 "Memorial Day" : [ "lastMayMonday", 0 ],
2273 "Independence Day" : [ 7, 4 ],
2274 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2275 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2276 "Veterans Day" : [ 11, 11 ],
2277 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2278 "Day after Thanksgiving" : [ "firstNovemberThursday", 22 ],
2279 "Christmas Day" : [ 12, 25 ]
2280 }
2281 },
2282 'New York': {
2283 'PH': {
2284 "New Year's Day" : [ 1, 1 ],
2285 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2286 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2287 "Memorial Day" : [ "lastMayMonday", 0 ],
2288 "Independence Day" : [ 7, 4 ],
2289 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2290 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2291 "Veterans Day" : [ 11, 11 ],
2292 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2293 "Christmas Day" : [ 12, 25 ],
2294 "Lincoln's Birthday" : [ 2, 12 ],
2295 "Election Day" : [ "firstNovemberMonday", 1 ]
2296 }
2297 },
2298 'North Carolina': {
2299 'PH': {
2300 "New Year's Day" : [ 1, 1 ],
2301 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2302 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2303 "Memorial Day" : [ "lastMayMonday", 0 ],
2304 "Independence Day" : [ 7, 4 ],
2305 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2306 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2307 "Veterans Day" : [ 11, 11 ],
2308 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2309 "Day after Thanksgiving" : [ "firstNovemberThursday", 22 ],
2310 "Christmas Eve" : [ 12, 24 ],
2311 "Christmas Day" : [ 12, 25 ],
2312 "Day after Christmas" : [ 12, 26 ],
2313 "Good Friday" : [ "easter", -2 ]
2314 }
2315 },
2316 'North Dakota': {
2317 'PH': {
2318 "New Year's Day" : [ 1, 1 ],
2319 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2320 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2321 "Memorial Day" : [ "lastMayMonday", 0 ],
2322 "Independence Day" : [ 7, 4 ],
2323 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2324 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2325 "Veterans Day" : [ 11, 11 ],
2326 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2327 "Christmas Day" : [ 12, 25 ]
2328 }
2329 },
2330 'Ohio': {
2331 'PH': {
2332 "New Year's Day" : [ 1, 1 ],
2333 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2334 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2335 "Memorial Day" : [ "lastMayMonday", 0 ],
2336 "Independence Day" : [ 7, 4 ],
2337 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2338 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2339 "Veterans Day" : [ 11, 11 ],
2340 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2341 "Christmas Day" : [ 12, 25 ]
2342 }
2343 },
2344 'Oklahoma': {
2345 'PH': {
2346 "New Year's Day" : [ 1, 1 ],
2347 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2348 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2349 "Memorial Day" : [ "lastMayMonday", 0 ],
2350 "Independence Day" : [ 7, 4 ],
2351 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2352 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2353 "Veterans Day" : [ 11, 11 ],
2354 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2355 "Day after Thanksgiving" : [ "firstNovemberThursday", 22 ],
2356 "Christmas Day" : [ 12, 25 ]
2357 }
2358 },
2359 'Oregon': {
2360 'PH': {
2361 "New Year's Day" : [ 1, 1 ],
2362 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2363 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2364 "Memorial Day" : [ "lastMayMonday", 0 ],
2365 "Independence Day" : [ 7, 4 ],
2366 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2367 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2368 "Veterans Day" : [ 11, 11 ],
2369 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2370 "Christmas Day" : [ 12, 25 ]
2371 }
2372 },
2373 'Pennsylvania': {
2374 'PH': {
2375 "New Year's Day" : [ 1, 1 ],
2376 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2377 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2378 "Memorial Day" : [ "lastMayMonday", 0 ],
2379 "Independence Day" : [ 7, 4 ],
2380 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2381 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2382 "Veterans Day" : [ 11, 11 ],
2383 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2384 "Christmas Day" : [ 12, 25 ],
2385 "Flag Day" : [ 6, 14 ]
2386 }
2387 },
2388 'Puerto Rico': {
2389 'PH': {
2390 "Día de Año Nuevo" : [ 1, 1 ],
2391 "Día de Reyes" : [ 1, 6 ],
2392 "Natalicio de Eugenio María de Hostos" : [ "firstJanuaryMonday", 7 ],
2393 "Natalicio de Martin Luther King, Jr." : [ "firstJanuaryMonday", 14 ],
2394 "Día de los Presidentes" : [ "firstFebruaryMonday", 14 ],
2395 "Día de la Abolición de Esclavitud" : [ 3, 22 ],
2396 "Viernes Santo" : [ "easter", -2 ],
2397 "Natalicio de José de Diego" : [ "firstAprilMonday", 14 ],
2398 "Recordación de los Muertos de la Guerra" : [ "lastMayMonday", 0 ],
2399 "Día de la Independencia" : [ 7, 4 ],
2400 "Constitución de Puerto Rico" : [ 7, 25 ],
2401 "Natalicio de Dr. José Celso Barbosa" : [ 7, 27 ],
2402 "Día del Trabajo" : [ "firstSeptemberMonday", 0 ],
2403 "Día de la Raza Descubrimiento de América" : [ "firstOctoberMonday", 7 ],
2404 "Día del Veterano" : [ 11, 11 ],
2405 "Día del Descubrimiento de Puerto Rico" : [ 11, 19 ],
2406 "Día de Acción de Gracias" : [ "firstNovemberThursday", 21 ],
2407 "Noche Buena" : [ 12, 24 ],
2408 "Día de Navidad" : [ 12, 25 ]
2409 }
2410 },
2411 'Rhode Island': {
2412 'PH': {
2413 "New Year's Day" : [ 1, 1 ],
2414 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2415 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2416 "Memorial Day" : [ "lastMayMonday", 0 ],
2417 "Independence Day" : [ 7, 4 ],
2418 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2419 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2420 "Veterans Day" : [ 11, 11 ],
2421 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2422 "Christmas Day" : [ 12, 25 ],
2423 "Victory Day" : [ "firstAugustMonday", 7 ]
2424 }
2425 },
2426 'South Carolina': {
2427 'PH': {
2428 "New Year's Day" : [ 1, 1 ],
2429 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2430 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2431 "Memorial Day" : [ "lastMayMonday", 0 ],
2432 "Independence Day" : [ 7, 4 ],
2433 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2434 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2435 "Veterans Day" : [ 11, 11 ],
2436 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2437 "Christmas Day" : [ 12, 25 ],
2438 "Confederate Memorial Day" : [ 5, 10 ]
2439 }
2440 },
2441 'South Dakota': {
2442 'PH': {
2443 "New Year's Day" : [ 1, 1 ],
2444 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2445 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2446 "Memorial Day" : [ "lastMayMonday", 0 ],
2447 "Independence Day" : [ 7, 4 ],
2448 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2449 "Native American Day" : [ "firstOctoberMonday", 7 ],
2450 "Veterans Day" : [ 11, 11 ],
2451 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2452 "Christmas Day" : [ 12, 25 ]
2453 }
2454 },
2455 'Tennessee': {
2456 'PH': {
2457 "New Year's Day" : [ 1, 1 ],
2458 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2459 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2460 "Memorial Day" : [ "lastMayMonday", 0 ],
2461 "Independence Day" : [ 7, 4 ],
2462 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2463 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2464 "Veterans Day" : [ 11, 11 ],
2465 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2466 "Christmas Eve" : [ 12, 24 ],
2467 "Christmas Day" : [ 12, 25 ],
2468 "Good Friday" : [ "easter", -2 ]
2469 }
2470 },
2471 'Texas': {
2472 'PH': {
2473 "New Year's Day" : [ 1, 1 ],
2474 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2475 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2476 "Memorial Day" : [ "lastMayMonday", 0 ],
2477 "Independence Day" : [ 7, 4 ],
2478 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2479 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2480 "Veterans Day" : [ 11, 11 ],
2481 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2482 "Friday after Thanksgiving" : [ "firstNovemberThursday", 22 ],
2483 "Christmas Eve" : [ 12, 24 ],
2484 "Christmas Day" : [ 12, 25 ],
2485 "Day after Christmas" : [ 12, 26 ]
2486 }
2487 },
2488 'United States Virgin Islands': {
2489 'PH': {
2490 "New Year's Day" : [ 1, 1 ],
2491 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2492 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2493 "Memorial Day" : [ "lastMayMonday", 0 ],
2494 "Independence Day" : [ 7, 4 ],
2495 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2496 "Virgin Islands-Puerto Rico Friendship Day" : [ "firstOctoberMonday", 7 ],
2497 "Veterans Day" : [ 11, 11 ],
2498 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2499 "Christmas Day" : [ 12, 25 ],
2500 "Three Kings Day" : [ 1, 6 ],
2501 "Transfer Day" : [ 3, 31 ],
2502 "Holy Thursday" : [ "easter", -3 ],
2503 "Good Friday" : [ "easter", -2 ],
2504 "Easter Monday" : [ "easter", 1 ],
2505 "Emancipation Day" : [ 7, 3 ],
2506 "Hurricane Supplication Day" : [ "firstJulyMonday", 21 ],
2507 "Hurricane Thanksgiving" : [ 10, 25 ],
2508 "Liberty Day" : [ 11, 1 ],
2509 "Christmas Second Day" : [ 12, 26 ],
2510 "New Year's Eve" : [ 12, 31 ]
2511 }
2512 },
2513 'Utah': {
2514 'PH': {
2515 "New Year's Day" : [ 1, 1 ],
2516 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2517 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2518 "Memorial Day" : [ "lastMayMonday", 0 ],
2519 "Independence Day" : [ 7, 4 ],
2520 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2521 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2522 "Veterans Day" : [ 11, 11 ],
2523 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2524 "Christmas Day" : [ 12, 25 ],
2525 "Pioneer Day" : [ 7, 24 ]
2526 }
2527 },
2528 'Vermont': {
2529 'PH': {
2530 "New Year's Day" : [ 1, 1 ],
2531 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2532 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2533 "Memorial Day" : [ "lastMayMonday", 0 ],
2534 "Independence Day" : [ 7, 4 ],
2535 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2536 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2537 "Veterans Day" : [ 11, 11 ],
2538 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2539 "Christmas Day" : [ 12, 25 ],
2540 "Town Meeting Day" : [ "firstMarchTuesday", 0 ],
2541 "Battle of Bennington" : [ "firstAugustMonday", 14 ]
2542 }
2543 },
2544 'Virginia': {
2545 'PH': {
2546 "New Year's Day" : [ 1, 1 ],
2547 "Lee-Jackson Day" : [ "firstJanuaryMonday", 11 ],
2548 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2549 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2550 "Memorial Day" : [ "lastMayMonday", 0 ],
2551 "Independence Day" : [ 7, 4 ],
2552 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2553 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2554 "Veterans Day" : [ 11, 11 ],
2555 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2556 "Christmas Day" : [ 12, 25 ]
2557 }
2558 },
2559 'Washington': {
2560 'PH': {
2561 "New Year's Day" : [ 1, 1 ],
2562 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2563 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2564 "Memorial Day" : [ "lastMayMonday", 0 ],
2565 "Independence Day" : [ 7, 4 ],
2566 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2567 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2568 "Veterans Day" : [ 11, 11 ],
2569 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2570 "Christmas Day" : [ 12, 25 ]
2571 }
2572 },
2573 'West Virginia': {
2574 'PH': {
2575 "New Year's Day" : [ 1, 1 ],
2576 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2577 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2578 "Memorial Day" : [ "lastMayMonday", 0 ],
2579 "Independence Day" : [ 7, 4 ],
2580 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2581 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2582 "Veterans Day" : [ 11, 11 ],
2583 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2584 "Christmas Day" : [ 12, 25 ],
2585 "West Virginia Day" : [ 6, 20 ],
2586 "Lincoln's Day" : [ "firstNovemberThursday", 22 ]
2587 }
2588 },
2589 'Wisconsin': {
2590 'PH': { // http://docs.legis.wisconsin.gov/statutes/statutes/995/20
2591 "New Year's Day" : [ 1, 1 ],
2592 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2593 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2594 "Memorial Day" : [ "lastMayMonday", 0 ],
2595 "Independence Day" : [ 7, 4 ],
2596 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2597 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2598 "Veterans Day" : [ 11, 11 ],
2599 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2600 "Christmas Day" : [ 12, 25 ],
2601 "Primary Election Day" : [ "firstAugustTuesday", 7 ],
2602 "Election Day" : [ "firstNovemberMonday", 1 ]
2603 }
2604 },
2605 'Wyoming': {
2606 'PH': {
2607 "New Year's Day" : [ 1, 1 ],
2608 "Martin Luther King, Jr. Day" : [ "firstJanuaryMonday", 14 ],
2609 "Washington's Birthday" : [ "firstFebruaryMonday", 14 ],
2610 "Memorial Day" : [ "lastMayMonday", 0 ],
2611 "Independence Day" : [ 7, 4 ],
2612 "Labor Day" : [ "firstSeptemberMonday", 0 ],
2613 "Columbus Day" : [ "firstOctoberMonday", 7 ],
2614 "Veterans Day" : [ 11, 11 ],
2615 "Thanksgiving" : [ "firstNovemberThursday", 21 ],
2616 "Christmas Day" : [ 12, 25 ]
2617 }
2618 }
2619 }, // }}}
2620 'si': { // {{{
2621 'PH': { // http://www.vlada.si/o_sloveniji/politicni_sistem/prazniki/
2622 'novo leto' : [ 1, 1 ],
2623 'Prešernov dan, slovenski kulturni praznik' : [ 2, 8 ],
2624 'velikonočna nedelja' : [ 'easter', 0 ],
2625 'velikonočni ponedeljek' : [ 'easter', 1 ],
2626 'dan upora proti okupatorju' : [ 4, 27 ],
2627 'praznik dela 1' : [ 5, 1 ],
2628 'praznik dela 2' : [ 5, 2 ],
2629 'binkoštna nedelja - binkošti' : [ 'easter', 49 ],
2630 'dan državnosti' : [ 6, 25 ],
2631 'Marijino vnebovzetje' : [ 8, 15 ],
2632 'dan reformacije' : [ 10, 31 ],
2633 'dan spomina na mrtve' : [ 11, 1 ],
2634 'božič' : [ 12, 25 ],
2635 'dan samostojnosti in enotnosti' : [ 12, 26 ]
2636 }
2637 }, // }}}
2638 'it': { // {{{
2639 'PH': { // http://www.governo.it/Presidenza/ufficio_cerimoniale/cerimoniale/giornate.html
2640 'Capodanno' : [ 1, 1 ],
2641 'Epifania' : [ 1, 6 ],
2642 'Liberazione dal nazifascismo (1945)' : [ 4, 25 ],
2643 'Pasqua' : [ 'easter', 0 ],
2644 'Lunedì di Pasqua' : [ 'easter', 1 ],
2645 'Festa del lavoro' : [ 5, 1 ],
2646 'Festa della Repubblica' : [ 6, 2 ],
2647 'Assunzione di Maria' : [ 8, 15 ],
2648 'Ognissanti' : [ 11, 1 ],
2649 'Festa dell’unità nazionale' : [ 'firstSeptemberSunday', 0 ],
2650 'Immacolata Concezione' : [ 12, 8 ],
2651 'Natale di Gesù' : [ 12, 25 ],
2652 'Santo Stefano' : [ 12, 26 ],
2653 },
2654 }, // }}}
2655 };
2656 // }}}
2657
2658 // error correction {{{
2659 // Taken form http://www.netzwolf.info/j/osm/time_domain.js
2660 // Credits go to Netzwolf
2661 //
2662 // Key to word_error_correction is the token name except wrong_words
2663 var word_error_correction = {
2664 wrong_words: { /* {{{ */
2665 'Assuming "<ok>" for "<ko>".': {
2666 'Frühling': 'Mar-May',
2667 'Frühjahr': 'Mar-May',
2668 'Sommer': 'Jun-Aug',
2669 'Herbst': 'Sep-Nov',
2670 'winter': 'Dec-Feb',
2671 }, '"<ko>" wird als "<ok>" interpertiert.': {
2672 'spring': 'Mar-May',
2673 'summer': 'Jun-Aug',
2674 'autumn': 'Sep-Nov',
2675 // 'winter': 'Dec-Feb', // Same as in English.
2676 // morning: '08:00-12:00',
2677 // evening: '13:00-18:00',
2678 '_': '-',
2679 'daytime': 'sunrise-sunset',
2680 }, 'Bitte benutze die englische Schreibweise "<ok>" für "<ko>".': {
2681 'sommer': 'summer',
2682 'werktag': 'Mo-Fr',
2683 'werktags': 'Mo-Fr',
2684 }, 'Bitte benutze "<ok>" für "<ko>". Beispiel: "Mo-Fr 08:00-12:00; Tu off".': {
2685 'ruhetag': 'off',
2686 'ruhetage': 'off',
2687 'geschlossen': 'off',
2688 'geschl': 'off',
2689 // 'ausser': 'off',
2690 // 'außer': 'off',
2691 }, 'Neem de engelse afkorting "<ok>" voor "<ko>" alstublieft.': {
2692 'gesloten': 'off',
2693 'feestdag': 'PH',
2694 'feestdagen': 'PH',
2695 }, 'Assuming "<ok>" for "<ko>". Please avoid using "workday": http://wiki.openstreetmap.org/wiki/Talk:Key:opening_hours#need_syntax_for_holidays_and_workingdays': {
2696 // Used around 260 times but the problem is, that work day might be different in other countries.
2697 'wd': 'Mo-Fr',
2698 'on work day': 'Mo-Fr',
2699 'on work days': 'Mo-Fr',
2700 'weekday': 'Mo-Fr',
2701 'weekdays': 'Mo-Fr',
2702 'vardagar': 'Mo-Fr',
2703 }, 'Please use something like "Mo off" instead "<ko>".': {
2704 'except': 'off',
2705 }, 'Please omit "<ko>" or use a colon instead: "12:00-14:00".': {
2706 'h': '',
2707 }, 'Please omit "<ko>".': {
2708 'season': '',
2709 'hs': '',
2710 'hrs': '',
2711 'hours': '',
2712 '·': '',
2713 }, 'Please omit "<ko>". The key must not be in the value.': {
2714 'opening_hours=': '',
2715 }, 'Please omit "<ko>". You might want to express open end which can be specified as "12:00+" for example.': {
2716 'from': '',
2717 }, 'You can use notation "<ok>" for "<ko>" in the case that you want to express open end times. Example: "12:00+".': {
2718 'til late': '+',
2719 'till late': '+',
2720 'bis open end': '+',
2721 '-late': '+',
2722 '-open end': '+',
2723 '-openend': '+',
2724 }, '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".': {
2725 '~': '-',
2726 '~': '-',
2727 }, 'Please use notation "<ok>" for "<ko>". Fallback rule: 12:00-14:00 || "call us"': {
2728 'otherwise': '||',
2729 }, 'You can use notation "<ok>" for "<ko>" temporally if the syntax will still be valid.': {
2730 '?': 'unknown "please add this if known"',
2731 }, 'Please use notation "<ok>" for "<ko>". Although using "–" is typographical correct, the opening_hours syntax is defined with the normal hyphen. Correct typography should be done on application level …': {
2732 '–': '-',
2733 }, 'Please use notation "<ok>" for "<ko>".': {
2734 '→': '-',
2735 '−': '-',
2736 '=': '-',
2737 'ー': '-',
2738 'to': '-',
2739 'до': '-',
2740 'a': '-', // language unknown
2741 'as': '-', // language unknown
2742 'á': '-', // language unknown
2743 'ás': '-', // language unknown
2744 'às': '-', // language unknown
2745 'ate': '-', // language unknown
2746 'till': '-',
2747 'til': '-',
2748 'until': '-',
2749 'through': '-',
2750 'and': ',',
2751 '&': ',',
2752 // '/': ',', // Can not be corrected as / is a valid token
2753 ':': ':',
2754 '°°': ':00',
2755 'always': '24/7',
2756 'nonstop': '24/7',
2757 '24x7': '24/7',
2758 'anytime': '24/7',
2759 'all day': '24/7',
2760 'daily': 'Mo-Su',
2761 'everyday': 'Mo-Su',
2762 'every day': 'Mo-Su',
2763 'all days': 'Mo-Su',
2764 '7j/7': 'Mo-Su', // I guess that it means that
2765 '7/7': 'Mo-Su', // I guess that it means that
2766 /* {{{
2767 * Fixing this causes to ignore the following warning: "There should be no
2768 * reason to differ more than 6 days from a constrained
2769 * weekdays. If so tell us …".
2770 * The following mistake is expected to occur more often.
2771 */
2772 '7days': 'Mo-Su',
2773 '7 days': 'Mo-Su',
2774 // }}}
2775 '7 days a week': 'Mo-Su',
2776 '7 days/week': 'Mo-Su',
2777 '24 hours 7 days a week': '24/7',
2778 '24 hours': '00:00-24:00',
2779 'midday': '12:00',
2780 'midnight': '00:00',
2781 'holiday': 'PH',
2782 'holidays': 'PH',
2783 'public holidays': 'PH',
2784 'public holiday': 'PH',
2785 'day after public holiday': 'PH +1 day',
2786 'one day after public holiday': 'PH +1 day',
2787 'day before public holiday': 'PH -1 day',
2788 'one day before public holiday': 'PH -1 day',
2789 'school holiday': 'SH',
2790 'school holidays': 'SH',
2791 // summerholiday: 'SH',
2792 // summerholidays: 'SH',
2793 /* Not implemented {{{ */
2794 // 'day after school holiday': 'SH +1 day',
2795 // 'one day after school holiday': 'SH +1 day',
2796 // 'day before school holiday': 'SH -1 day',
2797 // 'one day before school holiday': 'SH -1 day',
2798 /* }}} */
2799 'weekend': 'Sa,Su',
2800 'weekends': 'Sa,Su',
2801 'daylight': 'sunrise-sunset',
2802 }, 'Please use notation "<ok>" for "<ko>". Those characters look very similar but are not the same!': {
2803 'оff': 'off', // Russian o
2804 }, '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.': {
2805 'pm': '',
2806 'рм': '',
2807 'am': '',
2808 'ам': '',
2809 }, 'Bitte verzichte auf "<ko>".': {
2810 'uhr': '',
2811 'geöffnet': '',
2812 'zwischen': '',
2813 'ist': '',
2814 'durchgehend': '',
2815 }, 'Bitte verzichte auf "<ko>". Sie möchten eventuell eine Öffnungszeit ohne vorgegebenes Ende (Open End) angeben. Beispiel: "12:00+"': {
2816 'ab': '',
2817 'von': '',
2818 }, 'Es sieht so aus also möchten Sie zusätzliche Einschränkungen für eine Öffnungszeit geben. Falls sich dies nicht mit der Syntax ausdrücken lässt können Kommentare verwendet werden. Zusätzlich sollte eventuell das Schlüsselwort `open` benutzt werden. Bitte probiere "<ok>" für "<ko>".': {
2819 'damen': 'open "Damen"',
2820 'herren': 'open "Herren"',
2821 }, 'Bitte benutze die Schreibweise "<ok>" für "<ko>".': {
2822 'bis': '-',
2823 'täglich': 'Mo-Su',
2824 'schulferien': 'SH',
2825 'sonn und feiertag': 'PH,Su',
2826 'sonn und feiertags': 'PH,Su',
2827 'sonn- und feiertags': 'PH,Su',
2828 'sonn-/feiertag': 'PH,Su',
2829 'sonn-/feiertags': 'PH,Su',
2830 'an sonn- und feiertagen': 'PH,Su',
2831 'nur sonn-/feiertags': 'PH,Su',
2832 'sonn- und feiertage': 'PH,Su',
2833 }, 'Bitte benutze die Schreibweise "<ok>" für "<ko>". Es ist war typografisch korrekt aber laut der Spezifikation für opening_hours nicht erlaubt. Siehe auch: http://wiki.openstreetmap.org/wiki/DE:Key:opening_hours/specification.': {
2834 '„': '"',
2835 '“': '"',
2836 '”': '"',
2837 }, 'Please use notation "<ok>" for "<ko>". The used quote signs might be typographically correct but are not defined in the specification. See http://wiki.openstreetmap.org/wiki/Key:opening_hours/specification.': {
2838 '«': '"',
2839 '»': '"',
2840 '‚': '"',
2841 '‘': '"',
2842 '’': '"',
2843 '「': '"',
2844 '」': '"',
2845 '『': '"',
2846 '』': '"',
2847 }, 'Please use notation "<ok>" for "<ko>". The used quote signs are not defined in the specification. See http://wiki.openstreetmap.org/wiki/Key:opening_hours/specification.': {
2848 "'": '"',
2849 }, 'You might want to use comments instead of brackets (which are not valid in this context). If you do, replace "<ok>" with "<ko>".': {
2850 // '(': '"',
2851 // ')': '"',
2852 }, 'Bitte benutze die Schreibweise "<ok>" als Ersatz für "und" bzw. "u.".': {
2853 'und': ',',
2854 'u': ',',
2855 }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>".': {
2856 'feiertag': 'PH',
2857 'feiertags': 'PH',
2858 'feiertage': 'PH',
2859 'feiertagen': 'PH'
2860 }, 'S\'il vous plaît utiliser "<ok>" pour "<ko>".': {
2861 'fermé': 'off',
2862 'et': ',',
2863 'à': '-',
2864 'jours fériés': 'PH',
2865 }
2866 }, /* }}} */
2867
2868 month: { /* {{{ */
2869 'default': {
2870 'jan': 0,
2871 'feb': 1,
2872 'mar': 2,
2873 'apr': 3,
2874 'may': 4,
2875 'jun': 5,
2876 'jul': 6,
2877 'aug': 7,
2878 'sep': 8,
2879 'oct': 9,
2880 'nov': 10,
2881 'dec': 11,
2882 }, 'Please use the English abbreviation "<ok>" for "<ko>".': {
2883 'jänner': 0, // Austria
2884 'january': 0,
2885 'february': 1,
2886 'march': 2,
2887 'april': 3,
2888 // 'may': 4,
2889 'june': 5,
2890 'july': 6,
2891 'august': 7,
2892 'september': 8,
2893 'sept': 8,
2894 'october': 9,
2895 'november': 10,
2896 'december': 11,
2897 }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>".': {
2898 'januar': 0,
2899 'februar': 1,
2900 'märz': 2,
2901 'maerz': 2,
2902 'mai': 4,
2903 'juni': 5,
2904 'juli': 6,
2905 'okt': 9,
2906 'oktober': 9,
2907 'dez': 11,
2908 'dezember': 11,
2909 }, 'S\'il vous plaît utiliser l\'abréviation "<ok>" pour "<ko>".': {
2910 'janvier': 0,
2911 'février': 1,
2912 'fév': 1,
2913 'mars': 2,
2914 'avril': 3,
2915 'avr': 3,
2916 'mai': 4,
2917 'juin': 5,
2918 'juillet': 6,
2919 'août': 7,
2920 'aoû': 7,
2921 'septembre': 8,
2922 'octobre': 9,
2923 'novembre': 10,
2924 'décembre': 11,
2925 }, 'Neem de engelse afkorting "<ok>" voor "<ko>" alstublieft.': {
2926 'januari': 0,
2927 'februari': 1,
2928 'maart': 2,
2929 'mei': 4,
2930 'augustus': 7,
2931 }
2932 }, /* }}} */
2933
2934 calcday: {
2935 'default': {
2936 'day': 'day',
2937 'days': 'days',
2938 },
2939 },
2940
2941 weekday: { // {{{ Good source: http://www.omniglot.com/language/time/days.htm */
2942 'default': {
2943 'su': 0,
2944 'mo': 1,
2945 'tu': 2,
2946 'we': 3,
2947 'th': 4,
2948 'fr': 5,
2949 'sa': 6,
2950 }, 'Assuming "<ok>" for "<ko>"': {
2951 'm': 1,
2952 'w': 3,
2953 'f': 5,
2954 }, 'Please use the abbreviation "<ok>" for "<ko>".': {
2955 'sun': 0,
2956 'sunday': 0,
2957 'sundays': 0,
2958 'mon': 1,
2959 'monday': 1,
2960 'mondays': 1,
2961 'tue': 2,
2962 'tues': 2, // Used here: http://www.westerhambeauty.co.uk/contact.php
2963 'tuesday': 2,
2964 'tuesdays': 2,
2965 'wed': 3,
2966 'weds': 3,
2967 'wednesday': 3,
2968 'wednesdays': 3,
2969 'thu': 4,
2970 'thur': 4,
2971 'thurs': 4,
2972 'thursday': 4,
2973 'thursdays': 4,
2974 'fri': 5,
2975 'friday': 5,
2976 'fridays': 5,
2977 'sat': 6,
2978 'saturday': 6,
2979 'saturdays': 6,
2980 }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>". Could also mean Saturday in Polish …': {
2981 'so': 0,
2982 }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>".': {
2983 'son': 0,
2984 'sonntag': 0,
2985 'sonn-': 0,
2986 'sonntags': 0,
2987 'montag': 1,
2988 'montags': 1,
2989 'di': 2,
2990 'die': 2,
2991 'dienstag': 2,
2992 'dienstags': 2,
2993 'mi': 3,
2994 'mit': 3,
2995 'mittwoch': 3,
2996 'mittwochs': 3,
2997 'do': 4,
2998 'don': 4,
2999 'donnerstag': 4,
3000 'donnerstags': 4,
3001 'fre': 5,
3002 'freitag': 5,
3003 'freitags': 5,
3004 'sam': 6,
3005 'samstag': 6,
3006 'samstags': 6,
3007 }, 'S\'il vous plaît utiliser l\'abréviation "<ok>" pour "<ko>".': {
3008 'dim': 0,
3009 'dimanche': 0,
3010 'lu': 1,
3011 'lun': 1,
3012 'lundi': 1,
3013 'mardi': 2,
3014 'mer': 3,
3015 'mercredi': 3,
3016 'je': 4,
3017 'jeu': 4,
3018 'jeudi': 4,
3019 've': 5,
3020 'ven': 5,
3021 'vendredi': 5,
3022 'samedi': 6,
3023 }, 'Neem de engelse afkorting "<ok>" voor "<ko>" alstublieft.': {
3024 'zo': 0,
3025 'zon': 0,
3026 'zontag': 0, // correct?
3027 'zondag': 0,
3028 'maandag': 1,
3029 'din': 2,
3030 'dinsdag': 2,
3031 'wo': 3,
3032 'woe': 3,
3033 'woensdag': 3,
3034 'donderdag': 4,
3035 'vr': 5,
3036 'vri': 5,
3037 'vrijdag': 5,
3038 'za': 6,
3039 'zat': 6,
3040 'zaterdag': 6,
3041 }, 'Please use the English abbreviation "<ok>" for "<ko>".': { // FIXME: Translate to Czech.
3042 'neděle': 0,
3043 'ne': 0,
3044 'pondělí': 1,
3045 'po': 1,
3046 'úterý': 2,
3047 'út': 2,
3048 'středa': 3,
3049 'st': 3,
3050 'čtvrtek': 4,
3051 'čt': 4,
3052 'pátek': 5,
3053 'pá': 5,
3054 'sobota': 6,
3055 }, 'Please use the English abbreviation "<ok>" (Spanish) for "<ko>".': {
3056 'martes': 0,
3057 'miércoles': 1,
3058 'jueves': 2,
3059 'viernes': 3,
3060 'sábado': 4,
3061 'domingo': 5,
3062 'lunes': 6,
3063 }, 'Please use the English abbreviation "<ok>" (Indonesian) for "<ko>".': {
3064 'selasa': 0,
3065 'rabu': 1,
3066 'kami': 2,
3067 'jumat': 3,
3068 'sabtu': 4,
3069 'minggu': 5,
3070 'senin': 6,
3071 }, 'Please use the English abbreviation "<ok>" (Swedish) for "<ko>".': {
3072 'söndag': 0,
3073 'söndagar': 0,
3074 'måndag': 1,
3075 'ma': 1,
3076 'tisdag': 2,
3077 'onsdag': 3, // Same in Danish
3078 'torsdag': 4,
3079 'fredag': 5,
3080 'lördag': 6,
3081 'lördagar': 6,
3082 }, 'Please use the English abbreviation "<ok>" (Polish) for "<ko>".': {
3083 'niedziela': 0, 'niedz': 0, 'n': 0, 'ndz': 0,
3084 'poniedziałek': 1, 'poniedzialek': 1, 'pon': 1, 'pn': 1,
3085 'wtorek': 2, 'wt': 2,
3086 'środa': 3, 'sroda': 3, 'śr': 3, 'sr': 3,
3087 'czwartek': 4, 'czw': 4, 'cz': 4,
3088 'piątek': 5, 'piatek': 5, 'pt': 5,
3089 'sobota': 6, 'sob': 6, // 'so': 6 // abbreviation also used in German
3090 }, 'Please use the English abbreviation "<ok>" (Russian) for "<ko>".': {
3091 'воскресенье' : 0,
3092 'Вс' : 0,
3093 "voskresen'ye": 0,
3094 'понедельник' : 1,
3095 'Пн' : 1,
3096 "ponedel'nik" : 1,
3097 'вторник' : 2,
3098 'vtornik' : 2,
3099 'среда' : 3,
3100 'sreda' : 3,
3101 'четверг' : 4,
3102 'chetverk' : 4,
3103 'пятница' : 5,
3104 'pyatnitsa' : 5,
3105 'суббота' : 6,
3106 'subbota' : 6,
3107 }, 'Please use the English abbreviation "<ok>" (Danish) for "<ko>".': {
3108 'søndag' : 0,
3109 'mandag' : 1,
3110 'tirsdag': 2,
3111 'onsdag' : 3, // Same in Swedish
3112 'torsdag': 4,
3113 'fredag' : 5,
3114 'lørdag' : 6,
3115 },
3116 }, /* }}} */
3117
3118 timevar: { /* {{{ Special time variables which actual value depends on the date and the position of the facility. */
3119 'default': {
3120 'sunrise': 'sunrise',
3121 'sunset': 'sunset',
3122 'dawn': 'dawn',
3123 'dusk': 'dusk',
3124 }, 'Please use notation "<ok>" for "<ko>".': {
3125 'sundown': 'sunset',
3126 }, 'Bitte benutze die Schreibweise "<ok>" für "<ko>".': {
3127 'morgendämmerung': 'dawn',
3128 'abenddämmerung': 'dusk',
3129 'sonnenaufgang': 'sunrise',
3130 'sonnenuntergang': 'sunset',
3131 },
3132 }, /* }}} */
3133
3134 'event': { // variable events
3135 'default': {
3136 'easter': 'easter',
3137 }, 'Bitte benutze die Schreibweise "<ok>" für "<ko>".': {
3138 'ostern': 'easter',
3139 },
3140 },
3141 };
3142 // }}}
3143 // }}}
3144
3145 // make the library accessible for the outside world {{{
3146 if (typeof exports === 'object') {
3147 // For nodejs
3148 var SunCalc = require('suncalc');
3149 module.exports = factory(SunCalc, holidays, word_error_correction);
3150 } else {
3151 // For browsers
3152 root.opening_hours = factory(root.SunCalc, holidays, word_error_correction);
3153 }
3154 /// }}}
3155}(this, function (SunCalc, holidays, word_error_correction) {
3156 return function(value, nominatiomJSON, oh_mode) {
3157 // short constants {{{
3158 var word_value_replacement = { // If the correct values can not be calculated.
3159 dawn : 60 * 5 + 30,
3160 sunrise : 60 * 6,
3161 sunset : 60 * 18,
3162 dusk : 60 * 18 + 30,
3163 };
3164 var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
3165 var weekdays = ['Su','Mo','Tu','We','Th','Fr','Sa'];
3166 var default_prettify_conf = {
3167 // Update README.md if changed.
3168 'zero_pad_hour': true, // enforce ("%02d", hour)
3169 'one_zero_if_hour_zero': false, // only one zero "0" if hour is zero "0"
3170 'leave_off_closed': true, // leave keywords "off" and "closed" as is
3171 'keyword_for_off_closed': 'off', // use given keyword instead of "off" or "closed"
3172 'rule_sep_string': ' ', // separate rules by string
3173 'print_semicolon': true, // print token which separates normal rules
3174 '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
3175 'sep_one_day_between': ',', // separator which should be used
3176 'zero_pad_month_and_week_numbers': false, // Format week (e.g. `week 01`) and month day numbers (e.g. `Jan 01`) with "%02d".
3177 };
3178
3179 var minutes_in_day = 60 * 24;
3180 var msec_in_day = 1000 * 60 * minutes_in_day;
3181 var msec_in_week = msec_in_day * 7;
3182
3183 var library_name = 'opening_hours.js';
3184 var repository_url = 'https://github.com/ypid/' + library_name;
3185 var issues_url = repository_url + '/issues?state=open';
3186 // }}}
3187
3188 // constructor parameters {{{
3189 // Evaluate additional information which can be given. They are
3190 // required to reasonably calculate 'sunrise' and to use the correct
3191 // holidays.
3192 var location_cc, location_state, lat, lon;
3193 if (typeof nominatiomJSON !== 'undefined') {
3194 if (typeof nominatiomJSON.address !== 'undefined') {
3195 if (typeof nominatiomJSON.address.country_code !== 'undefined') {
3196 location_cc = nominatiomJSON.address.country_code;
3197 }
3198 if (typeof nominatiomJSON.address.state !== 'undefined') {
3199 location_state = nominatiomJSON.address.state;
3200 } else if (typeof nominatiomJSON.address.county !== 'undefined') {
3201 location_state = nominatiomJSON.address.county;
3202 }
3203 }
3204
3205 if (typeof nominatiomJSON.lon !== 'undefined') { // lat will be tested later …
3206 lat = nominatiomJSON.lat;
3207 lon = nominatiomJSON.lon;
3208 }
3209 }
3210
3211 // 0: time ranges (default), tags: opening_hours, lit, …
3212 // 1: points in time
3213 // 2: both (time ranges and points in time), tags: collection_times, service_times
3214 if (typeof oh_mode === 'undefined') {
3215 oh_mode = 0;
3216 } else if (!(typeof oh_mode === 'number' && (oh_mode === 0 || oh_mode === 1 || oh_mode === 2))) {
3217 throw 'The third constructor parameter is oh_mode and must be a number (0, 1 or 2)';
3218 }
3219 // }}}
3220
3221 // Tokenize value and generate selector functions. {{{
3222 if (value.match(/^(?:\s*;?\s*)+$/))
3223 throw 'Value contains nothing meaningful which can be parsed';
3224
3225 var parsing_warnings = []; // Elements are fed into function formatWarnErrorMessage(nrule, at, message)
3226 var done_with_warnings = false; // The functions which throw warnings can be called multiple times.
3227 var done_with_selector_reordering = false;
3228 var done_with_selector_reordering_warnings = false;
3229 var tokens = tokenize(value);
3230 // console.log(JSON.stringify(tokens, null, ' '));
3231 var prettified_value = '';
3232 var week_stable = true;
3233
3234 var rules = [];
3235 var new_tokens = [];
3236
3237 for (var nrule = 0; nrule < tokens.length; nrule++) {
3238 if (tokens[nrule][0].length === 0) {
3239 // Rule does contain nothing useful e.g. second rule of '10:00-12:00;' (empty) which needs to be handled.
3240 parsing_warnings.push([nrule, -1,
3241 'This rule does not contain anything useful. Please remove this empty rule.'
3242 + (nrule === tokens.length - 1 && nrule > 0 && !tokens[nrule][1] ?
3243 ' Might it be possible that you are a programmer and adding a semicolon after each statement is hardwired in your muscle memory ;) ?'
3244 + ' The thing is that the semicolon in the opening_hours syntax is defined as rule separator.'
3245 + ' So for compatibility reasons you should omit this last semicolon.': '')
3246 ]);
3247 continue;
3248 }
3249
3250 var continue_at = 0;
3251 var next_rule_is_additional = false;
3252 do {
3253 if (continue_at === tokens[nrule][0].length) break;
3254 // Additional rule does contain nothing useful e.g. second rule of '10:00-12:00,' (empty) which needs to be handled.
3255
3256 var selectors = {
3257 // Time selectors
3258 time: [],
3259
3260 // Temporary array of selectors from time wrapped to the next day
3261 wraptime: [],
3262
3263 // Date selectors
3264 weekday: [],
3265 holiday: [],
3266 week: [],
3267 month: [],
3268 monthday: [],
3269 year: [],
3270
3271 // Array with non-empty date selector types, with most optimal ordering
3272 date: [],
3273
3274 fallback: tokens[nrule][1],
3275 additional: continue_at ? true : false,
3276 meaning: true,
3277 unknown: false,
3278 comment: undefined,
3279 build_from_token_rule: undefined,
3280 };
3281
3282 selectors.build_from_token_rule = [ nrule, continue_at, new_tokens.length ];
3283 continue_at = parseGroup(tokens[nrule][0], continue_at, selectors, nrule);
3284 if (typeof continue_at === 'object') {
3285 continue_at = continue_at[0];
3286 } else {
3287 continue_at = 0;
3288 }
3289
3290 // console.log('Current tokens: ' + JSON.stringify(tokens[nrule], null, ' '));
3291
3292 new_tokens.push(
3293 [
3294 tokens[nrule][0].slice(
3295 selectors.build_from_token_rule[1],
3296 continue_at === 0
3297 ? tokens[nrule][0].length
3298 : continue_at
3299 ),
3300 tokens[nrule][1],
3301 tokens[nrule][2],
3302 ]
3303 );
3304
3305 if (next_rule_is_additional && new_tokens.length > 1) {
3306 // Move 'rule separator' from last token of last rule to first token of this rule.
3307 new_tokens[new_tokens.length - 1][0].unshift(new_tokens[new_tokens.length - 2][0].pop());
3308 }
3309
3310 next_rule_is_additional = continue_at === 0 ? false : true;
3311
3312 if (selectors.year.length > 0)
3313 selectors.date.push(selectors.year);
3314 if (selectors.holiday.length > 0)
3315 selectors.date.push(selectors.holiday);
3316 if (selectors.month.length > 0)
3317 selectors.date.push(selectors.month);
3318 if (selectors.monthday.length > 0)
3319 selectors.date.push(selectors.monthday);
3320 if (selectors.week.length > 0)
3321 selectors.date.push(selectors.week);
3322 if (selectors.weekday.length > 0)
3323 selectors.date.push(selectors.weekday);
3324
3325 // console.log('weekday: ' + JSON.stringify(selectors.weekday, null, '\t'));
3326 rules.push(selectors);
3327
3328 // This handles selectors with time ranges wrapping over midnight (e.g. 10:00-02:00)
3329 // it generates wrappers for all selectors and creates a new rule.
3330 if (selectors.wraptime.length > 0) {
3331 var wrapselectors = {
3332 time: selectors.wraptime,
3333 date: [],
3334
3335 meaning: selectors.meaning,
3336 unknown: selectors.unknown,
3337 comment: selectors.comment,
3338
3339 wrapped: true,
3340 build_from_token_rule: selectors.build_from_token_rule,
3341 };
3342
3343 for (var dselg = 0; dselg < selectors.date.length; dselg++) {
3344 wrapselectors.date.push([]);
3345 for (var dsel = 0; dsel < selectors.date[dselg].length; dsel++) {
3346 wrapselectors.date[wrapselectors.date.length-1].push(
3347 generateDateShifter(selectors.date[dselg][dsel], -msec_in_day)
3348 );
3349 }
3350 }
3351
3352 rules.push(wrapselectors);
3353 }
3354 } while (continue_at);
3355 }
3356 // console.log(JSON.stringify(tokens, null, ' '));
3357 // console.log(JSON.stringify(new_tokens, null, ' '));
3358 // }}}
3359
3360 /* Format warning or error message for the user. {{{
3361 *
3362 * :param nrule: Rule number starting with zero.
3363 * :param at: Token position at which the issue occurred.
3364 * :param message: Human readable string with the message.
3365 * :returns: String with position of the warning or error marked for the user.
3366 */
3367 function formatWarnErrorMessage(nrule, at, message) {
3368 // FIXME: Change to new_tokens.
3369 if (typeof nrule === 'number') {
3370 var pos = 0;
3371 if (nrule === -1) { // Usage of rule index not required because we do have access to value.length.
3372 pos = value.length - at;
3373 } else { // Issue accrued at a later time, position in string needs to be reconstructed.
3374 if (typeof tokens[nrule][0][at] === 'undefined') {
3375 if (typeof tokens[nrule][0] && at === -1) {
3376 pos = value.length;
3377 if (typeof tokens[nrule+1] === 'object' && typeof tokens[nrule+1][2] === 'number') {
3378 pos -= tokens[nrule+1][2];
3379 } else if (typeof tokens[nrule][2] === 'number') {
3380 pos -= tokens[nrule][2];
3381 }
3382 } else {
3383 // Given position is invalid.
3384 //
3385 formatLibraryBugMessage('Bug in warning generation code which could not determine the exact position of the warning or error in value.');
3386 pos = value.length;
3387 if (typeof tokens[nrule][2] !== 'undefined') {
3388 // Fallback: Point to last token in the rule which caused the problem.
3389 // Run real_test regularly to fix the problem before a user is confronted with it.
3390 pos -= tokens[nrule][2];
3391 console.warn('Last token for rule: ' + tokens[nrule]);
3392 console.log(value.substring(0, pos) + ' <--- (' + message + ')');
3393 console.log('\n');
3394 } {
3395 console.warn('tokens[nrule][2] is undefined. This is ok if nrule is the last rule.');
3396 }
3397 }
3398 } else {
3399 pos = value.length;
3400 if (typeof tokens[nrule][0][at+1] !== 'undefined') {
3401 pos -= tokens[nrule][0][at+1][2];
3402 } else if (typeof tokens[nrule][2] !== 'undefined') {
3403 pos -= tokens[nrule][2];
3404 }
3405 }
3406 }
3407 return value.substring(0, pos) + ' <--- (' + message + ')';
3408 } else if (typeof nrule === 'string') {
3409 return nrule.substring(0, at) + ' <--- (' + message + ')';
3410 }
3411 }
3412 // }}}
3413
3414 /* Format internal library error message. {{{
3415 *
3416 * :param message: Human readable string with the error message.
3417 * :returns: Error message for the user.
3418 */
3419 function formatLibraryBugMessage(message) {
3420 if (typeof message === 'undefined')
3421 message = '';
3422 else
3423 message = ' ' + message;
3424
3425 message = 'An error occurred during evaluation of the value "' + value + '".'
3426 + ' Please file a bug report here: ' + issues_url + '.'
3427 + message;
3428 console.log(message);
3429 return message;
3430 }
3431 // }}}
3432
3433 /* Tokenize input stream. {{{
3434 *
3435 * :param value: Raw opening_hours value.
3436 * :returns: Tokenized list object. Complex structure. Check the
3437 * internal documentation in the doc directory for details.
3438 */
3439 function tokenize(value) {
3440 var all_tokens = [];
3441 var curr_rule_tokens = [];
3442
3443 var last_rule_fallback_terminated = false;
3444
3445 while (value !== '') {
3446 // console.log("Parsing value: " + value);
3447 var tmp;
3448 if (tmp = value.match(/^week\b/i)) {
3449 // Reserved keywords.
3450 curr_rule_tokens.push([tmp[0].toLowerCase(), tmp[0].toLowerCase(), value.length ]);
3451 value = value.substr(tmp[0].length);
3452 } else if (tmp = value.match(/^(?:off\b|closed\b|open\b|unknown\b)/i)) {
3453 // Reserved keywords.
3454 curr_rule_tokens.push([tmp[0].toLowerCase(), 'state', value.length ]);
3455 value = value.substr(tmp[0].length);
3456 } else if (tmp = value.match(/^24\/7/i)) {
3457 // Reserved keyword.
3458 curr_rule_tokens.push([tmp[0], tmp[0], value.length ]);
3459 value = value.substr(tmp[0].length);
3460 } else if (tmp = value.match(/^(?:PH|SH)/i)) {
3461 // special day name (holidays)
3462 curr_rule_tokens.push([tmp[0].toUpperCase(), 'holiday', value.length ]);
3463 value = value.substr(2);
3464 } else if (tmp = value.match(/^(&|_|→|–|−|=|·|opening_hours=|ー|\?|~|~|:|°°|24x7|24 hours 7 days a week|24 hours|7 ?days(?:(?: a |\/)week)?|7j?\/7|all days?|every day|(?:|-|till? |bis )?(?:late|open[ ]?end)|(?:(?:one )?day (?:before|after) )?(?:school|public) holidays?|days?\b|до|рм|ам|jours fériés|on work days?|sonntag?|(?:nur |an )?sonn-?(?:(?: und |\/)feiertag(?:s|en?)?)?|[a-zäößàáéøčěíúýřПнВсо]+\b|à|á|mo|tu|we|th|fr|sa|su|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\.?/i)) {
3465 /* Handle all remaining words and specific other characters with error tolerance.
3466 *
3467 * à|á: Word boundary does not work with unicode chars: 'test à test'.match(/\bà\b/i)
3468 * https://stackoverflow.com/questions/10590098/javascript-regexp-word-boundaries-unicode-characters
3469 * Order in the regular expression capturing group is important in some cases.
3470 *
3471 * mo|tu|we|th|fr|sa|su|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec: Prefer defended keywords
3472 * if used in cases like 'mo12:00-14:00' (when keyword is followed by number).
3473 */
3474 var correct_val = returnCorrectWordOrToken(tmp[1].toLowerCase(), value.length);
3475 // console.log('Error tolerance for string "' + tmp[1] + '" returned "' + correct_val + '".');
3476 if (typeof correct_val === 'object') {
3477 curr_rule_tokens.push([ correct_val[0], correct_val[1], value.length ]);
3478 value = value.substr(tmp[0].length);
3479 } else if (typeof correct_val === 'string') {
3480 if (tmp[1].toLowerCase() === 'pm') {
3481 var hours_token_at = curr_rule_tokens.length - 1;
3482 var hours_token;
3483 if (hours_token_at >= 0) {
3484 if (hours_token_at -2 >= 0 &&
3485 matchTokens(
3486 curr_rule_tokens, hours_token_at - 2,
3487 'number', 'timesep', 'number'
3488 )
3489 ) {
3490 hours_token_at -= 2;
3491 hours_token = curr_rule_tokens[hours_token_at];
3492 } else if (matchTokens(curr_rule_tokens, hours_token_at, 'number')) {
3493 hours_token = curr_rule_tokens[hours_token_at];
3494 }
3495
3496 if (typeof hours_token === 'object' && hours_token[0] <= 12) {
3497 hours_token[0] += 12;
3498 curr_rule_tokens[hours_token_at] = hours_token;
3499 }
3500 }
3501 }
3502 var correct_tokens = tokenize(correct_val)[0];
3503 if (correct_tokens[1] === true) { // last_rule_fallback_terminated
3504 throw formatLibraryBugMessage();
3505 }
3506 for (var i = 0; i < correct_tokens[0].length; i++) {
3507 curr_rule_tokens.push([correct_tokens[0][i][0], correct_tokens[0][i][1], value.length]);
3508 // value.length - tmp[0].length does not have the desired effect for all test cases.
3509 }
3510
3511 value = value.substr(tmp[0].length);
3512 // value = correct_val + value.substr(tmp[0].length);
3513 // Does not work because it would generate the wrong length for formatWarnErrorMessage.
3514 } else {
3515 // other single-character tokens
3516 curr_rule_tokens.push([value[0].toLowerCase(), value[0].toLowerCase(), value.length - 1 ]);
3517 value = value.substr(1);
3518 }
3519 } else if (tmp = value.match(/^\d+/)) {
3520 // number
3521 if (Number(tmp[0]) > 1900) { // Assumed to be a year number.
3522 curr_rule_tokens.push([Number(tmp[0]), 'year', value.length ]);
3523 if (Number(tmp[0]) >= 2100) // Probably an error
3524 parsing_warnings.push([ -1, value.length - 1,
3525 'The number ' + Number(tmp[0]) + ' will be interpreted as year.'
3526 + ' This is probably not intended. Times can be specified as "12:00".'
3527 ]);
3528 } else {
3529 curr_rule_tokens.push([Number(tmp[0]), 'number', value.length ]);
3530 }
3531
3532 value = value.substr(tmp[0].length);
3533 } else if (tmp = value.match(/^"([^"]+)"/)) {
3534 // Comment following the specification.
3535 // Any character is allowed inside the comment except " itself.
3536 curr_rule_tokens.push([tmp[1], 'comment', value.length ]);
3537 value = value.substr(tmp[0].length);
3538 } else if (tmp = value.match(/^(["'„“‚‘’«「『])([^"'“”‘’»」』;|]*)(["'”“‘’»」』])/)) {
3539 // Comments with error tolerance.
3540 // The comments still have to be somewhat correct meaning
3541 // the start and end quote signs used have to be
3542 // appropriate. So “testing„ will not match as it is not a
3543 // quote but rather something unknown which the user should
3544 // fix first.
3545 // console.log('Matched: ' + JSON.stringify(tmp));
3546 for (var pos = 1; pos <= 3; pos += 2) {
3547 // console.log('Pos: ' + pos + ', substring: ' + tmp[pos]);
3548 var correct_val = returnCorrectWordOrToken(tmp[pos],
3549 value.length - (pos === 3 ? tmp[1].length + tmp[2].length : 0)
3550 );
3551 if (typeof correct_val !== 'string' && tmp[pos] !== '"') {
3552 throw formatLibraryBugMessage(
3553 'A character for error tolerance was allowed in the regular expression'
3554 + ' but is not covered by word_error_correction'
3555 + ' which is needed to format a proper message for the user.'
3556 );
3557 }
3558 }
3559 curr_rule_tokens.push([tmp[2], 'comment', value.length ]);
3560 value = value.substr(tmp[0].length);
3561 } else if (value.match(/^;/)) {
3562 // semicolon terminates rule
3563 // next tokens belong to a new rule
3564 all_tokens.push([ curr_rule_tokens, last_rule_fallback_terminated, value.length ]);
3565 value = value.substr(1);
3566
3567 curr_rule_tokens = [];
3568 last_rule_fallback_terminated = false;
3569 } else if (value.match(/^\|\|/)) {
3570 // || terminates rule
3571 // Next tokens belong to a fallback rule.
3572 if (curr_rule_tokens.length === 0)
3573 throw formatWarnErrorMessage(-1, value.length - 2, 'Rule before fallback rule does not contain anything useful');
3574
3575 all_tokens.push([ curr_rule_tokens, last_rule_fallback_terminated, value.length ]);
3576 curr_rule_tokens = [];
3577 // curr_rule_tokens = [ [ '||', 'rule separator', value.length ] ];
3578 // FIXME: Use this. Unknown bug needs to be solved in the process.
3579 value = value.substr(2);
3580
3581 last_rule_fallback_terminated = true;
3582 } else if (value.match(/^(?:␣|\s)/)) {
3583 // Using "␣" as space is not expected to be a normal
3584 // mistake. Just ignore it to make using taginfo easier.
3585 value = value.substr(1);
3586 } else if (tmp = value.match(/^\s+/)) {
3587 // whitespace is ignored
3588 value = value.substr(tmp[0].length);
3589 } else if (value.match(/^[:.]/)) {
3590 // time separator
3591 if (value[0] === '.' && !done_with_warnings)
3592 parsing_warnings.push([ -1, value.length - 1, 'Please use ":" as hour/minute-separator' ]);
3593 curr_rule_tokens.push([ ':', 'timesep', value.length ]);
3594 value = value.substr(1);
3595 } else {
3596 // other single-character tokens
3597 curr_rule_tokens.push([value[0].toLowerCase(), value[0].toLowerCase(), value.length ]);
3598 value = value.substr(1);
3599 }
3600 }
3601
3602 all_tokens.push([ curr_rule_tokens, last_rule_fallback_terminated ]);
3603
3604 return all_tokens;
3605 }
3606 // }}}
3607
3608 /* error correction/tolerance function {{{
3609 * Go through word_error_correction hash and get correct value back.
3610 *
3611 * :param word: Wrong Word or character.
3612 * :param value_length: Current value_length (used for warnings).
3613 * :returns:
3614 * * (valid) opening_hours sub string.
3615 * * object with [ internal_value, token_name ] if value is correct.
3616 * * undefined if word could not be found (and thus is not be corrected).
3617 */
3618 function returnCorrectWordOrToken(word, value_length) {
3619 for (var token_name in word_error_correction) {
3620 for (var comment in word_error_correction[token_name]) {
3621 for (var old_val in word_error_correction[token_name][comment]) {
3622 if (old_val === word) {
3623 var val = word_error_correction[token_name][comment][old_val];
3624 if (comment === 'default') {
3625 // Return internal representation of word.
3626 return [ val, token_name ];
3627 } else if (token_name === 'wrong_words' && !done_with_warnings) {
3628 // Replace wrong words or characters with correct ones.
3629 // This will return a string which is then being tokenized.
3630 parsing_warnings.push([ -1, value_length - old_val.length,
3631 comment.replace(/<ko>/, old_val).replace(/<ok>/, val) ]);
3632 return val;
3633 } else {
3634 // Get correct string value from the 'default' hash and generate warning.
3635 var correct_abbr;
3636 for (correct_abbr in word_error_correction[token_name]['default']) {
3637 if (word_error_correction[token_name]['default'][correct_abbr] === val)
3638 break;
3639 }
3640 if (typeof correct_abbr === 'undefined') {
3641 throw formatLibraryBugMessage('Including the stacktrace.');
3642 }
3643 if (token_name !== 'timevar') {
3644 // Everything else than timevar:
3645 // E.g. 'Mo' start with a upper case letter.
3646 // It just looks better.
3647 correct_abbr = correct_abbr.charAt(0).toUpperCase()
3648 + correct_abbr.slice(1);
3649 }
3650 if (!done_with_warnings)
3651 parsing_warnings.push([ -1, value_length - old_val.length,
3652 comment.replace(/<ko>/, old_val).replace(/<ok>/, correct_abbr) ]);
3653 return [ val, token_name ];
3654 }
3655 }
3656 }
3657 }
3658 }
3659 return undefined;
3660 }
3661 // }}}
3662
3663 /* return warnings as list {{{
3664 *
3665 * :param it: Iterator object if available (optional).
3666 * :returns: Warnings as list with one warning per element.
3667 */
3668 function getWarnings(it) {
3669 if (!done_with_warnings && typeof it === 'object') {
3670 /* getWarnings was called in a state without critical errors.
3671 * We can do extended tests.
3672 */
3673
3674 /* Place all tests in this function if an additional (high
3675 * level) test is added and this does not require to rewrite
3676 * big parts of (sub) selector parsers only to get the
3677 * position. If that is the case, then rather place the test
3678 * code in the (sub) selector parser function directly.
3679 */
3680
3681 var wide_range_selectors = [ 'year', 'month', 'week', 'holiday' ];
3682 var small_range_selectors = [ 'weekday', 'time', '24/7', 'state', 'comment'];
3683
3684 // How many times was a selector_type used per rule? {{{
3685 var used_selectors = [];
3686 var used_selectors_types_array = [];
3687 var has_token = {};
3688
3689 for (var nrule = 0; nrule < new_tokens.length; nrule++) {
3690 if (new_tokens[nrule][0].length === 0) continue;
3691 // Rule does contain nothing useful e.g. second rule of '10:00-12:00;' (empty) which needs to be handled.
3692
3693 var selector_start_end_type = [ 0, 0, undefined ],
3694 prettified_group_value = [];
3695 // console.log(new_tokens[nrule][0]);
3696
3697 used_selectors[nrule] = {};
3698 used_selectors_types_array[nrule] = [];
3699
3700 do {
3701 selector_start_end_type = getSelectorRange(new_tokens[nrule][0], selector_start_end_type[1]);
3702 // console.log(selector_start_end_type, new_tokens[nrule][0].length);
3703
3704 if (selector_start_end_type[0] === selector_start_end_type[1] &&
3705 new_tokens[nrule][0][selector_start_end_type[0]][0] === '24/7'
3706 ) {
3707 has_token['24/7'] = true;
3708 }
3709
3710 if (typeof used_selectors[nrule][selector_start_end_type[2]] !== 'object') {
3711 used_selectors[nrule][selector_start_end_type[2]] = [ selector_start_end_type[1] ];
3712 } else {
3713 used_selectors[nrule][selector_start_end_type[2]].push(selector_start_end_type[1]);
3714 }
3715 used_selectors_types_array[nrule].push(selector_start_end_type[2]);
3716
3717 selector_start_end_type[1]++;
3718 } while (selector_start_end_type[1] < new_tokens[nrule][0].length);
3719 }
3720 // console.log('used_selectors: ' + JSON.stringify(used_selectors, null, ' '));
3721 // }}}
3722
3723 for (var nrule = 0; nrule < used_selectors.length; nrule++) {
3724
3725 /* Check if more than one not connected selector of the same type is used in one rule {{{ */
3726 for (var selector_type in used_selectors[nrule]) {
3727 // console.log(selector_type + ' use at: ' + used_selectors[nrule][selector_type].length);
3728 if (used_selectors[nrule][selector_type].length > 1) {
3729 parsing_warnings.push([nrule, used_selectors[nrule][selector_type][used_selectors[nrule][selector_type].length - 1],
3730 'You have used ' + used_selectors[nrule][selector_type].length
3731 + (selector_type.match(/^(?:comment|state)/) ?
3732 ' ' + selector_type
3733 + (selector_type === 'state' ? ' keywords' : 's')
3734 + ' in one rule.'
3735 + ' You may only use one in one rule.'
3736 :
3737 ' not connected ' + selector_type
3738 + (selector_type.match(/^(?:month|weekday)$/) ? 's' : ' ranges')
3739 + ' in one rule.'
3740 + ' This is probably an error.'
3741 + ' Equal selector types can (and should) always be written in conjunction separated by comma.'
3742 + ' Example for time ranges "12:00-13:00,15:00-18:00".'
3743 + ' Example for weekdays "Mo-We,Fr".'
3744 )
3745 + ' Rules can be separated by ";".' ]
3746 );
3747 done_with_selector_reordering = true; // Correcting the selector order makes no sense if this kind of issue exists.
3748 }
3749 }
3750 /* }}} */
3751
3752 /* Check if change default state rule is not the first rule {{{ */
3753 if ( typeof used_selectors[nrule].state === 'object'
3754 && Object.keys(used_selectors[nrule]).length === 1
3755 ) {
3756
3757 if (nrule !== 0) {
3758 parsing_warnings.push([nrule, new_tokens[nrule][0].length - 1,
3759 "This rule which changes the default state (which is closed) for all following rules is not the first rule."
3760 + " The rule will overwrite all previous rules."
3761 + " It can be legitimate to change the default state to open for example"
3762 + " and then only specify for which times the facility is closed."
3763 ]);
3764 }
3765 /* }}} */
3766 /* Check if a rule (with state open) has no time selector {{{ */
3767 } else if (typeof used_selectors[nrule].time === 'undefined') {
3768 if ( ( typeof used_selectors[nrule].state === 'object'
3769 && new_tokens[nrule][0][used_selectors[nrule].state[0]][0] === 'open'
3770 && typeof used_selectors[nrule].comment === 'undefined'
3771 ) || ( typeof used_selectors[nrule].comment === 'undefined'
3772 && typeof used_selectors[nrule].state === 'undefined'
3773 ) &&
3774 typeof used_selectors[nrule]['24/7'] === 'undefined'
3775 ) {
3776
3777 parsing_warnings.push([nrule, new_tokens[nrule][0].length - 1,
3778 "This rule is not very explicit because there is no time selector being used."
3779 + " Please add a time selector to this rule or use a comment to make it more explicit."
3780 ]);
3781 }
3782 }
3783 /* }}} */
3784
3785 /* Check if empty comment was given {{{ */
3786 if (typeof used_selectors[nrule].comment === 'object'
3787 && new_tokens[nrule][0][used_selectors[nrule].comment[0]][0].length === 0
3788 ) {
3789
3790 parsing_warnings.push([nrule, used_selectors[nrule].comment[0],
3791 "You have used an empty comment."
3792 + " Please either write something in the comment or use the keyword unknown instead."
3793 ]);
3794 }
3795 /* }}} */
3796
3797 /* Check if rule with closed|off modifier is additional {{{ */
3798 /* FIXME: Enable this test. */
3799 if (typeof new_tokens[nrule][0][0] === 'object'
3800 && new_tokens[nrule][0][0][0] === ','
3801 && new_tokens[nrule][0][0][1] === 'rule separator'
3802 && typeof used_selectors[nrule].state === 'object'
3803 && (
3804 new_tokens[nrule][0][used_selectors[nrule].state[0]][0] === 'closed'
3805 || new_tokens[nrule][0][used_selectors[nrule].state[0]][0] === 'off'
3806 )
3807 ) {
3808
3809 // parsing_warnings.push([nrule, new_tokens[nrule][0].length - 1,
3810 // "This rule will be evaluated as closed but it was specified as additional rule."
3811 // + " It is enough to specify this rule as normal rule using the \";\" character."
3812 // + " See https://wiki.openstreetmap.org/wiki/Key:opening_hours/specification#explain:rule_modifier:closed."
3813 // ]);
3814 }
3815 /* }}} */
3816
3817 /* Check for valid use of <separator_for_readability> {{{ */
3818 for (var i = 0; i < used_selectors_types_array[nrule].length - 1; i++) {
3819 var selector_type = used_selectors_types_array[nrule][i];
3820 var next_selector_type = used_selectors_types_array[nrule][i+1];
3821 if ( ( wide_range_selectors.indexOf(selector_type) !== -1
3822 && wide_range_selectors.indexOf(next_selector_type) !== -1
3823 ) || ( small_range_selectors.indexOf(selector_type) !== -1
3824 && small_range_selectors.indexOf(next_selector_type) !== -1)
3825 ) {
3826
3827 if (new_tokens[nrule][0][used_selectors[nrule][selector_type][0]][0] === ':') {
3828 parsing_warnings.push([nrule, used_selectors[nrule][selector_type][0],
3829 "You have used the optional symbol <separator_for_readability> in the wrong place."
3830 + " Please check the syntax specification to see where it could be used or remove it."
3831 ]);
3832 }
3833 }
3834 }
3835 /* }}} */
3836
3837 }
3838
3839 /* Check if 24/7 is used and it does not mean 24/7 because there are other rules {{{ */
3840 var has_advanced = it.advance();
3841
3842 if (has_advanced === true && has_token['24/7'] && !done_with_warnings) {
3843 parsing_warnings.push([ -1, 0, 'You used 24/7 in a way that is probably not interpreted as "24 hours 7 days a week".'
3844 // Probably because of: "24/7; 12:00-14:00 open", ". Needs extra testing.
3845 + ' For correctness you might want to use "open" or "closed"'
3846 + ' for this rule and then write your exceptions which should achieve the same goal and is more clear'
3847 + ' e.g. "open; Mo 12:00-14:00 off".']);
3848 }
3849 /* }}} */
3850
3851 prettifyValue();
3852 }
3853 done_with_warnings = true;
3854
3855 var warnings = [];
3856 // FIXME: Sort based on parsing_warnings[1], tricky …
3857 for (var i = 0; i < parsing_warnings.length; i++) {
3858 warnings.push( formatWarnErrorMessage(parsing_warnings[i][0], parsing_warnings[i][1], parsing_warnings[i][2]) );
3859 }
3860 return warnings;
3861 }
3862
3863 /* Helpers for getWarnings {{{ */
3864
3865 /* Check if token is the begin of a selector and why. {{{
3866 *
3867 * :param tokens: List of token objects.
3868 * :param at: Position where to start.
3869 * :returns:
3870 * * false the current token is not the begin of a selector.
3871 * * Position in token array from where the decision was made that
3872 * the token is the start of a selector.
3873 */
3874 function tokenIsTheBeginOfSelector(tokens, at) {
3875 if (typeof tokens[at][3] === 'string') {
3876 return 3;
3877 } else if (tokens[at][1] === 'comment'
3878 || tokens[at][1] === 'state'
3879 || tokens[at][1] === '24/7'
3880 || tokens[at][1] === 'rule separator'
3881 ){
3882
3883 return 1;
3884 } else {
3885 return false;
3886 }
3887 }
3888 /* }}} */
3889
3890 /* Get start and end position of a selector. {{{
3891 * For example this value 'Mo-We,Fr' will return the position of the
3892 * token lexeme 'Mo' and 'Fr' e.g. there indexes [ 0, 4 ] in the
3893 * selector array of tokens.
3894 *
3895 * :param tokens: List of token objects.
3896 * :param at: Position where to start.
3897 * :returns: Array:
3898 * 0. Index of first token in selector array of tokens.
3899 * 1. Index of last token in selector array of tokens.
3900 * 2. Selector type.
3901 */
3902 function getSelectorRange(tokens, at) {
3903 var selector_start = at,
3904 selector_end,
3905 pos_in_token_array;
3906
3907 for (; selector_start >= 0; selector_start--) {
3908 pos_in_token_array = tokenIsTheBeginOfSelector(tokens, selector_start);
3909 if (pos_in_token_array)
3910 break;
3911 }
3912 selector_end = selector_start;
3913
3914 if (pos_in_token_array === 1) {
3915 // Selector consists of a single token.
3916
3917 // Include tailing colon.
3918 if (selector_end + 1 < tokens.length && tokens[selector_end + 1][0] === ':')
3919 selector_end++;
3920
3921 return [ selector_start, selector_end, tokens[selector_start][pos_in_token_array] ];
3922 }
3923
3924 for (selector_end++; selector_end < tokens.length ; selector_end++) {
3925 if (tokenIsTheBeginOfSelector(tokens, selector_end))
3926 return [ selector_start, selector_end - 1, tokens[selector_start][pos_in_token_array] ];
3927 }
3928
3929 return [ selector_start, selector_end - 1, tokens[selector_start][pos_in_token_array] ];
3930 }
3931 /* }}} */
3932 /* }}} */
3933 /* }}} */
3934
3935 /* Prettify raw value from user. {{{
3936 * The value is generated by putting the tokens back together to a string.
3937 *
3938 * :param argument_hash: Hash which can contain:
3939 * 'conf': Configuration hash.
3940 * 'get_internals: If true export internal data structures.
3941 * 'rule_index: Only prettify the rule with this index.
3942 * :returns: Prettified value string or object if get_internals is true.
3943 */
3944 function prettifyValue(argument_hash) {
3945 var user_conf = {},
3946 get_internals = false,
3947 rule_index;
3948 if (typeof argument_hash !== 'undefined') {
3949
3950 if (typeof argument_hash.conf === 'object')
3951 user_conf = argument_hash.conf;
3952
3953 if (typeof argument_hash.rule_index === 'number')
3954 rule_index = argument_hash.rule_index;
3955
3956 if (argument_hash.get_internals === true)
3957 get_internals = true;
3958 }
3959
3960 for (var key in default_prettify_conf) {
3961 if (typeof user_conf[key] === 'undefined')
3962 user_conf[key] = default_prettify_conf[key];
3963 }
3964
3965 prettified_value = '';
3966 var prettified_value_array = [];
3967
3968 for (var nrule = 0; nrule < new_tokens.length; nrule++) {
3969 if (new_tokens[nrule][0].length === 0) continue;
3970 // Rule does contain nothing useful e.g. second rule of '10:00-12:00;' (empty) which needs to be handled.
3971
3972 if (typeof rule_index === 'number') {
3973 if (rule_index !== nrule) continue;
3974 } else {
3975 if (nrule !== 0)
3976 prettified_value += (
3977 new_tokens[nrule][1]
3978 ? user_conf.rule_sep_string + '|| '
3979 : (
3980 new_tokens[nrule][0][0][1] === 'rule separator'
3981 ? ','
3982 : (
3983 user_conf.print_semicolon
3984 ? ';'
3985 : ''
3986 )
3987 ) +
3988 user_conf.rule_sep_string);
3989 }
3990
3991 var selector_start_end_type = [ 0, 0, undefined ],
3992 prettified_group_value = [];
3993 // console.log(new_tokens[nrule][0]);
3994 var count = 0;
3995
3996
3997 do {
3998 selector_start_end_type = getSelectorRange(new_tokens[nrule][0], selector_start_end_type[1]);
3999 // console.log(selector_start_end_type, new_tokens[nrule][0].length, count);
4000
4001 if (count > 50) {
4002 throw formatLibraryBugMessage('infinite loop');
4003 }
4004
4005 if (selector_start_end_type[2] !== 'rule separator') {
4006 prettified_group_value.push(
4007 [
4008 selector_start_end_type,
4009 prettifySelector(
4010 new_tokens[nrule][0],
4011 selector_start_end_type[0],
4012 selector_start_end_type[1],
4013 selector_start_end_type[2],
4014 user_conf
4015 ),
4016 ]
4017 );
4018 }
4019
4020 selector_start_end_type[1]++;
4021 count++;
4022 // console.log(selector_start_end_type, new_tokens[nrule][0].length, count);
4023 } while (selector_start_end_type[1] < new_tokens[nrule][0].length);
4024 // console.log('Prettified value: ' + JSON.stringify(prettified_group_value, null, ' '));
4025 var not_sorted_prettified_group_value = prettified_group_value.slice();
4026
4027 if (!done_with_selector_reordering) {
4028 prettified_group_value.sort(
4029 function (a, b) {
4030 var selector_order = [ 'year', 'month', 'week', 'holiday', 'weekday', 'time', '24/7', 'state', 'comment'];
4031 return selector_order.indexOf(a[0][2]) - selector_order.indexOf(b[0][2]);
4032 }
4033 );
4034 }
4035 var old_prettified_value_length = prettified_value.length;
4036
4037 prettified_value += prettified_group_value.map(
4038 function (array) {
4039 return array[1];
4040 }
4041 ).join(' ');
4042
4043 prettified_value_array.push( prettified_group_value );
4044
4045 if (!done_with_selector_reordering_warnings) {
4046 for (var i = 0, l = not_sorted_prettified_group_value.length; i < l; i++) {
4047 if (not_sorted_prettified_group_value[i] !== prettified_group_value[i]) {
4048 // console.log(i + ': ' + prettified_group_value[i][0][2]);
4049 var length = i + old_prettified_value_length; // i: Number of spaces in string.
4050 for (var x = 0; x <= i; x++) {
4051 length += prettified_group_value[x][1].length;
4052 // console.log('Length: ' + length + ' ' + prettified_group_value[x][1]);
4053 }
4054 // console.log(length);
4055 parsing_warnings.push([ prettified_value, length,
4056 'The selector "' + prettified_group_value[i][0][2] + '" was switched with'
4057 + ' the selector "' + not_sorted_prettified_group_value[i][0][2] + '"'
4058 + ' for readablitity and compatibiltity reasons.'
4059 ]);
4060 }
4061 }
4062 }
4063 }
4064
4065 done_with_selector_reordering_warnings = true;
4066 // console.log(JSON.stringify(prettified_value_array, null, ' '));
4067
4068 if (get_internals) {
4069 return [ prettified_value_array, new_tokens ];
4070 } else {
4071 return prettified_value;
4072 }
4073 }
4074 // }}}
4075
4076 /* Check selector array of tokens for specific token name pattern. {{{
4077 *
4078 * :param tokens: List of token objects.
4079 * :param at: Position at which the matching should begin.
4080 * :param token_name(s): One or many token_name strings which have to match in that order.
4081 * :returns: true if token_name(s) match in order false otherwise.
4082 */
4083 function matchTokens(tokens, at /*, matches... */) {
4084 if (at + arguments.length - 2 > tokens.length)
4085 return false;
4086 for (var i = 0; i < arguments.length - 2; i++) {
4087 if (tokens[at + i][1] !== arguments[i + 2])
4088 return false;
4089 }
4090
4091 return true;
4092 }
4093 // }}}
4094
4095 /* Generate selector wrapper with time offset {{{
4096 *
4097 * :param func: Generated selector code function.
4098 * :param shirt: Time to shift in milliseconds.
4099 * :param token_name(s): One or many token_name strings which have to match in that order.
4100 * :returns: See selector code.
4101 */
4102 function generateDateShifter(func, shift) {
4103 return function(date) {
4104 var res = func(new Date(date.getTime() + shift));
4105
4106 if (typeof res[1] === 'undefined')
4107 return res;
4108 return [ res[0], new Date(res[1].getTime() - shift) ];
4109 };
4110 }
4111 // }}}
4112
4113 /* Top-level parser {{{
4114 *
4115 * :param tokens: List of token objects.
4116 * :param at: Position where to start.
4117 * :param selectors: Reference to selector object.
4118 * :param nrule: Rule number starting with 0.
4119 * :returns: See selector code.
4120 */
4121 function parseGroup(tokens, at, selectors, nrule) {
4122 var rule_modifier_specified = false;
4123
4124 // console.log(tokens); // useful for debugging of tokenize
4125 while (at < tokens.length) {
4126 // console.log('Parsing at position', at +':', tokens[at]);
4127 if (matchTokens(tokens, at, 'weekday')) {
4128 at = parseWeekdayRange(tokens, at, selectors);
4129 } else if (matchTokens(tokens, at, '24/7')) {
4130 selectors.time.push(function(date) { return [true]; });
4131 // Not needed. If there is no selector it automatically matches everything.
4132 // WRONG: This only works if there is no other selector in this selector group ...
4133 at++;
4134 } else if (matchTokens(tokens, at, 'holiday')) {
4135 if (matchTokens(tokens, at+1, ','))
4136 at = parseHoliday(tokens, at, selectors, true);
4137 else
4138 at = parseHoliday(tokens, at, selectors, false);
4139 week_stable = false;
4140 } else if (matchTokens(tokens, at, 'month', 'number')
4141 || matchTokens(tokens, at, 'month', 'weekday')
4142 || matchTokens(tokens, at, 'year', 'month', 'number')
4143 || matchTokens(tokens, at, 'year', 'event')
4144 || matchTokens(tokens, at, 'event')) {
4145
4146 at = parseMonthdayRange(tokens, at, nrule);
4147 week_stable = false;
4148 } else if (matchTokens(tokens, at, 'year')) {
4149 at = parseYearRange(tokens, at);
4150 week_stable = false;
4151 } else if (matchTokens(tokens, at, 'month')) {
4152 at = parseMonthRange(tokens, at);
4153 // week_stable = false; // Decided based on the actual value/tokens.
4154 } else if (matchTokens(tokens, at, 'week')) {
4155 tokens[at][3] = 'week';
4156 at = parseWeekRange(tokens, at);
4157
4158 } else if (at !== 0 && at !== tokens.length - 1 && tokens[at][0] === ':') {
4159 /* Ignore colon if they appear somewhere else than as time separator.
4160 * Except the start or end of the value.
4161 * This provides compatibility with the syntax proposed by Netzwolf:
4162 * http://wiki.openstreetmap.org/wiki/Key:opening_hours/specification#separator_for_readability
4163 * Check for valid use of <separator_for_readability> is implemented in function getWarnings().
4164 */
4165
4166 if (!done_with_warnings && matchTokens(tokens, at-1, 'holiday'))
4167 parsing_warnings.push([nrule, at, 'Please don’t use ":" after ' + tokens[at-1][1] + '.']);
4168
4169 at++;
4170 } else if (matchTokens(tokens, at, 'number', 'timesep')
4171 || matchTokens(tokens, at, 'timevar')
4172 || matchTokens(tokens, at, '(', 'timevar')
4173 || matchTokens(tokens, at, 'number', '-')) {
4174
4175 at = parseTimeRange(tokens, at, selectors, false);
4176
4177 } else if (matchTokens(tokens, at, 'state')) {
4178
4179 if (tokens[at][0] === 'open') {
4180 selectors.meaning = true;
4181 } else if (tokens[at][0] === 'closed' || tokens[at][0] === 'off') {
4182 selectors.meaning = false;
4183 } else {
4184 selectors.meaning = false;
4185 selectors.unknown = true;
4186 }
4187
4188 rule_modifier_specified = true;
4189 at++;
4190 if (typeof tokens[at] === 'object' && tokens[at][0] === ',') // additional rule
4191 at = [ at + 1 ];
4192
4193 } else if (matchTokens(tokens, at, 'comment')) {
4194 selectors.comment = tokens[at][0];
4195 if (!rule_modifier_specified) {
4196 // Then it is unknown. Either with unknown explicitly
4197 // specified or just a comment.
4198 selectors.meaning = false;
4199 selectors.unknown = true;
4200 }
4201
4202 rule_modifier_specified = true;
4203 at++;
4204 if (typeof tokens[at] === 'object' && tokens[at][0] === ',') // additional rule
4205 at = [ at + 1 ];
4206 } else if ((at === 0 || at === tokens.length - 1) && matchTokens(tokens, at, 'rule separator')) {
4207 at++;
4208 console.log("value: " + nrule);
4209 // throw formatLibraryBugMessage('Not implemented yet.');
4210 } else {
4211 var warnings = getWarnings();
4212 throw formatWarnErrorMessage(nrule, at, 'Unexpected token: "' + tokens[at][1]
4213 + '" This means that the syntax is not valid at that point or it is currently not supported.')
4214 + (warnings ? (' ' + warnings.join('; ')) : '');
4215 }
4216
4217 if (typeof at === 'object') { // additional rule
4218 tokens[at[0] - 1][1] = 'rule separator';
4219 break;
4220 }
4221 }
4222
4223 return at;
4224 }
4225
4226 function get_last_token_pos_in_token_group(tokens, at, last_at) {
4227 for (at++; at < last_at; at++) {
4228 if (typeof tokens[at] !== 'undefined') {
4229 if (typeof tokens[at][3] === 'string'
4230 || tokens[at][1] === 'comment'
4231 || tokens[at][1] === 'state'){
4232
4233 return at - 1;
4234 }
4235 }
4236 }
4237 return last_at;
4238 }
4239 // }}}
4240
4241 // helper functions for sub parser {{{
4242
4243 /* For given date, returns date moved to the start of the day with an offset specified in minutes. {{{
4244 * For example, if date is 2014-05-19_18:17:12, dateAtDayMinutes would
4245 * return 2014-05-19_02:00:00 for minutes=120.
4246 *
4247 * :param date: Date object.
4248 * :param minutes: Minutes used as offset starting from midnight of current day.
4249 * :returns: Moved date object.
4250 */
4251 function dateAtDayMinutes(date, minutes) {
4252 return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, minutes);
4253 }
4254 // }}}
4255
4256 /* For given date, returns date moved to the specific day of week {{{
4257 *
4258 * :param date: Date object.
4259 * :param weekday: Integer number for day of week. Starting with zero (Sunday).
4260 * :returns: Moved date object.
4261 */
4262 function dateAtNextWeekday(date, weekday) {
4263 var delta = weekday - date.getDay();
4264 return new Date(date.getFullYear(), date.getMonth(), date.getDate() + delta + (delta < 0 ? 7 : 0));
4265 }
4266 // }}}
4267
4268 /* Function to determine whether an array contains a value {{{
4269 * Source: http://stackoverflow.com/a/1181586
4270 *
4271 * :param needle: Element to find.
4272 * :returns: Index of element if present, if not present returns -1.
4273 */
4274 function indexOf(needle) {
4275 if(typeof Array.prototype.indexOf === 'function') {
4276 indexOf = Array.prototype.indexOf;
4277 } else {
4278 indexOf = function(needle) {
4279 var i = -1, index = -1;
4280 for(i = 0; i < this.length; i++) {
4281 if(this[i] === needle) {
4282 index = i;
4283 break;
4284 }
4285 }
4286 return index;
4287 };
4288 }
4289 return indexOf.call(this, needle);
4290 }
4291 // }}}
4292
4293 /* Numeric list parser (1,2,3-4,-1) {{{
4294 * Used in weekday parser above.
4295 *
4296 * :param tokens: List of token objects.
4297 * :param at: Position where to start.
4298 * :param func: Function func(from, to, at).
4299 * :returns: Position at which the token does not belong to the list any more.
4300 */
4301 function parseNumRange(tokens, at, func) {
4302 for (; at < tokens.length; at++) {
4303 if (matchTokens(tokens, at, 'number', '-', 'number')) {
4304 // Number range
4305 func(tokens[at][0], tokens[at+2][0], at);
4306 at += 3;
4307 } else if (matchTokens(tokens, at, '-', 'number')) {
4308 // Negative number
4309 func(-tokens[at+1][0], -tokens[at+1][0], at);
4310 at += 2;
4311 } else if (matchTokens(tokens, at, 'number')) {
4312 // Single number
4313 func(tokens[at][0], tokens[at][0], at);
4314 at++;
4315 } else {
4316 throw formatWarnErrorMessage(nrule, at + matchTokens(tokens, at, '-'),
4317 'Unexpected token in number range: ' + tokens[at][1]);
4318 }
4319
4320 if (!matchTokens(tokens, at, ','))
4321 break;
4322 }
4323
4324 return at;
4325 }
4326 // }}}
4327
4328 /* List parser for constrained weekdays in month range {{{
4329 * e.g. Su[-1] which selects the last Sunday of the month.
4330 *
4331 * :param tokens: List of token objects.
4332 * :param at: Position where to start.
4333 * :returns: Array:
4334 * 0. Constrained weekday number.
4335 * 1. Position at which the token does not belong to the list any more (after ']' token).
4336 */
4337 function getConstrainedWeekday(tokens, at) {
4338 var number = 0;
4339 var endat = parseNumRange(tokens, at, function(from, to, at) {
4340
4341 // bad number
4342 if (from === 0 || from < -5 || from > 5)
4343 throw formatWarnErrorMessage(nrule, at,
4344 'Number between -5 and 5 (except 0) expected');
4345
4346 if (from === to) {
4347 if (number !== 0)
4348 throw formatWarnErrorMessage(nrule, at,
4349 'You can not use more than one constrained weekday in a month range');
4350 number = from;
4351 } else {
4352 throw formatWarnErrorMessage(nrule, at+2,
4353 'You can not use a range of constrained weekdays in a month range');
4354 }
4355 });
4356
4357 if (!matchTokens(tokens, endat, ']'))
4358 throw formatWarnErrorMessage(nrule, endat, '"]" expected.');
4359
4360 return [ number, endat + 1 ];
4361 }
4362 // }}}
4363
4364 // Check if period is ok. Period 0 or 1 don’t make much sense.
4365 function checkPeriod(at, period, period_type, parm_string) {
4366 if (done_with_warnings)
4367 return;
4368
4369 if (period === 0) {
4370 throw formatWarnErrorMessage(nrule, at,
4371 'You can not use '+ period_type +' ranges with period equals zero.');
4372 } else if (period === 1) {
4373 if (typeof parm_string === 'string' && parm_string === 'no_end_year')
4374 parsing_warnings.push([nrule, at,
4375 'Please don’t use '+ period_type +' ranges with period equals one.'
4376 + ' If you want to express that a facility is open starting from a year without limit use "<year>+".']);
4377 else
4378 parsing_warnings.push([nrule, at,
4379 'Please don’t use '+ period_type +' ranges with period equals one.']);
4380 }
4381 }
4382
4383 /* Get date moved to constrained weekday (and moved for add_days. {{{
4384 * E.g. used for 'Aug Su[-1] -1 day'.
4385 *
4386 * :param year: Year as integer.
4387 * :param month: Month as integer starting with zero.
4388 * :param weekday: Integer number for day of week. Starting with zero (Sunday).
4389 * :param constrained_weekday: Position where to start.
4390 * :returns: Date object.
4391 */
4392 function getDateForConstrainedWeekday(year, month, weekday, constrained_weekday, add_days) {
4393 var tmp_date = dateAtNextWeekday(
4394 new Date(year, month + (constrained_weekday[0] > 0 ? 0 : 1), 1), weekday);
4395
4396 tmp_date.setDate(tmp_date.getDate() + (constrained_weekday[0] + (constrained_weekday[0] > 0 ? -1 : 0)) * 7);
4397
4398 if (typeof add_days !== 'undefined' && add_days[1])
4399 tmp_date.setDate(tmp_date.getDate() + add_days[0]);
4400
4401 return tmp_date;
4402 }
4403 // }}}
4404
4405 /* Check if date is valid. {{{
4406 *
4407 * :param month: Month as integer starting with zero.
4408 * :param date: Day of month as integer.
4409 * :returns: undefined. There is no real return value. This function just throws an exception if something is wrong.
4410 */
4411 function checkIfDateIsValid(month, day, nrule, at) {
4412 // May use this instead. The problem is that this does not give feedback as precise as the code which is used in this function.
4413 // var testDate = new Date(year, month, day);
4414 // if (testDate.getDate() !== day || testDate.getMonth() !== month || testDate.getFullYear() !== year) {
4415 // console.error('date not valid');
4416 // }
4417
4418 // https://en.wikipedia.org/wiki/Month#Julian_and_Gregorian_calendars
4419 if (day < 1 || day > 31)
4420 throw formatWarnErrorMessage(nrule, at, 'Day must be between 1 and 31.');
4421 if ((month===3 || month===5 || month===8 || month===10) && day===31)
4422 throw formatWarnErrorMessage(nrule, at, 'Month ' + months[month] + " doesn't have 31 days.!");
4423 if (month === 1 && day === 30)
4424 throw formatWarnErrorMessage(nrule, at, 'Month ' + months[1]+ " either has 28 or 29 days (leap years).");
4425 }
4426 // }}}
4427 // }}}
4428
4429 /* Time range parser (10:00-12:00,14:00-16:00) {{{
4430 *
4431 * :param tokens: List of token objects.
4432 * :param at: Position where to start.
4433 * :param selectors: Reference to selector object.
4434 * :param extended_open_end: Used for combined time range with open end.
4435 * extended_open_end: <time> - <time> +
4436 * param at is here A (if extended_open_end is true)
4437 * :returns: Position at which the token does not belong to the selector anymore.
4438 */
4439 function parseTimeRange(tokens, at, selectors, extended_open_end) {
4440 if (!extended_open_end)
4441 tokens[at][3] = 'time';
4442
4443 for (; at < tokens.length; at++) {
4444 var has_time_var_calc = [], has_normal_time = []; // element 0: start time, 1: end time
4445 has_normal_time[0] = matchTokens(tokens, at, 'number', 'timesep', 'number');
4446 has_time_var_calc[0] = matchTokens(tokens, at, '(', 'timevar');
4447 var minutes_from,
4448 minutes_to;
4449 if (has_normal_time[0] || matchTokens(tokens, at, 'timevar') || has_time_var_calc[0]) {
4450 // relying on the fact that always *one* of them is true
4451
4452 var is_point_in_time = false; // default no time range
4453 var has_open_end = false; // default no open end
4454 var timevar_add = [ 0, 0 ];
4455 var timevar_string = []; // capture timevar string like 'sunrise' to calculate it for the current date.
4456 var point_in_time_period;
4457
4458 // minutes_from
4459 if (has_normal_time[0]) {
4460 minutes_from = getMinutesByHoursMinutes(tokens, nrule, at+has_time_var_calc[0]);
4461 } else {
4462 timevar_string[0] = tokens[at+has_time_var_calc[0]][0];
4463 minutes_from = word_value_replacement[timevar_string[0]];
4464
4465 if (has_time_var_calc[0]) {
4466 timevar_add[0] = parseTimevarCalc(tokens, at);
4467 minutes_from += timevar_add[0];
4468 }
4469 }
4470
4471 var at_end_time = at+(has_normal_time[0] ? 3 : (has_time_var_calc[0] ? 7 : 1))+1; // after '-'
4472 if (!matchTokens(tokens, at_end_time - 1, '-')) { // not time range
4473 if (matchTokens(tokens, at_end_time - 1, '+')) {
4474 has_open_end = true;
4475 } else {
4476 if (oh_mode === 0) {
4477 throw formatWarnErrorMessage(nrule,
4478 at+(
4479 has_normal_time[0] ? (
4480 typeof tokens[at+3] === 'object' ? 3 : 2
4481 ) : (
4482 has_time_var_calc[0] ? 2 : (
4483 typeof tokens[at+1] !== 'undefined' ? 1 : 0
4484 )
4485 )
4486 ),
4487 'hyphen (-) or open end (+) in time range '
4488 + (has_time_var_calc[0] ? 'calculation ' : '') + 'expected.'
4489 + ' For working with points in time, the mode for ' + library_name + ' has to be altered.'
4490 + ' Maybe wrong tag?');
4491 } else {
4492 minutes_to = minutes_from + 1;
4493 is_point_in_time = true;
4494 }
4495 }
4496 }
4497
4498 // minutes_to
4499 if (has_open_end) {
4500 if (extended_open_end === 1)
4501 minutes_from += minutes_in_day;
4502 if (minutes_from >= 22 * 60)
4503 minutes_to = minutes_from + 8 * 60;
4504 else if (minutes_from >= 17 * 60)
4505 minutes_to = minutes_from + 10 * 60;
4506 else
4507 minutes_to = minutes_in_day;
4508 } else if (!is_point_in_time) {
4509 has_normal_time[1] = matchTokens(tokens, at_end_time, 'number', 'timesep', 'number');
4510 has_time_var_calc[1] = matchTokens(tokens, at_end_time, '(', 'timevar');
4511 if (!has_normal_time[1] && !matchTokens(tokens, at_end_time, 'timevar') && !has_time_var_calc[1]) {
4512 throw formatWarnErrorMessage(nrule, at_end_time - (typeof tokens[at_end_time] !== 'undefined' ? 0 : 1),
4513 'Time range does not continue as expected');
4514 } else {
4515 if (has_normal_time[1]) {
4516 minutes_to = getMinutesByHoursMinutes(tokens, nrule, at_end_time);
4517 } else {
4518 timevar_string[1] = tokens[at_end_time+has_time_var_calc[1]][0];
4519 minutes_to = word_value_replacement[timevar_string[1]];
4520 }
4521
4522 if (has_time_var_calc[1]) {
4523 timevar_add[1] = parseTimevarCalc(tokens, at_end_time);
4524 minutes_to += timevar_add[1];
4525 }
4526 }
4527 }
4528
4529 at = at_end_time + (is_point_in_time ? -1 :
4530 (has_normal_time[1] ? 3 : (has_time_var_calc[1] ? 7 : !has_open_end))
4531 );
4532
4533 if (matchTokens(tokens, at, '/', 'number')) {
4534 if (matchTokens(tokens, at + 2, 'timesep', 'number')) { // /hours:minutes
4535 point_in_time_period = getMinutesByHoursMinutes(tokens, nrule, at + 1);
4536 at += 4;
4537 } else { // /minutes
4538 point_in_time_period = tokens[at + 1][0];
4539 at += 2;
4540 if (matchTokens(tokens, at, 'timesep'))
4541 throw formatWarnErrorMessage(nrule, at,
4542 'Time period does not continue as expected. Exampe "/01:30".');
4543 }
4544
4545 // Check at this later state in the if condition to get the correct position.
4546 if (oh_mode === 0)
4547 throw formatWarnErrorMessage(nrule, at - 1,
4548 'opening_hours is running in "time range mode". Found point in time.');
4549
4550 is_point_in_time = true;
4551 } else if (matchTokens(tokens, at, '+')) {
4552 parseTimeRange(tokens, at_end_time, selectors, minutes_to < minutes_from ? 1 : true);
4553 at++;
4554 } else if (oh_mode === 1 && !is_point_in_time) {
4555 throw formatWarnErrorMessage(nrule, at_end_time,
4556 'opening_hours is running in "points in time mode". Found time range.');
4557 }
4558
4559 if (typeof lat !== 'undefined') { // lon will also be defined (see above)
4560 if (!has_normal_time[0] || !(has_normal_time[1] || has_open_end || is_point_in_time) )
4561 week_stable = false;
4562 } else { // we can not calculate exact times so we use the already applied constants (word_value_replacement).
4563 timevar_string = [];
4564 }
4565
4566 // Normalize minutes into range.
4567 if (!extended_open_end && minutes_from >= minutes_in_day)
4568 throw formatWarnErrorMessage(nrule, at_end_time - 2,
4569 'Time range starts outside of the current day');
4570 if (minutes_to < minutes_from || ((has_normal_time[0] && has_normal_time[1]) && minutes_from === minutes_to))
4571 minutes_to += minutes_in_day;
4572 if (minutes_to > minutes_in_day * 2)
4573 throw formatWarnErrorMessage(nrule, at_end_time + (has_normal_time[1] ? 4 : (has_time_var_calc[1] ? 7 : 1)) - 2,
4574 'Time spanning more than two midnights not supported');
4575
4576 // This shortcut makes always-open range check faster.
4577 if (minutes_from === 0 && minutes_to === minutes_in_day) {
4578 selectors.time.push(function(date) { return [true]; });
4579 } else {
4580 if (minutes_to > minutes_in_day) { // has_normal_time[1] must be true
4581 selectors.time.push(function(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period, extended_open_end) { return function(date) {
4582 var ourminutes = date.getHours() * 60 + date.getMinutes();
4583
4584 if (timevar_string[0]) {
4585 var date_from = SunCalc.getTimes(date, lat, lon)[timevar_string[0]];
4586 minutes_from = date_from.getHours() * 60 + date_from.getMinutes() + timevar_add[0];
4587 }
4588 if (timevar_string[1]) {
4589 var date_to = SunCalc.getTimes(date, lat, lon)[timevar_string[1]];
4590 minutes_to = date_to.getHours() * 60 + date_to.getMinutes() + timevar_add[1];
4591 minutes_to += minutes_in_day;
4592 // Needs to be added because it was added by
4593 // normal times: if (minutes_to < minutes_from)
4594 // above the selector construction.
4595 } else if (is_point_in_time && typeof point_in_time_period !== 'number') {
4596 minutes_to = minutes_from + 1;
4597 }
4598
4599 if (typeof point_in_time_period === 'number') {
4600 if (ourminutes < minutes_from) {
4601 return [false, dateAtDayMinutes(date, minutes_from)];
4602 } else if (ourminutes <= minutes_to) {
4603 for (var cur_min = minutes_from; ourminutes + point_in_time_period >= cur_min; cur_min += point_in_time_period) {
4604 if (cur_min === ourminutes) {
4605 return [true, dateAtDayMinutes(date, ourminutes + 1)];
4606 } else if (ourminutes < cur_min) {
4607 return [false, dateAtDayMinutes(date, cur_min)];
4608 }
4609 }
4610 }
4611 return [false, dateAtDayMinutes(date, minutes_in_day)];
4612 } else {
4613 if (ourminutes < minutes_from)
4614 return [false, dateAtDayMinutes(date, minutes_from)];
4615 else
4616 return [true, dateAtDayMinutes(date, minutes_to), has_open_end, extended_open_end];
4617 }
4618 }}(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period, extended_open_end));
4619
4620 selectors.wraptime.push(function(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period, extended_open_end) { return function(date) {
4621 var ourminutes = date.getHours() * 60 + date.getMinutes();
4622
4623 if (timevar_string[0]) {
4624 var date_from = SunCalc.getTimes(date, lat, lon)[timevar_string[0]];
4625 minutes_from = date_from.getHours() * 60 + date_from.getMinutes() + timevar_add[0];
4626 }
4627 if (timevar_string[1]) {
4628 var date_to = SunCalc.getTimes(date, lat, lon)[timevar_string[1]];
4629 minutes_to = date_to.getHours() * 60 + date_to.getMinutes() + timevar_add[1];
4630 // minutes_in_day does not need to be added.
4631 // For normal times in it was added in: if (minutes_to < // minutes_from)
4632 // above the selector construction and
4633 // subtracted in the selector construction call
4634 // which returns the selector function.
4635 }
4636
4637 if (typeof point_in_time_period === 'number') {
4638 if (ourminutes <= minutes_to) {
4639 for (var cur_min = 0; ourminutes + point_in_time_period >= cur_min; cur_min += point_in_time_period) {
4640 if (cur_min === ourminutes) {
4641 return [true, dateAtDayMinutes(date, ourminutes + 1)];
4642 } else if (ourminutes < cur_min) {
4643 return [false, dateAtDayMinutes(date, cur_min)];
4644 }
4645 }
4646 }
4647 } else {
4648 if (ourminutes < minutes_to)
4649 return [true, dateAtDayMinutes(date, minutes_to), has_open_end, extended_open_end];
4650 }
4651 return [false, undefined];
4652 }}(minutes_from, minutes_to - minutes_in_day, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period, extended_open_end));
4653 } else {
4654 selectors.time.push(function(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period, extended_open_end) { return function(date) {
4655 var ourminutes = date.getHours() * 60 + date.getMinutes();
4656
4657 if (timevar_string[0]) {
4658 var date_from = SunCalc.getTimes(date, lat, lon)[timevar_string[0]];
4659 minutes_from = date_from.getHours() * 60 + date_from.getMinutes() + timevar_add[0];
4660 }
4661 if (timevar_string[1]) {
4662 var date_to = SunCalc.getTimes(date, lat, lon)[timevar_string[1]];
4663 minutes_to = date_to.getHours() * 60 + date_to.getMinutes() + timevar_add[1];
4664 } else if (is_point_in_time && typeof point_in_time_period !== 'number') {
4665 minutes_to = minutes_from + 1;
4666 }
4667
4668 if (typeof point_in_time_period === 'number') {
4669 if (ourminutes < minutes_from) {
4670 return [false, dateAtDayMinutes(date, minutes_from)];
4671 } else if (ourminutes <= minutes_to) {
4672 for (var cur_min = minutes_from; ourminutes + point_in_time_period >= cur_min; cur_min += point_in_time_period) {
4673 if (cur_min === ourminutes) {
4674 return [true, dateAtDayMinutes(date, ourminutes + 1)];
4675 } else if (ourminutes < cur_min) {
4676 return [false, dateAtDayMinutes(date, cur_min)];
4677 }
4678 }
4679 }
4680 return [false, dateAtDayMinutes(date, minutes_in_day)];
4681 } else {
4682 if (ourminutes < minutes_from)
4683 return [false, dateAtDayMinutes(date, minutes_from)];
4684 else if (ourminutes < minutes_to)
4685 return [true, dateAtDayMinutes(date, minutes_to), has_open_end];
4686 else
4687 return [false, dateAtDayMinutes(date, minutes_from + minutes_in_day)];
4688 }
4689 }}(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period, extended_open_end));
4690 }
4691 }
4692
4693 } else if (matchTokens(tokens, at, 'number', '-', 'number')) { // "Mo 09-18" (Please don’t use this) -> "Mo 09:00-18:00".
4694 minutes_from = tokens[at][0] * 60;
4695 minutes_to = tokens[at+2][0] * 60;
4696 if (!done_with_warnings)
4697 parsing_warnings.push([nrule, at + 2,
4698 'Time range without minutes specified. Not very explicit!'
4699 + ' Please use this syntax instead "'
4700 + (tokens[at][0] < 10 ? '0' : '') + tokens[at][0] + ':00-'
4701 + (tokens[at+2][0] < 10 ? '0' : '') + tokens[at+2][0] + ':00".']);
4702
4703 if (minutes_from >= minutes_in_day)
4704 throw formatWarnErrorMessage(nrule, at,
4705 'Time range starts outside of the current day');
4706 if (minutes_to < minutes_from)
4707 minutes_to += minutes_in_day;
4708 if (minutes_to > minutes_in_day * 2)
4709 throw formatWarnErrorMessage(nrule, at + 2,
4710 'Time spanning more than two midnights not supported');
4711
4712 if (minutes_to > minutes_in_day) {
4713 selectors.time.push(function(minutes_from, minutes_to) { return function(date) {
4714 var ourminutes = date.getHours() * 60 + date.getMinutes();
4715
4716 if (ourminutes < minutes_from)
4717 return [false, dateAtDayMinutes(date, minutes_from)];
4718 else
4719 return [true, dateAtDayMinutes(date, minutes_to)];
4720 }}(minutes_from, minutes_to));
4721
4722 selectors.wraptime.push(function(minutes_from, minutes_to) { return function(date) {
4723 var ourminutes = date.getHours() * 60 + date.getMinutes();
4724
4725 if (ourminutes < minutes_to)
4726 return [true, dateAtDayMinutes(date, minutes_to)];
4727 else
4728 return [false, undefined];
4729 }}(minutes_from, minutes_to - minutes_in_day));
4730 } else {
4731 selectors.time.push(function(minutes_from, minutes_to) { return function(date) {
4732 var ourminutes = date.getHours() * 60 + date.getMinutes();
4733
4734 if (ourminutes < minutes_from)
4735 return [false, dateAtDayMinutes(date, minutes_from)];
4736 else if (ourminutes < minutes_to)
4737 return [true, dateAtDayMinutes(date, minutes_to), has_open_end];
4738 else
4739 return [false, dateAtDayMinutes(date, minutes_from + minutes_in_day)];
4740 }}(minutes_from, minutes_to));
4741 }
4742
4743 at += 3;
4744 } else { // additional rule
4745 if (matchTokens(tokens, at, '('))
4746 throw formatWarnErrorMessage(nrule, at, 'Missing variable time (e.g. sunrise) after: "' + tokens[at][1] + '"');
4747 if (matchTokens(tokens, at, 'number', 'timesep'))
4748 throw formatWarnErrorMessage(nrule, at+1, 'Missing minutes in time range after: "' + tokens[at+1][1] + '"');
4749 if (matchTokens(tokens, at, 'number'))
4750 throw formatWarnErrorMessage(nrule, at + (typeof tokens[at+1] !== 'undefined' ? 1 : 0),
4751 'Missing time separator in time range after: "' + tokens[at][1] + '"');
4752 return [ at ];
4753 }
4754
4755 if (!matchTokens(tokens, at, ','))
4756 break;
4757 }
4758
4759 return at;
4760 }
4761 // }}}
4762
4763 /* Helpers for time range parser {{{ */
4764
4765 /* Get time in minutes from <hour>:<minute> (tokens). {{{
4766 * Only used if throwing an error is wanted.
4767 *
4768 * :param tokens: List of token objects.
4769 * :param nrule: Rule number starting with 0.
4770 * :param at: Position at which the time begins.
4771 * :returns: Time in minutes.
4772 */
4773 function getMinutesByHoursMinutes(tokens, nrule, at) {
4774 if (tokens[at+2][0] > 59)
4775 throw formatWarnErrorMessage(nrule, at+2,
4776 'Minutes are greater than 59.');
4777 return tokens[at][0] * 60 + tokens[at+2][0];
4778 }
4779 // }}}
4780
4781 /* Get time in minutes from "(sunrise-01:30)" {{{
4782 * Extract the added or subtracted time from "(sunrise-01:30)"
4783 * returns time in minutes e.g. -90.
4784 *
4785 * :param tokens: List of token objects.
4786 * :param at: Position where the specification for the point in time could be.
4787 * :returns: Time in minutes on suggest, throws an exception otherwise.
4788 */
4789 function parseTimevarCalc(tokens, at) {
4790 var error;
4791 if (matchTokens(tokens, at+2, '+') || matchTokens(tokens, at+2, '-')) {
4792 if (matchTokens(tokens, at+3, 'number', 'timesep', 'number')) {
4793 if (matchTokens(tokens, at+6, ')')) {
4794 var add_or_subtract = tokens[at+2][0] === '+' ? '1' : '-1';
4795 var minutes = getMinutesByHoursMinutes(tokens, nrule, at+3) * add_or_subtract;
4796 if (minutes === 0)
4797 parsing_warnings.push([ nrule, at+5, 'Adding zero in a variable time calculation does not change the variable time.'
4798 + ' Please omit the calculation (example: "sunrise-(sunset-00:00)").' ]
4799 );
4800 return minutes;
4801 } else {
4802 error = [ at+6, '. Missing ")".'];
4803 }
4804 } else {
4805 error = [ at+5, ' (time).'];
4806 }
4807 } else {
4808 error = [ at+2, '. "+" or "-" expected.'];
4809 }
4810
4811 if (error)
4812 throw formatWarnErrorMessage(nrule, error[0],
4813 'Calculcation with variable time is not in the right syntax' + error[1]);
4814 }
4815 /* }}} */
4816 /* }}} */
4817
4818 /* Weekday range parser (Mo,We-Fr,Sa[1-2,-1],PH). {{{
4819 *
4820 * :param tokens: List of token objects.
4821 * :param at: Position where the weekday tokens could be.
4822 * :param selectors: Reference to selector object.
4823 * :returns: Position at which the token does not belong to the selector anymore.
4824 */
4825 function parseWeekdayRange(tokens, at, selectors, in_holiday_selector) {
4826 if (!in_holiday_selector) {
4827 in_holiday_selector = true;
4828 tokens[at][3] = 'weekday';
4829 }
4830
4831 for (; at < tokens.length; at++) {
4832 if (matchTokens(tokens, at, 'weekday', '[')) {
4833 // Conditional weekday (Mo[3])
4834 var numbers = [];
4835
4836 // Get list of constraints
4837 var endat = parseNumRange(tokens, at+2, function(from, to, at) {
4838
4839 // bad number
4840 if (from === 0 || from < -5 || from > 5)
4841 throw formatWarnErrorMessage(nrule, at,
4842 'Number between -5 and 5 (except 0) expected');
4843
4844 if (from === to) {
4845 numbers.push(from);
4846 } else if (from < to) {
4847 for (var i = from; i <= to; i++) {
4848 // bad number
4849 if (i === 0 || i < -5 || i > 5)
4850 throw formatWarnErrorMessage(nrule, at+2,
4851 'Number between -5 and 5 (except 0) expected.');
4852
4853 numbers.push(i);
4854 }
4855 } else {
4856 throw formatWarnErrorMessage(nrule, at+2,
4857 'Bad range: ' + from + '-' + to);
4858 }
4859 });
4860
4861 if (!matchTokens(tokens, endat, ']'))
4862 throw formatWarnErrorMessage(nrule, endat, '"]" or more numbers expected.');
4863
4864 var add_days = getMoveDays(tokens, endat+1, 6, 'constrained weekdays');
4865 week_stable = false;
4866
4867 // Create selector for each list element.
4868 for (var nnumber = 0; nnumber < numbers.length; nnumber++) {
4869
4870 selectors.weekday.push(function(weekday, number, add_days) { return function(date) {
4871 var date_num = getValueForDate(date, false); // Year not needed to distinguish.
4872 var start_of_this_month = new Date(date.getFullYear(), date.getMonth(), 1);
4873 var start_of_next_month = new Date(date.getFullYear(), date.getMonth() + 1, 1);
4874
4875 var target_day_this_month;
4876
4877 target_day_this_month = getDateForConstrainedWeekday(date.getFullYear(), date.getMonth(), weekday, [ number ]);
4878
4879 var target_day_with_added_days_this_month = new Date(target_day_this_month.getFullYear(),
4880 target_day_this_month.getMonth(), target_day_this_month.getDate() + add_days);
4881
4882 // The target day with added days can be before this month
4883 if (target_day_with_added_days_this_month.getTime() < start_of_this_month.getTime()) {
4884 // but in this case, the target day without the days added needs to be in this month
4885 if (target_day_this_month.getTime() >= start_of_this_month.getTime()) {
4886 // so we calculate it for the month
4887 // following this month and hope that the
4888 // target day will actually be this month.
4889
4890 target_day_with_added_days_this_month = dateAtNextWeekday(
4891 new Date(date.getFullYear(), date.getMonth() + (number > 0 ? 0 : 1) + 1, 1), weekday);
4892 target_day_this_month.setDate(target_day_with_added_days_this_month.getDate()
4893 + (number + (number > 0 ? -1 : 0)) * 7 + add_days);
4894 } else {
4895 // Calculated target day is not inside this month
4896 // therefore the specified weekday (e.g. fifth Sunday)
4897 // does not exist this month. Try it next month.
4898 return [false, start_of_next_month];
4899 }
4900 } else if (target_day_with_added_days_this_month.getTime() >= start_of_next_month.getTime()) {
4901 // The target day is in the next month. If the target day without the added days is not in this month
4902 if (target_day_this_month.getTime() >= start_of_next_month.getTime())
4903 return [false, start_of_next_month];
4904 }
4905
4906 var target_day_with_added_moved_days_this_month;
4907 if (add_days > 0) {
4908 target_day_with_added_moved_days_this_month = dateAtNextWeekday(
4909 new Date(date.getFullYear(), date.getMonth() + (number > 0 ? 0 : 1) -1, 1), weekday);
4910 target_day_with_added_moved_days_this_month.setDate(target_day_with_added_moved_days_this_month.getDate()
4911 + (number + (number > 0 ? -1 : 0)) * 7 + add_days);
4912
4913 if (date_num === getValueForDate(target_day_with_added_moved_days_this_month, false))
4914 return [true, dateAtDayMinutes(date, minutes_in_day)];
4915 } else if (add_days < 0) {
4916 target_day_with_added_moved_days_this_month = dateAtNextWeekday(
4917 new Date(date.getFullYear(), date.getMonth() + (number > 0 ? 0 : 1) + 1, 1), weekday);
4918 target_day_with_added_moved_days_this_month.setDate(target_day_with_added_moved_days_this_month.getDate()
4919 + (number + (number > 0 ? -1 : 0)) * 7 + add_days);
4920
4921 if (target_day_with_added_moved_days_this_month.getTime() >= start_of_next_month.getTime()) {
4922 if (target_day_with_added_days_this_month.getTime() >= start_of_next_month.getTime())
4923 return [false, target_day_with_added_moved_days_this_month];
4924 } else {
4925 if (target_day_with_added_days_this_month.getTime() < start_of_next_month.getTime()
4926 && getValueForDate(target_day_with_added_days_this_month, false) === date_num)
4927 return [true, dateAtDayMinutes(date, minutes_in_day)];
4928
4929 target_day_with_added_days_this_month = target_day_with_added_moved_days_this_month;
4930 }
4931 }
4932
4933 // we hit the target day
4934 if (date.getDate() === target_day_with_added_days_this_month.getDate()) {
4935 return [true, dateAtDayMinutes(date, minutes_in_day)];
4936 }
4937
4938 // we're before target day
4939 if (date.getDate() < target_day_with_added_days_this_month.getDate()) {
4940 return [false, target_day_with_added_days_this_month];
4941 }
4942
4943 // we're after target day, set check date to next month
4944 return [false, start_of_next_month];
4945 }}(tokens[at][0], numbers[nnumber], add_days[0]));
4946 }
4947
4948 at = endat + 1 + add_days[1];
4949 } else if (matchTokens(tokens, at, 'weekday')) {
4950 // Single weekday (Mo) or weekday range (Mo-Fr)
4951 var is_range = matchTokens(tokens, at+1, '-', 'weekday');
4952
4953 var weekday_from = tokens[at][0];
4954 var weekday_to = is_range ? tokens[at+2][0] : weekday_from;
4955
4956 var inside = true;
4957
4958 // handle reversed range
4959 if (weekday_to < weekday_from) {
4960 var tmp = weekday_to;
4961 weekday_to = weekday_from - 1;
4962 weekday_from = tmp + 1;
4963 inside = false;
4964 }
4965
4966 if (weekday_to < weekday_from) { // handle full range
4967 selectors.weekday.push(function(date) { return [true]; });
4968 // Not needed. If there is no selector it automatically matches everything.
4969 // WRONG: This only works if there is no other selector in this selector group ...
4970 } else {
4971 selectors.weekday.push(function(weekday_from, weekday_to, inside) { return function(date) {
4972 var ourweekday = date.getDay();
4973
4974 if (ourweekday < weekday_from || ourweekday > weekday_to) {
4975 return [!inside, dateAtNextWeekday(date, weekday_from)];
4976 } else {
4977 return [inside, dateAtNextWeekday(date, weekday_to + 1)];
4978 }
4979 }}(weekday_from, weekday_to, inside));
4980 }
4981
4982 at += is_range ? 3 : 1;
4983 } else if (matchTokens(tokens, at, 'holiday')) {
4984 week_stable = false;
4985 return parseHoliday(tokens, at, selectors, true, in_holiday_selector);
4986 } else if (matchTokens(tokens, at - 1, ',')) { // additional rule
4987 throw formatWarnErrorMessage(
4988 nrule,
4989 at - 1,
4990 'An additional rule does not make sense here. Just use a ";" as rule separator.'
4991 + ' See https://wiki.openstreetmap.org/wiki/Key:opening_hours/specification#explain:additional_rule_separator');
4992 } else {
4993 throw formatWarnErrorMessage(nrule, at, 'Unexpected token in weekday range: ' + tokens[at][1]);
4994 }
4995
4996 if (!matchTokens(tokens, at, ','))
4997 break;
4998 }
4999
5000 return at;
5001 }
5002 // }}}
5003
5004 /* Get the number of days a date should be moved (if any). {{{
5005 *
5006 * :param tokens: List of token objects.
5007 * :param at: Position where the date moving tokens could be.
5008 * :param max_differ: Maximal number of days to move (could also be zero if there are no day move tokens).
5009 * :returns: Array:
5010 * 0. Days to add.
5011 * 1. How many tokens.
5012 */
5013 function getMoveDays(tokens, at, max_differ, name) {
5014 var add_days = [ 0, 0 ]; // [ 'days to add', 'how many tokens' ]
5015 add_days[0] = matchTokens(tokens, at, '+') || (matchTokens(tokens, at, '-') ? -1 : 0);
5016 if (add_days[0] !== 0 && matchTokens(tokens, at+1, 'number', 'calcday')) {
5017 // continues with '+ 5 days' or something like that
5018 if (tokens[at+1][0] > max_differ)
5019 throw formatWarnErrorMessage(nrule, at+2,
5020 'There should be no reason to differ more than ' + max_differ + ' days from a ' + name + '. If so tell us …');
5021 add_days[0] *= tokens[at+1][0];
5022 if (add_days[0] === 0 && !done_with_warnings)
5023 parsing_warnings.push([ nrule, at+2, 'Adding 0 does not change the date. Please omit this.' ]);
5024 add_days[1] = 3;
5025 } else {
5026 add_days[0] = 0;
5027 }
5028 return add_days;
5029 }
5030 // }}}
5031
5032 /* Holiday parser for public and school holidays (PH,SH) {{{
5033 *
5034 * :param tokens: List of token objects.
5035 * :param at: Position where to start.
5036 * :param selectors: Reference to selector object.
5037 * :param 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.
5038 * :returns: Position at which the token does not belong to the selector anymore.
5039 */
5040 function parseHoliday(tokens, at, selectors, push_to_weekday, in_holiday_selector) {
5041 if (!in_holiday_selector) {
5042
5043 if (push_to_weekday)
5044 tokens[at][3] = 'weekday';
5045 else
5046 tokens[at][3] = 'holiday'; // Could also be holiday but this is not important here.
5047 }
5048
5049 for (; at < tokens.length; at++) {
5050 if (matchTokens(tokens, at, 'holiday')) {
5051 if (tokens[at][0] === 'PH') {
5052 var applying_holidays = getMatchingHoliday(tokens[at][0]);
5053
5054 // Only allow moving one day in the past or in the future.
5055 // This makes implementation easier because only one holiday is assumed to be moved to the next year.
5056 var add_days = getMoveDays(tokens, at+1, 1, 'public holiday');
5057
5058 var selector = function(applying_holidays, add_days) { return function(date) {
5059
5060 var holidays = getApplyingHolidaysForYear(applying_holidays, date.getFullYear(), add_days);
5061 // Needs to be calculated each time because of movable days.
5062
5063 var date_num = getValueForDate(date, true);
5064
5065 for (var i = 0; i < holidays.length; i++) {
5066 var next_holiday_date_num = getValueForDate(holidays[i][0], true);
5067
5068 if (date_num < next_holiday_date_num) {
5069
5070 if (add_days[0] > 0) {
5071 // Calculate the last holiday from previous year to tested against it.
5072 var holidays_last_year = getApplyingHolidaysForYear(applying_holidays, date.getFullYear() - 1, add_days);
5073 var last_holiday_last_year = holidays_last_year[holidays_last_year.length - 1];
5074 var last_holiday_last_year_num = getValueForDate(last_holiday_last_year[0], true);
5075
5076 if (date_num < last_holiday_last_year_num ) {
5077 return [ false, last_holiday_last_year[0] ];
5078 } else if (date_num === last_holiday_last_year_num) {
5079 return [true, dateAtDayMinutes(last_holiday_last_year[0], minutes_in_day),
5080 'Day after ' +last_holiday_last_year[1] ];
5081 }
5082 }
5083
5084 return [ false, holidays[i][0] ];
5085 } else if (date_num === next_holiday_date_num) {
5086 return [true, new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1),
5087 (add_days[0] > 0 ? 'Day after ' : (add_days[0] < 0 ? 'Day before ' : '')) + holidays[i][1] ];
5088 }
5089 }
5090
5091 if (add_days[0] < 0) {
5092 // Calculate the first holiday from next year to tested against it.
5093 var holidays_next_year = getApplyingHolidaysForYear(applying_holidays, date.getFullYear() + 1, add_days);
5094 var first_holidays_next_year = holidays_next_year[0];
5095 var first_holidays_next_year_num = getValueForDate(first_holidays_next_year[0], true);
5096 if (date_num === first_holidays_next_year_num) {
5097 return [true, dateAtDayMinutes(first_holidays_next_year[0], minutes_in_day),
5098 'Day before ' + first_holidays_next_year[1] ];
5099 }
5100 }
5101
5102 // continue next year
5103 return [ false, new Date(holidays[0][0].getFullYear() + 1,
5104 holidays[0][0].getMonth(),
5105 holidays[0][0].getDate()) ];
5106
5107 }}(applying_holidays, add_days);
5108
5109 if (push_to_weekday)
5110 selectors.weekday.push(selector);
5111 else
5112 selectors.holiday.push(selector);
5113
5114 at += 1 + add_days[1];
5115 } else if (tokens[at][0] === 'SH') {
5116 var applying_holidays = getMatchingHoliday(tokens[at][0]);
5117
5118 var holidays = []; // needs to be sorted each time because of movable days
5119
5120 var selector = function(applying_holidays) { return function(date) {
5121 var date_num = getValueForDate(date);
5122
5123 // Iterate over holiday array containing the different holiday ranges.
5124 for (var i = 0; i < applying_holidays.length; i++) {
5125
5126 var holiday = getSHForYear(applying_holidays[i], date.getFullYear());
5127
5128 for (var h = 0; h < holiday.length; h+=4) {
5129 var holiday_to_plus = new Date(date.getFullYear(), holiday[2+h] - 1, holiday[3+h] + 1);
5130 var holiday_from = (holiday[0+h] - 1) * 100 + holiday[1+h];
5131 var holiday_to = (holiday[2+h] - 1) * 100 + holiday[3+h];
5132 holiday_to_plus = getValueForDate(holiday_to_plus);
5133
5134 var holiday_ends_next_year = holiday_to < holiday_from;
5135
5136 if (date_num < holiday_from) { // date is before selected holiday
5137
5138 // check if we are in the holidays from the last year spanning into this year
5139 var last_year_holiday = getSHForYear(applying_holidays[applying_holidays.length - 1], date.getFullYear() - 1, false);
5140 if (typeof last_year_holiday !== 'undefined') {
5141 var last_year_holiday_from = (last_year_holiday[last_year_holiday.length - 4] - 1) * 100
5142 + last_year_holiday[last_year_holiday.length - 3]; // e.g. 1125
5143 var last_year_holiday_to = (last_year_holiday[last_year_holiday.length - 2] - 1) * 100
5144 + last_year_holiday[last_year_holiday.length - 1]; // e.g. 0005
5145
5146 if (last_year_holiday_to < last_year_holiday_from && date_num < last_year_holiday_to)
5147 return [ true, new Date(date.getFullYear(),
5148 last_year_holiday[last_year_holiday.length - 2] - 1,
5149 last_year_holiday[last_year_holiday.length - 1] + 1),
5150 applying_holidays[applying_holidays.length - 1].name ];
5151 else
5152 return [ false, new Date(date.getFullYear(), holiday[0+h] - 1, holiday[1+h]) ];
5153 } else { // school holidays for last year are not defined.
5154 return [ false, new Date(date.getFullYear(), holiday[0+h] - 1, holiday[1+h]) ];
5155 }
5156 } else if (holiday_from <= date_num && (date_num < holiday_to_plus || holiday_ends_next_year)) {
5157 return [ true, new Date(date.getFullYear() + holiday_ends_next_year, holiday[2+h] - 1, holiday[3+h] + 1),
5158 applying_holidays[i].name ];
5159 } else if (holiday_to_plus === date_num) { // selected holiday end is equal to month and day
5160 if (h + 4 < holiday.length) { // next holiday is next date range of the same holidays
5161 h += 4;
5162 return [ false, new Date(date.getFullYear(), holiday[0+h] - 1, holiday[1+h]) ];
5163 } else {
5164 if (i + 1 === applying_holidays.length) { // last holidays are handled, continue all over again
5165 var holiday = getSHForYear(applying_holidays[0], date.getFullYear() + 1);
5166 return [ false, new Date(date.getFullYear() + !holiday_ends_next_year, holiday[0+h] - 1, holiday[1+h]) ];
5167 } else { // return the start of the next holidays
5168 var holiday = getSHForYear(applying_holidays[i+1], date.getFullYear());
5169 return [ false, new Date(date.getFullYear(), holiday[0] - 1, holiday[1]) ];
5170 }
5171 }
5172 }
5173 }
5174 }
5175 return [ false ];
5176 }}(applying_holidays);
5177
5178 if (push_to_weekday)
5179 selectors.weekday.push(selector);
5180 else
5181 selectors.holiday.push(selector);
5182 at += 1; // FIXME: test
5183 }
5184 } else if (matchTokens(tokens, at, 'weekday')) {
5185 return parseWeekdayRange(tokens, at, selectors, true);
5186 } else if (matchTokens(tokens, at - 1, ',')) { // additional rule
5187 throw formatWarnErrorMessage(
5188 nrule,
5189 at - 1,
5190 'An additional rule does not make sense here. Just use a ";" as rule separator.'
5191 + ' See https://wiki.openstreetmap.org/wiki/Key:opening_hours/specification#explain:additional_rule_separator');
5192 } else {
5193 throw formatWarnErrorMessage(nrule, at, 'Unexpected token (holiday parser): ' + tokens[at][1]);
5194 }
5195
5196 if (!matchTokens(tokens, at, ','))
5197 break;
5198 }
5199
5200 return at;
5201 }
5202
5203 // Helpers for holiday parsers {{{
5204
5205 /* Returns a number for a date which can then be used to compare just the dates (without the time). {{{
5206 * 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.
5207 * Example: Returns 20150015 for Jan 01 2015
5208 *
5209 * :param date: Date object.
5210 * :param include_year: Boolean. If true include the year.
5211 * :returns: Number for the date.
5212 */
5213 function getValueForDate(date, include_year) {
5214 // Implicit because undefined evaluates to false.
5215 // include_year = typeof include_year !== 'undefined' ? include_year : false;
5216
5217 return (include_year ? date.getFullYear() * 10000 : 0) + date.getMonth() * 100 + date.getDate();
5218 }
5219 // }}}
5220
5221 // return the school holiday definition e.g. [ 5, 25, /* to */ 6, 5 ],
5222 // for the specified year
5223 function getSHForYear(SH_hash, year, fatal) {
5224 if (typeof fatal === 'undefined')
5225 fatal = true;
5226
5227 var holiday = SH_hash[year];
5228 if (typeof holiday === 'undefined') {
5229 holiday = SH_hash['default']; // applies for any year without explicit definition
5230 if (typeof holiday === 'undefined') {
5231 if (fatal) {
5232 throw formatLibraryBugMessage('School holiday ' + SH_hash.name + ' has no definition for the year ' + year + '.'
5233 + ' You can also add them: ' + repository_url);
5234 } else {
5235 return undefined;
5236 }
5237 }
5238 }
5239 return holiday;
5240 }
5241
5242 // Return closed holiday definition available.
5243 // First try to get the state, if missing get the country wide holidays
5244 // (which can be limited to some states).
5245 function getMatchingHoliday(type_of_holidays) {
5246 if (typeof location_cc !== 'undefined') {
5247 if (holidays.hasOwnProperty(location_cc)) {
5248 if (typeof location_state !== 'undefined'
5249 && holidays[location_cc][location_state]
5250 && holidays[location_cc][location_state][type_of_holidays]) {
5251 // if holidays for the state are specified use it
5252 // and ignore lesser specific ones (for the country)
5253 return holidays[location_cc][location_state][type_of_holidays];
5254 } else if (holidays[location_cc][type_of_holidays]) {
5255 // holidays are only defined country wide
5256 var matching_holiday = {}; // holidays in the country wide scope can be limited to certain states
5257 for (var holiday_name in holidays[location_cc][type_of_holidays]) {
5258 if (typeof holidays[location_cc][type_of_holidays][holiday_name][2] === 'object') {
5259 if (-1 !== holidays[location_cc][type_of_holidays][holiday_name][2].indexOf(location_state))
5260 matching_holiday[holiday_name] = holidays[location_cc][type_of_holidays][holiday_name];
5261 } else {
5262 matching_holiday[holiday_name] = holidays[location_cc][type_of_holidays][holiday_name];
5263 }
5264 }
5265 if (Object.keys(matching_holiday).length === 0)
5266 throw formatLibraryBugMessage('There are no holidays ' + type_of_holidays + ' defined for country ' + location_cc + '.'
5267 + ' You can also add them: ' + repository_url);
5268 return matching_holiday;
5269 } else {
5270 throw formatLibraryBugMessage('Holidays ' + type_of_holidays + ' are not defined for country ' + location_cc
5271 + ' and state ' + location_state + '.'
5272 + ' You can also add them: ' + repository_url);
5273 }
5274 } else {
5275 throw formatLibraryBugMessage('No holidays are defined for country ' + location_cc + '.'
5276 + ' You can also add them: ' + repository_url);
5277 }
5278 } else { /* We have no idea which holidays do apply because the country code was not provided. */
5279 throw 'Country code missing which is needed to select the correct holidays (see README how to provide it)';
5280 }
5281 }
5282
5283 function getMovableEventsForYear(Y) {
5284 // calculate easter
5285 var C = Math.floor(Y/100);
5286 var N = Y - 19*Math.floor(Y/19);
5287 var K = Math.floor((C - 17)/25);
5288 var I = C - Math.floor(C/4) - Math.floor((C - K)/3) + 19*N + 15;
5289 I = I - 30*Math.floor((I/30));
5290 I = I - Math.floor(I/28)*(1 - Math.floor(I/28)*Math.floor(29/(I + 1))*Math.floor((21 - N)/11));
5291 var J = Y + Math.floor(Y/4) + I + 2 - C + Math.floor(C/4);
5292 J = J - 7*Math.floor(J/7);
5293 var L = I - J;
5294 var M = 3 + Math.floor((L + 40)/44);
5295 var D = L + 28 - 31*Math.floor(M/4);
5296
5297 // calculate orthodox easter
5298 var oA = Y % 4;
5299 var oB = Y % 7;
5300 var oC = Y % 19;
5301 var oD = (19*oC + 15) % 30;
5302 var oE = (2*oA+4*oB - oD + 34) % 7;
5303 var oF = oD+oE;
5304
5305 var oDate;
5306 if (oF < 9) {
5307 oDate = new Date(Y, 4-1, oF+4);
5308 } else {
5309 if ((oF+4)<31) {
5310 oDate = new Date(Y, 4-1, oF+4);
5311 } else {
5312 oDate = new Date(Y, 5-1, oF-26);
5313 }
5314 }
5315
5316 // calculate last Sunday in February
5317 var lastFebruaryDay = new Date(Y, 2, 0);
5318 var lastFebruarySunday = lastFebruaryDay.getDate() - lastFebruaryDay.getDay();
5319
5320 // calculate Victoria Day. last Monday before or on May 24
5321 var may_24 = new Date(Y, 4, 24);
5322 var victoriaDay = 24 - ((6 + may_24.getDay()) % 7);
5323
5324 // calculate Canada Day. July 1st unless 1st is on Sunday, then July 2.
5325 var july_1 = new Date(Y, 6, 1);
5326 var canadaDay = july_1.getDay() === 0 ? 2 : 1;
5327
5328 function firstWeekdayOfMonth(month, weekday){
5329 var first = new Date(Y, month, 1);
5330 return 1 + ((7 + weekday - first.getDay()) % 7);
5331 }
5332
5333 function lastWeekdayOfMonth(month, weekday){
5334 var last = new Date(Y, month+1, 0);
5335 var offset=((7 + last.getDay() - weekday) % 7);
5336 return last.getDate() - offset;
5337 }
5338
5339 return {
5340 'easter' : new Date(Y, M - 1, D),
5341 'orthodox easter' : oDate,
5342 'victoriaDay' : new Date(Y, 4, victoriaDay),
5343 'canadaDay' : new Date(Y, 6, canadaDay),
5344 'firstJanuaryMonday' : new Date(Y, 0, firstWeekdayOfMonth(0, 1)),
5345 'firstFebruaryMonday' : new Date(Y, 1, firstWeekdayOfMonth(1, 1)),
5346 'lastFebruarySunday' : new Date(Y, 1, lastFebruarySunday),
5347 'firstMarchMonday' : new Date(Y, 2, firstWeekdayOfMonth(2, 1)),
5348 'firstAprilMonday' : new Date(Y, 3, firstWeekdayOfMonth(3, 1)),
5349 'firstMayMonday' : new Date(Y, 4, firstWeekdayOfMonth(4, 1)),
5350 'firstJuneMonday' : new Date(Y, 5, firstWeekdayOfMonth(5, 1)),
5351 'firstJulyMonday' : new Date(Y, 6, firstWeekdayOfMonth(6, 1)),
5352 'firstAugustMonday' : new Date(Y, 7, firstWeekdayOfMonth(7, 1)),
5353 'firstSeptemberMonday' : new Date(Y, 8, firstWeekdayOfMonth(8, 1)),
5354 'firstSeptemberSunday' : new Date(Y, 8, firstWeekdayOfMonth(8, 0)),
5355 'firstOctoberMonday' : new Date(Y, 9, firstWeekdayOfMonth(9, 1)),
5356 'firstNovemberMonday' : new Date(Y, 10, firstWeekdayOfMonth(10, 1)),
5357 'firstMarchTuesday' : new Date(Y, 2, firstWeekdayOfMonth(2, 2)),
5358 'firstAugustTuesday' : new Date(Y, 7, firstWeekdayOfMonth(7, 2)),
5359 'firstAugustFriday' : new Date(Y, 7, firstWeekdayOfMonth(7, 5)),
5360 'firstNovemberThursday' : new Date(Y, 10, firstWeekdayOfMonth(10, 4)),
5361 'lastMayMonday' : new Date(Y, 4, lastWeekdayOfMonth(4, 1)),
5362 'lastMarchMonday' : new Date(Y, 2, lastWeekdayOfMonth(2, 1)),
5363 'lastAprilMonday' : new Date(Y, 3, lastWeekdayOfMonth(3, 1)),
5364 'lastAprilFriday' : new Date(Y, 3, lastWeekdayOfMonth(3, 5)),
5365 'lastOctoberFriday' : new Date(Y, 9, lastWeekdayOfMonth(9, 5)),
5366 };
5367 }
5368
5369 function getApplyingHolidaysForYear(applying_holidays, year, add_days) {
5370 var movableDays = getMovableEventsForYear(year);
5371
5372 var sorted_holidays = [];
5373 var next_holiday;
5374
5375 for (var holiday_name in applying_holidays) {
5376 if (typeof applying_holidays[holiday_name][0] === 'string') {
5377 var selected_movableDay = movableDays[applying_holidays[holiday_name][0]];
5378 if (!selected_movableDay)
5379 throw 'Movable day ' + applying_holidays[holiday_name][0] + ' can not not be calculated.'
5380 + ' Please add the formula how to calculate it.';
5381 next_holiday = new Date(selected_movableDay.getFullYear(),
5382 selected_movableDay.getMonth(),
5383 selected_movableDay.getDate()
5384 + applying_holidays[holiday_name][1]
5385 );
5386 if (year !== next_holiday.getFullYear())
5387 throw 'The movable day ' + applying_holidays[holiday_name][0] + ' plus '
5388 + applying_holidays[holiday_name][1]
5389 + ' days is not in the year of the movable day anymore. Currently not supported.';
5390 } else {
5391 next_holiday = new Date(year,
5392 applying_holidays[holiday_name][0] - 1,
5393 applying_holidays[holiday_name][1]
5394 );
5395 }
5396 if (add_days[0])
5397 next_holiday.setDate(next_holiday.getDate() + add_days[0]);
5398
5399 sorted_holidays.push([ next_holiday, holiday_name ]);
5400 }
5401
5402 sorted_holidays = sorted_holidays.sort(function(a,b){
5403 if (a[0].getTime() < b[0].getTime()) return -1;
5404 if (a[0].getTime() > b[0].getTime()) return 1;
5405 return 0;
5406 });
5407
5408 return sorted_holidays;
5409 }
5410 // }}}
5411 // }}}
5412
5413 /* Year range parser (2013,2016-2018,2020/2). {{{
5414 *
5415 * :param tokens: List of token objects.
5416 * :param at: Position where to start.
5417 * :returns: Position at which the token does not belong to the selector anymore.
5418 */
5419 function parseYearRange(tokens, at) {
5420 tokens[at][3] = 'year';
5421 for (; at < tokens.length; at++) {
5422 if (matchTokens(tokens, at, 'year')) {
5423 var is_range = false,
5424 has_period,
5425 period;
5426 if (matchTokens(tokens, at+1, '-', 'year', '/', 'number')) {
5427 is_range = true;
5428 has_period = true;
5429 period = parseInt(tokens[at+4][0]);
5430 checkPeriod(at+4, period, 'year');
5431 } else {
5432 is_range = matchTokens(tokens, at+1, '-', 'year');
5433 has_period = matchTokens(tokens, at+1, '/', 'number');
5434 if (has_period) {
5435 period = parseInt(tokens[at+2][0]);
5436 checkPeriod(at+2, period, 'year', 'no_end_year');
5437 } else if (matchTokens(tokens, at+1, '+')) {
5438 period = 1;
5439 has_period = 2;
5440 }
5441 }
5442
5443 var year_from = parseInt(tokens[at][0]);
5444 // error checking {{{
5445 if (is_range && tokens[at+2][0] <= year_from) {
5446 // handle reversed range
5447 if (tokens[at+2][0] === year_from) {
5448 throw formatWarnErrorMessage(nrule, at,
5449 'A year range in which the start year is equal to the end year does not make sense.'
5450 + ' Please remove the end year. E.g. "' + year_from + ' May 23"');
5451 } else {
5452 throw formatWarnErrorMessage(nrule, at,
5453 'A year range in which the start year is greater than the end year does not make sense.'
5454 + ' Please turn it over.');
5455 }
5456 }
5457 // }}}
5458
5459 selectors.year.push(function(tokens, at, year_from, is_range, has_period, period) { return function(date) {
5460 var ouryear = date.getFullYear();
5461 var year_to = is_range ? parseInt(tokens[at+2][0]) : year_from;
5462
5463 if (ouryear < year_from ){
5464 return [false, new Date(year_from, 0, 1)];
5465 } else if (has_period) {
5466 if (year_from <= ouryear) {
5467 if (is_range && ouryear > year_to)
5468 return [false];
5469 if (period > 0) {
5470 if ((ouryear - year_from) % period === 0) {
5471 return [true, new Date(ouryear + 1, 0, 1)];
5472 } else {
5473 return [false, new Date(ouryear + period - 1, 0, 1)];
5474 }
5475 }
5476 }
5477 } else if (is_range) {
5478 if (ouryear <= year_to)
5479 return [true, new Date(year_to + 1, 0, 1)];
5480 } else if (ouryear === year_from) {
5481 return [true];
5482 }
5483
5484 return [false];
5485
5486 }}(tokens, at, year_from, is_range, has_period, period));
5487
5488 at += 1 + (is_range ? 2 : 0) + (has_period ? (has_period === 2 ? 1 : 2) : 0);
5489 } else if (matchTokens(tokens, at - 1, ',')) { // additional rule
5490 throw formatWarnErrorMessage(
5491 nrule,
5492 at - 1,
5493 'An additional rule does not make sense here. Just use a ";" as rule separator.'
5494 + ' See https://wiki.openstreetmap.org/wiki/Key:opening_hours/specification#explain:additional_rule_separator');
5495 } else {
5496 throw formatWarnErrorMessage(nrule, at, 'Unexpected token in year range: ' + tokens[at][1]);
5497 }
5498
5499 if (!matchTokens(tokens, at, ','))
5500 break;
5501 }
5502
5503 return at;
5504 }
5505 // }}}
5506
5507 /* Week range parser (week 11-20, week 1-53/2). {{{
5508 *
5509 * :param tokens: List of token objects.
5510 * :param at: Position where to start.
5511 * :returns: Position at which the token does not belong to the selector anymore.
5512 */
5513 function parseWeekRange(tokens, at) {
5514 for (; at < tokens.length; at++) {
5515 if (matchTokens(tokens, at, 'week')) {
5516 at++;
5517 }
5518 if (matchTokens(tokens, at, 'number')) {
5519 var is_range = matchTokens(tokens, at+1, '-', 'number'), period = 0;
5520 var week_from = tokens[at][0];
5521 var week_to = is_range ? tokens[at+2][0] : week_from;
5522 if (week_from > week_to) {
5523 throw formatWarnErrorMessage(nrule, at+2,
5524 'You have specified a week range in reverse order or leaping over a year. This is (currently) not supported.');
5525 }
5526 if (week_from < 1) {
5527 throw formatWarnErrorMessage(nrule, at,
5528 'You have specified a week date less then one. A valid week date range is 1-53.');
5529 }
5530 if (week_to > 53) {
5531 throw formatWarnErrorMessage(nrule, is_range ? at+2 : at,
5532 'You have specified a week date greater then 53. A valid week date range is 1-53.');
5533 }
5534 if (is_range) {
5535 period = matchTokens(tokens, at+3, '/', 'number');
5536 if (period) {
5537 period = tokens[at+4][0];
5538 if (period < 2) {
5539 throw formatWarnErrorMessage(nrule, at+4,
5540 'You have specified a week period which is less than two.'
5541 + ' If you want to select the whole range from week ' + week_from + ' to week ' + week_to + ' then just omit the "/' + period + '".');
5542 } else if (period > 26) {
5543 throw formatWarnErrorMessage(nrule, at+4,
5544 'You have specified a week period which is greater than 26.'
5545 + ' 26.5 is the half of the maximum 53 week dates per year so a week date period greater than 26 would only apply once per year.'
5546 + ' Please specify the week selector as "week ' + week_from + '" if that is what you want to express.');
5547 }
5548 }
5549 }
5550
5551 if (week_stable && (!(week_from <= 1 && week_to >= 53) || period)) {
5552 week_stable = false;
5553 }
5554
5555 if (!period && week_from === 1 && week_to === 53) {
5556 /* Shortcut and work around bug. */
5557 selectors.week.push(function(date) { return [true]; });
5558 } else {
5559
5560 selectors.week.push(function(week_from, week_to, is_range, period) { return function(date) {
5561 var ourweek = getWeekNumber(date);
5562
5563 // console.log("week_from: %s, week_to: %s", week_from, week_to);
5564 // console.log("ourweek: %s, date: %s", ourweek, date);
5565
5566 // before range
5567 if (ourweek < week_from) {
5568 // console.log("Before: " + getNextDateOfISOWeek(week_from, date));
5569 return [false, getNextDateOfISOWeek(week_from, date)];
5570 }
5571
5572 // we're after range, set check date to next year
5573 if (ourweek > week_to) {
5574 // console.log("After");
5575 return [false, getNextDateOfISOWeek(week_from, date)];
5576 }
5577
5578 // we're in range
5579 if (period) {
5580 var in_period = (ourweek - week_from) % period === 0;
5581 if (in_period) {
5582 return [true, getNextDateOfISOWeek(ourweek + 1, date)];
5583 } else {
5584 return [false, getNextDateOfISOWeek(ourweek + period - 1, date)];
5585 }
5586 }
5587
5588 // console.log("Match");
5589 return [true, getNextDateOfISOWeek(week_to === 53 ? 1 : week_to + 1, date)];
5590 }}(week_from, week_to, is_range, period));
5591 }
5592
5593 at += 1 + (is_range ? 2 : 0) + (period ? 2 : 0);
5594 } else if (matchTokens(tokens, at - 1, ',')) { // additional rule
5595 throw formatWarnErrorMessage(
5596 nrule,
5597 at - 1,
5598 'An additional rule does not make sense here. Just use a ";" as rule separator.'
5599 + ' See https://wiki.openstreetmap.org/wiki/Key:opening_hours/specification#explain:additional_rule_separator');
5600 } else {
5601 throw formatWarnErrorMessage(nrule, at, 'Unexpected token in week range: ' + tokens[at][1]);
5602 }
5603
5604 if (!matchTokens(tokens, at, ','))
5605 break;
5606 }
5607
5608 return at;
5609 }
5610
5611 // http://stackoverflow.com/a/6117889
5612 /* For a given date, get the ISO week number
5613 *
5614 * Based on information at:
5615 *
5616 * http://www.merlyn.demon.co.uk/weekcalc.htm#WNR
5617 *
5618 * Algorithm is to find nearest thursday, it's year
5619 * is the year of the week number. Then get weeks
5620 * between that date and the first day of that year.
5621 *
5622 * Note that dates in one year can be weeks of previous
5623 * or next year, overlap is up to 3 days.
5624 *
5625 * e.g. 2014/12/29 is Monday in week 1 of 2015
5626 * 2012/1/1 is Sunday in week 52 of 2011
5627 */
5628 function getWeekNumber(d) {
5629 // Copy date so don't modify original
5630 d = new Date(+d);
5631 d.setHours(0,0,0);
5632 // Set to nearest Thursday: current date + 4 - current day number
5633 // Make Sunday's day number 7
5634 d.setDate(d.getDate() + 4 - (d.getDay()||7));
5635 // Get first day of year
5636 var yearStart = new Date(d.getFullYear(),0,1);
5637 // Calculate full weeks to nearest Thursday
5638 return Math.ceil(( ( (d - yearStart) / 86400000) + 1)/7)
5639 }
5640 // http://stackoverflow.com/a/16591175
5641 function getDateOfISOWeek(w, y) {
5642 var simple = new Date(y, 0, 1 + (w - 1) * 7);
5643 var dow = simple.getDay();
5644 var ISOweekStart = simple;
5645 if (dow <= 4)
5646 ISOweekStart.setDate(simple.getDate() - simple.getDay() + 1);
5647 else
5648 ISOweekStart.setDate(simple.getDate() + 8 - simple.getDay());
5649 return ISOweekStart;
5650 }
5651 function getNextDateOfISOWeek(week, date) {
5652 var next_date;
5653 for (var i = -1; i <= 1; i++) {
5654 next_date = getDateOfISOWeek(week, date.getFullYear() + i);
5655 if (next_date.getTime() > date.getTime()) {
5656 return next_date;
5657 }
5658 }
5659 throw formatLibraryBugMessage();
5660 }
5661 // }}}
5662
5663 /* Month range parser (Jan,Feb-Mar). {{{
5664 *
5665 * :param tokens: List of token objects.
5666 * :param at: Position where to start.
5667 * :param 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).
5668 * :returns: Position at which the token does not belong to the selector anymore.
5669 */
5670 function parseMonthRange(tokens, at, push_to_monthday, in_selector) {
5671 if (!in_selector)
5672 tokens[at][3] = 'month';
5673
5674 for (; at < tokens.length; at++) {
5675 // Use parseMonthdayRange if '<month> <daynum>' and not '<month> <hour>:<minute>'
5676 if (matchTokens(tokens, at, 'month', 'number') && !matchTokens(tokens, at+2, 'timesep', 'number')) {
5677 return parseMonthdayRange(tokens, at, nrule, true);
5678 } else if (matchTokens(tokens, at, 'month')) {
5679 // Single month (Jan) or month range (Feb-Mar)
5680 var is_range = matchTokens(tokens, at+1, '-', 'month');
5681
5682 var month_from = tokens[at][0];
5683 var month_to = is_range ? tokens[at+2][0] : month_from;
5684
5685 if (is_range && week_stable) {
5686 if (month_from !== (month_to + 1) % 12)
5687 week_stable = false;
5688 } else {
5689 week_stable = false;
5690 }
5691
5692 var inside = true;
5693
5694 // handle reversed range
5695 if (month_to < month_from) {
5696 var tmp = month_to;
5697 month_to = month_from - 1;
5698 month_from = tmp + 1;
5699 inside = false;
5700 }
5701
5702 var selector = function(tokens, at, month_from, month_to, is_range, inside) { return function(date) {
5703 var ourmonth = date.getMonth();
5704
5705 // handle full range
5706 if (month_to < month_from)
5707 return [!inside];
5708
5709 if (ourmonth < month_from || ourmonth > month_to) {
5710 return [!inside, dateAtNextMonth(date, month_from)];
5711 } else {
5712 return [inside, dateAtNextMonth(date, month_to + 1)];
5713 }
5714 }}(tokens, at, month_from, month_to, is_range, inside);
5715
5716 if (push_to_monthday === true)
5717 selectors.monthday.push(selector);
5718 else
5719 selectors.month.push(selector);
5720
5721 at += is_range ? 3 : 1;
5722 } else {
5723 throw formatWarnErrorMessage(nrule, at, 'Unexpected token in month range: ' + tokens[at][1]);
5724 }
5725
5726 if (!matchTokens(tokens, at, ','))
5727 break;
5728 }
5729
5730 return at;
5731 }
5732
5733 function dateAtNextMonth(date, month) {
5734 return new Date(date.getFullYear(), month < date.getMonth() ? month + 12 : month);
5735 }
5736 // }}}
5737
5738 /* Month day range parser (Jan 26-31; Jan 26-Feb 26). {{{
5739 *
5740 * :param tokens: List of token objects.
5741 * :param at: Position where to start.
5742 * :param nrule: Rule number starting with 0.
5743 * :param 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).
5744 * :returns: Position at which the token does not belong to the selector anymore.
5745 */
5746 function parseMonthdayRange(tokens, at, nrule, push_to_month) {
5747 if (!push_to_month)
5748 tokens[at][3] = 'month';
5749
5750 for (; at < tokens.length; at++) {
5751 var has_year = [], has_month = [], has_event = [], has_calc = [], has_constrained_weekday = [];
5752 has_year[0] = matchTokens(tokens, at, 'year');
5753 has_month[0] = matchTokens(tokens, at+has_year[0], 'month', 'number');
5754 has_event[0] = matchTokens(tokens, at+has_year[0], 'event');
5755
5756 if (has_event[0])
5757 has_calc[0] = getMoveDays(tokens, at+has_year[0]+1, 200, 'event like easter');
5758
5759 var at_range_sep;
5760 if (matchTokens(tokens, at+has_year[0], 'month', 'weekday', '[')) {
5761 has_constrained_weekday[0] = getConstrainedWeekday(tokens, at+has_year[0]+3);
5762 has_calc[0] = getMoveDays(tokens, has_constrained_weekday[0][1], 6, 'constrained weekdays');
5763 at_range_sep = has_constrained_weekday[0][1] + (typeof has_calc[0] !== 'undefined' && has_calc[0][1] ? 3 : 0);
5764 } else {
5765 at_range_sep = at+has_year[0]
5766 + (has_event[0]
5767 ? (typeof has_calc[0] !== 'undefined' && has_calc[0][1] ? 4 : 1)
5768 : 2);
5769 }
5770
5771 var at_sec_event_or_month;
5772 if ((has_month[0] || has_event[0] || has_constrained_weekday[0]) && matchTokens(tokens, at_range_sep, '-')) {
5773 has_year[1] = matchTokens(tokens, at_range_sep+1, 'year');
5774 at_sec_event_or_month = at_range_sep+1+has_year[1];
5775 has_month[1] = matchTokens(tokens, at_sec_event_or_month, 'month', 'number');
5776 if (!has_month[1]) {
5777 has_event[1] = matchTokens(tokens, at_sec_event_or_month, 'event');
5778 if (has_event[1]) {
5779 has_calc[1] = getMoveDays(tokens, at_sec_event_or_month+1, 366, 'event like easter');
5780 } else if (matchTokens(tokens, at_sec_event_or_month, 'month', 'weekday', '[')) {
5781 has_constrained_weekday[1] = getConstrainedWeekday(tokens, at_sec_event_or_month+3);
5782 has_calc[1] = getMoveDays(tokens, has_constrained_weekday[1][1], 6, 'constrained weekdays');
5783 }
5784 }
5785 }
5786
5787 // monthday range like Jan 26-Feb 26 {{{
5788 if (has_year[0] === has_year[1] && (has_month[1] || has_event[1] || has_constrained_weekday[1])) {
5789
5790 if (has_month[0])
5791 checkIfDateIsValid(tokens[at+has_year[0]][0], tokens[at+has_year[0]+1][0], nrule, at+has_year[0]+1);
5792 if (has_month[1])
5793 checkIfDateIsValid(tokens[at_sec_event_or_month][0], tokens[at_sec_event_or_month+1][0], nrule, at_sec_event_or_month+1);
5794
5795 var selector = function(tokens, at, nrule, has_year, has_event, has_calc, at_sec_event_or_month, has_constrained_weekday) { return function(date) {
5796 var start_of_next_year = new Date(date.getFullYear() + 1, 0, 1);
5797
5798 var movableDays,
5799 from_date;
5800 if (has_event[0]) {
5801 movableDays = getMovableEventsForYear(has_year[0] ? parseInt(tokens[at][0]) : date.getFullYear());
5802 from_date = movableDays[tokens[at+has_year[0]][0]];
5803
5804 if (typeof has_calc[0] !== 'undefined' && has_calc[0][1]) {
5805 var from_year_before_calc = from_date.getFullYear();
5806 from_date.setDate(from_date.getDate() + has_calc[0][0]);
5807 if (from_year_before_calc !== from_date.getFullYear())
5808 throw formatWarnErrorMessage(nrule, at+has_year[0]+has_calc[0][1]*3,
5809 'The movable day ' + tokens[at+has_year[0]][0] + ' plus ' + has_calc[0][0]
5810 + ' days is not in the year of the movable day anymore. Currently not supported.');
5811 }
5812 } else if (has_constrained_weekday[0]) {
5813 from_date = getDateForConstrainedWeekday((has_year[0] ? tokens[at][0] : date.getFullYear()), // year
5814 tokens[at+has_year[0]][0], // month
5815 tokens[at+has_year[0]+1][0], // weekday
5816 has_constrained_weekday[0],
5817 has_calc[0]);
5818 } else {
5819 from_date = new Date((has_year[0] ? tokens[at][0] : date.getFullYear()),
5820 tokens[at+has_year[0]][0], tokens[at+has_year[0]+1][0]);
5821 }
5822
5823 var to_date;
5824 if (has_event[1]) {
5825 movableDays = getMovableEventsForYear(has_year[1]
5826 ? parseInt(tokens[at_sec_event_or_month-1][0])
5827 : date.getFullYear());
5828 to_date = movableDays[tokens[at_sec_event_or_month][0]];
5829
5830 if (typeof has_calc[1] !== 'undefined' && has_calc[1][1]) {
5831 var to_year_before_calc = to_date.getFullYear();
5832 to_date.setDate(to_date.getDate() + has_calc[1][0]);
5833 if (to_year_before_calc !== to_date.getFullYear())
5834 throw formatWarnErrorMessage(nrule, at_sec_event_or_month+has_calc[1][1],
5835 'The movable day ' + tokens[at_sec_event_or_month][0] + ' plus ' + has_calc[1][0]
5836 + ' days is not in the year of the movable day anymore. Currently not supported.');
5837 }
5838 } else if (has_constrained_weekday[1]) {
5839 to_date = getDateForConstrainedWeekday((has_year[1] ? tokens[at_sec_event_or_month-1][0] : date.getFullYear()), // year
5840 tokens[at_sec_event_or_month][0], // month
5841 tokens[at_sec_event_or_month+1][0], // weekday
5842 has_constrained_weekday[1],
5843 has_calc[1]);
5844 } else {
5845 to_date = new Date((has_year[1] ? tokens[at_sec_event_or_month-1][0] : date.getFullYear()),
5846 tokens[at_sec_event_or_month][0], tokens[at_sec_event_or_month+1][0] + 1);
5847 }
5848
5849 var inside = true;
5850
5851 if (to_date < from_date) {
5852 var tmp = to_date;
5853 to_date = from_date;
5854 from_date = tmp;
5855 inside = false;
5856 }
5857
5858 if (date.getTime() < from_date.getTime()) {
5859 return [!inside, from_date];
5860 } else if (date.getTime() < to_date.getTime()) {
5861 return [inside, to_date];
5862 } else {
5863 if (has_year[0]) {
5864 return [!inside];
5865 } else {
5866 return [!inside, start_of_next_year];
5867 }
5868 }
5869 }}(tokens, at, nrule, has_year, has_event, has_calc, at_sec_event_or_month, has_constrained_weekday);
5870
5871 if (push_to_month === true)
5872 selectors.month.push(selector);
5873 else
5874 selectors.monthday.push(selector);
5875
5876 at = (has_constrained_weekday[1]
5877 ? has_constrained_weekday[1][1]
5878 : at_sec_event_or_month + (has_event[1] ? 1 : 2))
5879 + (typeof has_calc[1] !== 'undefined' ? has_calc[1][1] : 0);
5880
5881 // }}}
5882 // Monthday range like Jan 26-31 {{{
5883 } else if (has_month[0]) {
5884
5885 has_year = has_year[0];
5886 var year = tokens[at][0]; // Could be month if has no year. Tested later.
5887 var month = tokens[at+has_year][0];
5888
5889 var first_round = true;
5890
5891 do {
5892 var range_from = tokens[at+1 + has_year][0];
5893 var is_range = matchTokens(tokens, at+2+has_year, '-', 'number');
5894 var period = undefined;
5895 var range_to = tokens[at+has_year+(is_range ? 3 : 1)][0] + 1;
5896 if (is_range && matchTokens(tokens, at+has_year+4, '/', 'number')) {
5897 period = tokens[at+has_year+5][0];
5898 checkPeriod(at+has_year+5, period, 'day');
5899 }
5900
5901 if (first_round) {
5902 var at_timesep_if_monthRange = at + has_year + 1 // at month number
5903 + (is_range ? 2 : 0) + (period ? 2 : 0)
5904 + !(is_range || period); // if not range nor has period, add one
5905
5906 // Check for '<month> <timespan>'
5907 if (matchTokens(tokens, at_timesep_if_monthRange, 'timesep', 'number')
5908 && (matchTokens(tokens, at_timesep_if_monthRange+2, '+')
5909 || matchTokens(tokens, at_timesep_if_monthRange+2, '-')
5910 || oh_mode !== 0)) {
5911 return parseMonthRange(tokens, at, true, true);
5912 }
5913 }
5914
5915 // error checking {{{
5916 if (range_to < range_from)
5917 throw formatWarnErrorMessage(nrule, at+has_year+3,
5918 'Range in wrong order. From day is greater than to day.');
5919
5920 checkIfDateIsValid(month, range_from, nrule, at+1 + has_year);
5921 checkIfDateIsValid(month, range_to - 1 /* added previously */,
5922 nrule, at+has_year+(is_range ? 3 : 1));
5923 // }}}
5924
5925 var selector = function(year, has_year, month, range_from, range_to, period) { return function(date) {
5926 var start_of_next_year = new Date(date.getFullYear() + 1, 0, 1);
5927
5928 var from_date = new Date(has_year ? year : date.getFullYear(),
5929 month, range_from);
5930 if (month === 1 && range_from !== from_date.getDate()) // Only on leap years does this day exist.
5931 return [false]; // If day 29 does not exist,
5932 // then the date object adds one day to date
5933 // and this selector should not match.
5934 var to_date = new Date(from_date.getFullYear(),
5935 month, range_to);
5936 if (month === 1 && is_range && range_to !== to_date.getDate()) // Only on leap years does this day exist.
5937 return [false];
5938
5939 if (date.getTime() < from_date.getTime())
5940 return [false, from_date];
5941 else if (date.getTime() >= to_date.getTime())
5942 return [false, start_of_next_year];
5943 else if (!period)
5944 return [true, to_date];
5945
5946 var nday = Math.floor((date.getTime() - from_date.getTime()) / msec_in_day);
5947 var in_period = nday % period;
5948
5949 if (in_period === 0)
5950 return [true, new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1)];
5951 else
5952 return [false, new Date(date.getFullYear(), date.getMonth(), date.getDate() + period - in_period)];
5953
5954 }}(year, has_year, month, range_from, range_to, period);
5955
5956 if (push_to_month === true)
5957 selectors.month.push(selector);
5958 else
5959 selectors.monthday.push(selector);
5960
5961 at += 2 + has_year + (is_range ? 2 : 0) + (period ? 2 : 0);
5962
5963 first_round = false;
5964 }
5965 while (matchTokens(tokens, at, ',', 'number'))
5966
5967
5968 // }}}
5969 // Only event like easter {{{
5970 } else if (has_event[0]) {
5971
5972 var selector = function(tokens, at, nrule, has_year, add_days) { return function(date) {
5973
5974 // console.log('enter selector with date: ' + date);
5975 var movableDays = getMovableEventsForYear((has_year ? tokens[at][0] : date.getFullYear()));
5976 var event_date = movableDays[tokens[at+has_year][0]];
5977 if (!event_date)
5978 throw 'Movable day ' + tokens[at+has_year][0] + ' can not not be calculated.'
5979 + ' Please add the formula how to calculate it.';
5980
5981 if (add_days[0]) {
5982 event_date.setDate(event_date.getDate() + add_days[0]);
5983 if (date.getFullYear() !== event_date.getFullYear())
5984 throw formatWarnErrorMessage(nrule, at+has_year+add_days[1], 'The movable day ' + tokens[at+has_year][0] + ' plus '
5985 + add_days[0]
5986 + ' days is not in the year of the movable day anymore. Currently not supported.');
5987 }
5988
5989 if (date.getTime() < event_date.getTime())
5990 return [false, event_date];
5991 // else if (date.getTime() < event_date.getTime() + msec_in_day) // does not work because of daylight saving times
5992 else if (event_date.getMonth() * 100 + event_date.getDate() === date.getMonth() * 100 + date.getDate())
5993 return [true, new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1)];
5994 else
5995 return [false, new Date(date.getFullYear() + 1, 0, 1)];
5996
5997 }}(tokens, at, nrule, has_year[0], has_calc[0]);
5998
5999 if (push_to_month === true)
6000 selectors.month.push(selector);
6001 else
6002 selectors.monthday.push(selector);
6003
6004 at += has_year[0] + has_event[0] + (typeof has_calc[0][1] !== 'undefined' && has_calc[0][1] ? 3 : 0);
6005 // }}}
6006 } else if (has_constrained_weekday[0]) {
6007 at = parseMonthRange(tokens, at);
6008 } else if (matchTokens(tokens, at, 'month')) {
6009 return parseMonthRange(tokens, at, true, true);
6010 } else {
6011 // throw 'Unexpected token in monthday range: "' + tokens[at] + '"';
6012 return at;
6013 }
6014
6015 if (!matchTokens(tokens, at, ','))
6016 break;
6017 }
6018
6019 return at;
6020 }
6021 // }}}
6022
6023 /* Main selector traversal function (return state array for date). {{{
6024 * Checks for given date which rule and those which state and comment applies.
6025 *
6026 * :param date: Date object.
6027 * :returns: Array:
6028 * 0. resultstate: State: true for 'open', false for 'closed'.
6029 * 1. changedate: Next change as date object.
6030 * 2. unknown: true if state open is not sure.
6031 * 3. comment: Comment which applies for this time range (from date to changedate).
6032 * 4. match_rule: Rule number starting with 0 (nrule).
6033 */
6034 this.getStatePair = function(date) {
6035 var resultstate = false;
6036 var changedate;
6037 var unknown = false;
6038 var comment;
6039 var match_rule;
6040
6041 var date_matching_rules = [];
6042
6043 /* Go though all date selectors and check if they return something
6044 * else than closed for the given date.
6045 */
6046 for (var nrule = 0; nrule < rules.length; nrule++) {
6047 var matching_date_rule = true;
6048 // console.log(nrule, 'length', rules[nrule].date.length);
6049
6050 /* Try each date selector type. */
6051 for (var ndateselector = 0; ndateselector < rules[nrule].date.length; ndateselector++) {
6052 var dateselectors = rules[nrule].date[ndateselector];
6053 // console.log(nrule, ndateselector);
6054
6055 var has_matching_selector = false;
6056 for (var datesel = 0; datesel < dateselectors.length; datesel++) {
6057 var res = dateselectors[datesel](date);
6058 if (res[0]) {
6059 has_matching_selector = true;
6060
6061 if (typeof res[2] === 'string') { // holiday name
6062 comment = [ res[2], nrule ];
6063 }
6064
6065 }
6066 if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1].getTime() < changedate.getTime()))
6067 changedate = res[1];
6068 }
6069
6070 if (!has_matching_selector) {
6071 matching_date_rule = false;
6072 // We can ignore other date selectors, as the state won't change
6073 // anyway until THIS selector matches (due to conjunction of date
6074 // selectors of different types).
6075 // This is also an optimization, if widest date selector types
6076 // are checked first.
6077 break;
6078 }
6079 }
6080
6081 if (matching_date_rule) {
6082 /* The following lines implement date overwriting logic (e.g. for
6083 * "Mo-Fr 10:00-20:00; We 10:00-16:00", We rule overrides Mo-Fr rule partly (We).
6084 *
6085 * 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:
6086 * 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.
6087 */
6088 if ((rules[nrule].date.length > 0 || nrule > 0 && rules[nrule].meaning && rules[nrule-1].date.length === 0)
6089 && (rules[nrule].meaning || rules[nrule].unknown)
6090 && !rules[nrule].wrapped && !rules[nrule].additional && !rules[nrule].fallback
6091 ) {
6092
6093 // var old_date_matching_rules = date_matching_rules;
6094 date_matching_rules = [];
6095 // for (var nrule = 0; nrule < old_date_matching_rules.length; nrule++) {
6096 // if (!rules[old_date_matching_rules[nrule]].wrapped)
6097 // date_matching_rules.push(nrule);
6098 // }
6099 }
6100 date_matching_rules.push(nrule);
6101 }
6102 }
6103
6104 // console.log(date_matching_rules);
6105 rule:
6106 for (var nrule = 0; nrule < date_matching_rules.length; nrule++) {
6107 var rule = date_matching_rules[nrule];
6108
6109 // console.log('Processing rule ' + rule + ': with date ' + date
6110 // + ' and ' + rules[rule].time.length + ' time selectors (comment: "' + rules[rule].comment + '").');
6111
6112 /* There is no time specified, state applies to the whole day. */
6113 if (rules[rule].time.length === 0) {
6114 // console.log('there is no time', date);
6115 if (!rules[rule].fallback || (rules[rule].fallback && !(resultstate || unknown))) {
6116 resultstate = rules[rule].meaning;
6117 unknown = rules[rule].unknown;
6118 match_rule = rule;
6119
6120 // if (rules[rule].fallback)
6121 // break rule; // fallback rule matched, no need for checking the rest
6122 // WRONG: What if closing rules follow?
6123 }
6124 }
6125
6126 for (var timesel = 0; timesel < rules[rule].time.length; timesel++) {
6127 var res = rules[rule].time[timesel](date);
6128
6129 // console.log('res:', res);
6130 if (res[0]) {
6131 if (!rules[rule].fallback || (rules[rule].fallback && !(resultstate || unknown))) {
6132 resultstate = rules[rule].meaning;
6133 unknown = rules[rule].unknown;
6134 match_rule = rule;
6135
6136 /* Reset open end comment */
6137 if (typeof comment === 'object' && comment[0] === 'Specified as open end. Closing time was guessed.')
6138 comment = undefined;
6139
6140 // open end
6141 if (res[2] === true && (resultstate || unknown)) {
6142 comment = [ 'Specified as open end. Closing time was guessed.', match_rule ];
6143
6144 resultstate = false;
6145 unknown = true;
6146
6147 /* Hack to make second rule in '07:00+,12:00-16:00; 16:00-24:00 closed "needed because of open end"' obsolete {{{ */
6148 if (typeof rules[rule].time[timesel+1] === 'function') {
6149
6150 var next_res = rules[rule].time[timesel+1](date);
6151 if ( !next_res[0]
6152 // && next_res[2]
6153 && typeof next_res[1] === 'object'
6154 // && getValueForDate(next_res[1], true) !== getValueForDate(date, true) // Just to be sure.
6155 && rules[rule].time[timesel](new Date(date.getTime() - 1))[0]
6156 /* To distinguish the following two values:
6157 * 'sunrise-14:00,14:00+',
6158 * '07:00+,12:00-16:00',
6159 */
6160 ) {
6161
6162 // console.log("07:00+,12:00-16:00 matched.");
6163
6164 resultstate = false;
6165 unknown = false;
6166 }
6167 }
6168
6169 /* Hack to handle '17:00+,13:00-02:00' {{{ */
6170 /* Not enabled. To complicated, just don‘t use them …
6171 * It gets even crazier …
6172 * Time wrapping over midnight is
6173 * stored in the next internal rule:
6174 * '17:00-00:00 unknown "Specified as open end. Closing time was guessed.", 13:00-00:00 open' // First internal rule.
6175 * + ', ' overwritten part: 00:00-03:00 open + '00:00-02:00 open', // Second internal rule.
6176 */
6177 if ( false
6178 && typeof rules[rule-1] === 'object'
6179 && rules[rule].build_from_token_rule.toString() === rules[rule-1].build_from_token_rule.toString()
6180 && typeof rules[rule] === 'object'
6181 && rules[rule].build_from_token_rule.toString() === rules[rule].build_from_token_rule.toString()
6182 ) {
6183
6184 var last_wrapping_time_selector = rules[rule].time[rules[rule].time.length - 1];
6185 var last_w_res = last_wrapping_time_selector(new Date(date.getTime() - 1));
6186 // console.log(last_w_res);
6187
6188 if ( last_w_res[0]
6189 && typeof last_w_res[2] === 'undefined'
6190 && (typeof last_w_res[2] === 'undefined' || last_w_res[2] === false) // Do not match for 'Tu 23:59-40:00+'
6191 && typeof last_w_res[1] === 'object'
6192 && date.getTime() === last_w_res[1].getTime()
6193 ) {
6194
6195 // '05:00-06:00,17:00+,13:00-02:00',
6196
6197 // console.log("17:00+,13:00-02:00 matched.");
6198 // console.log(JSON.stringify(rules, null, ' '));
6199
6200 resultstate = false;
6201 unknown = false;
6202 }
6203 /* }}} */
6204 }
6205 /* }}} */
6206 }
6207
6208 if (rules[rule].fallback) {
6209 if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1] < changedate))
6210 changedate = res[1];
6211
6212 // break rule; // Fallback rule matched, no need for checking the rest.
6213 // WRONG: What if 'off' is used after fallback rule.
6214 }
6215 }
6216 }
6217 if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1] < changedate))
6218 changedate = res[1];
6219 }
6220 }
6221
6222 if (typeof rules[match_rule] === 'object' && typeof rules[match_rule].comment === 'string') {
6223 /* Only use comment if one is explicitly specified. */
6224 comment = rules[match_rule].comment;
6225 } else if (typeof comment === 'object') {
6226 if (comment[1] === match_rule) {
6227 comment = comment[0];
6228 } else {
6229 comment = undefined;
6230 }
6231 }
6232
6233 // console.log('changedate', changedate, resultstate, comment, match_rule);
6234 return [ resultstate, changedate, unknown, comment, match_rule ];
6235 };
6236 // }}}
6237
6238 /* Generate prettified value for selector based on tokens. {{{
6239 *
6240 * :param tokens: List of token objects.
6241 * :param at: Position where to start.
6242 * :param last_at: Position where to stop.
6243 * :param conf: Configuration options.
6244 * :returns: Prettified value.
6245 */
6246 function prettifySelector(tokens, selector_start, selector_end, selector_type, conf) {
6247
6248 var value = '';
6249 var at = selector_start;
6250 while (at <= selector_end) {
6251 // console.log('At: ' + at + ', token: ' + tokens[at]);
6252 if (matchTokens(tokens, at, 'weekday')) {
6253 if (!conf.leave_weekday_sep_one_day_betw
6254 && at - selector_start > 1 && (matchTokens(tokens, at-1, ',') || matchTokens(tokens, at-1, '-'))
6255 && matchTokens(tokens, at-2, 'weekday')
6256 && tokens[at][0] === (tokens[at-2][0] + 1) % 7) {
6257 value = value.substring(0, value.length - 1) + conf.sep_one_day_between;
6258 }
6259 value += weekdays[tokens[at][0]];
6260 } else if (at - selector_start > 0 // e.g. '09:0' -> '09:00'
6261 && selector_type === 'time'
6262 && matchTokens(tokens, at-1, 'timesep')
6263 && matchTokens(tokens, at, 'number')) {
6264 value += (tokens[at][0] < 10 ? '0' : '') + tokens[at][0].toString();
6265 } else if (selector_type === 'time' // e.g. '9:00' -> ' 09:00'
6266 && conf.zero_pad_hour
6267 && at !== tokens.length
6268 && matchTokens(tokens, at, 'number')
6269 && matchTokens(tokens, at+1, 'timesep')) {
6270 value += (
6271 tokens[at][0] < 10 ?
6272 (tokens[at][0] === 0 && conf.one_zero_if_hour_zero ?
6273 '' : '0') :
6274 '') + tokens[at][0].toString();
6275 } else if (selector_type === 'time' // e.g. '9-18' -> '09:00-18:00'
6276 && at + 2 <= selector_end
6277 && matchTokens(tokens, at, 'number')
6278 && matchTokens(tokens, at+1, '-')
6279 && matchTokens(tokens, at+2, 'number')) {
6280 value += (tokens[at][0] < 10 ?
6281 (tokens[at][0] === 0 && conf.one_zero_if_hour_zero ? '' : '0')
6282 : '') + tokens[at][0].toString();
6283 value += ':00-'
6284 + (tokens[at+2][0] < 10 ? '0' : '') + tokens[at+2][0].toString()
6285 + ':00';
6286 at += 2;
6287 } else if (matchTokens(tokens, at, 'comment')) {
6288 value += '"' + tokens[at][0].toString() + '"';
6289 } else if (matchTokens(tokens, at, 'closed')) {
6290 value += (conf.leave_off_closed ? tokens[at][0] : conf.keyword_for_off_closed);
6291 } else if (at - selector_start > 0 && matchTokens(tokens, at, 'number')
6292 && (matchTokens(tokens, at-1, 'month') && selector_type === 'month'
6293 || matchTokens(tokens, at-1, 'week') && selector_type === 'week'
6294 )) {
6295 value += ' '
6296 + (conf.zero_pad_month_and_week_numbers && tokens[at][0] < 10 ? '0' : '')
6297 + tokens[at][0];
6298 } else if (at - selector_start > 0 && matchTokens(tokens, at, 'month')
6299 && matchTokens(tokens, at-1, 'year')) {
6300 value += ' ' + months[[tokens[at][0]]];
6301 } else if (at - selector_start > 0 && matchTokens(tokens, at, 'event')
6302 && matchTokens(tokens, at-1, 'year')) {
6303 value += ' ' + tokens[at][0];
6304 } else if (matchTokens(tokens, at, 'month')) {
6305 value += months[[tokens[at][0]]];
6306 if (at + 1 <= selector_end && matchTokens(tokens, at+1, 'weekday'))
6307 value += ' ';
6308 } else if (at + 2 <= selector_end
6309 && (matchTokens(tokens, at, '-') || matchTokens(tokens, at, '+'))
6310 && matchTokens(tokens, at+1, 'number', 'calcday')) {
6311 value += ' ' + tokens[at][0] + tokens[at+1][0] + ' day' + (Math.abs(tokens[at+1][0]) === 1 ? '' : 's');
6312 at += 2;
6313 } else if (at === selector_end
6314 && selector_type === 'weekday'
6315 && tokens[at][0] === ':') {
6316 // Do nothing.
6317 } else {
6318 value += tokens[at][0].toString();
6319 }
6320 at++;
6321 }
6322 return value;
6323 }
6324 // }}}
6325
6326 //======================================================================
6327 // Public interface {{{
6328 // All functions below are considered public.
6329 //======================================================================
6330
6331 // Iterator interface {{{
6332 this.getIterator = function(date) {
6333 return new function(oh) {
6334 if (typeof date === 'undefined')
6335 date = new Date();
6336
6337 var prevstate = [ undefined, date, undefined, undefined, undefined ];
6338 var state = oh.getStatePair(date);
6339
6340 /* getDate {{{ */
6341 this.getDate = function() {
6342 return prevstate[1];
6343 };
6344 /* }}} */
6345
6346 /* setDate {{{ */
6347 this.setDate = function(date) {
6348 if (typeof date !== 'object')
6349 throw 'Date parameter needed.';
6350
6351 prevstate = [ undefined, date, undefined, undefined, undefined ];
6352 state = oh.getStatePair(date);
6353 };
6354 /* }}} */
6355
6356 /* getState: Check whether facility is `open' {{{ */
6357 this.getState = function() {
6358 return state[0];
6359 };
6360 /* }}} */
6361
6362 /* getUnknown: Checks whether the opening state is conditional or unknown {{{ */
6363 this.getUnknown = function() {
6364 return state[2];
6365 };
6366 /* }}} */
6367
6368 /* getStateString: Get state string. Either 'open', 'unknown' or 'closed' {{{ */
6369 this.getStateString = function(past) {
6370 return (state[0] ? 'open' : (state[2] ? 'unknown' : (past ? 'closed' : 'close')));
6371 };
6372 /* }}} */
6373
6374 /* getComment: Get the comment, undefined in none {{{ */
6375 this.getComment = function() {
6376 return state[3];
6377 };
6378 /* }}} */
6379
6380 /* getMatchingRule: Get the rule which matched thus deterrents the current state {{{ */
6381 this.getMatchingRule = function() {
6382 if (typeof state[4] === 'undefined')
6383 return undefined;
6384
6385 return rules[state[4]].build_from_token_rule[2];
6386 };
6387 /* }}} */
6388
6389 /* advance: Advances to the next position {{{ */
6390 this.advance = function(datelimit) {
6391 if (typeof datelimit === 'undefined')
6392 datelimit = new Date(prevstate[1].getTime() + msec_in_day * 366 * 5);
6393 else if (datelimit.getTime() <= prevstate[1].getTime())
6394 return false; // The limit for advance needs to be after the current time.
6395
6396 do {
6397 // open range, we won't be able to advance
6398 if (typeof state[1] === 'undefined')
6399 return false;
6400
6401 // console.log('\n' + 'previous check time:', prevstate[1]
6402 // + ', current check time:',
6403 // // (state[1].getHours() < 10 ? '0' : '') + state[1].getHours() +
6404 // // ':'+(state[1].getMinutes() < 10 ? '0' : '')+ state[1].getMinutes(), state[1].getDate(),
6405 // state[1],
6406 // (state[0] ? 'open' : (state[2] ? 'unknown' : 'closed')) + ', comment:', state[3]);
6407
6408 // We're going backwards or staying at place.
6409 // This always indicates coding error in a selector code.
6410 if (state[1].getTime() <= prevstate[1].getTime())
6411 throw 'Fatal: infinite loop in nextChange';
6412
6413 // don't advance beyond limits (same as open range)
6414 if (state[1].getTime() >= datelimit.getTime())
6415 return false;
6416
6417 // do advance
6418 prevstate = state;
6419 state = oh.getStatePair(prevstate[1]);
6420 } while (state[0] === prevstate[0] && state[2] === prevstate[2] && state[3] === prevstate[3]);
6421 return true;
6422 };
6423 /* }}} */
6424 }(this);
6425 };
6426 // }}}
6427
6428 // Simple API {{{
6429
6430 this.getState = function(date) {
6431 var it = this.getIterator(date);
6432 return it.getState();
6433 };
6434
6435 this.getUnknown = function(date) {
6436 var it = this.getIterator(date);
6437 return it.getUnknown();
6438 };
6439
6440 this.getStateString = function(date, past) {
6441 var it = this.getIterator(date);
6442 return it.getStateString(past);
6443 };
6444
6445 this.getComment = function(date) {
6446 var it = this.getIterator(date);
6447 return it.getComment();
6448 };
6449
6450 this.getMatchingRule = function(date) {
6451 var it = this.getIterator(date);
6452 return it.getMatchingRule();
6453 };
6454
6455 /* Not available for iterator API {{{ */
6456 /* getWarnings: Get warnings, empty list if none {{{ */
6457 this.getWarnings = function() {
6458 var it = this.getIterator();
6459 return getWarnings(it);
6460 };
6461 /* }}} */
6462
6463 /* prettifyValue: Get a nicely formated value {{{ */
6464 this.prettifyValue = function(argument_hash) {
6465 this.getWarnings();
6466 /* getWarnings has to be run before prettifyValue because some
6467 * decisions if a certain aspect makes sense to prettify or not
6468 * are based on the warnings.
6469 * Basically, both functions depend on each other in some way :(
6470 * See done_with_selector_reordering.
6471 */
6472 return prettifyValue(argument_hash);
6473 };
6474 /* }}} */
6475
6476 /* getNextChange: Get time of next status change {{{ */
6477 this.getNextChange = function(date, maxdate) {
6478 var it = this.getIterator(date);
6479 if (!it.advance(maxdate))
6480 return undefined;
6481 return it.getDate();
6482 };
6483 /* }}} */
6484
6485 /* isWeekStable: Checks whether open intervals are same for every week. {{{ */
6486 this.isWeekStable = function() {
6487 return week_stable;
6488 };
6489 /* }}} */
6490 /* }}} */
6491 /* }}} */
6492
6493 // High-level API {{{
6494
6495 /* getOpenIntervals: Get array of open intervals between two dates {{{ */
6496 this.getOpenIntervals = function(from, to) {
6497 var res = [];
6498
6499 var it = this.getIterator(from);
6500
6501 if (it.getState() || it.getUnknown())
6502 res.push([from, undefined, it.getUnknown(), it.getComment()]);
6503
6504 while (it.advance(to)) {
6505 if (it.getState() || it.getUnknown()) {
6506 if (res.length !== 0 && typeof res[res.length - 1][1] === 'undefined') {
6507 // last state was also open or unknown
6508 res[res.length - 1][1] = it.getDate();
6509 }
6510 res.push([it.getDate(), undefined, it.getUnknown(), it.getComment()]);
6511 } else {
6512 if (res.length !== 0 && typeof res[res.length - 1][1] === 'undefined') {
6513 // only use the first time as closing/change time and ignore closing times which might follow
6514 res[res.length - 1][1] = it.getDate();
6515 }
6516 }
6517 }
6518
6519 if (res.length > 0 && typeof res[res.length - 1][1] === 'undefined')
6520 res[res.length - 1][1] = to;
6521
6522 return res;
6523 };
6524 /* }}} */
6525
6526 /* getOpenDuration: Get total number of milliseconds a facility is open,unknown within a given date range {{{ */
6527 this.getOpenDuration = function(from, to) {
6528 // console.log('-----------');
6529
6530 var open = 0;
6531 var unknown = 0;
6532
6533 var it = this.getIterator(from);
6534 var prevdate = (it.getState() || it.getUnknown()) ? from : undefined;
6535 var prevstate = it.getState();
6536 var prevunknown = it.getUnknown();
6537
6538 while (it.advance(to)) {
6539 if (it.getState() || it.getUnknown()) {
6540
6541 if (typeof prevdate !== 'undefined') {
6542 // last state was also open or unknown
6543 if (prevunknown) //
6544 unknown += it.getDate().getTime() - prevdate.getTime();
6545 else if (prevstate)
6546 open += it.getDate().getTime() - prevdate.getTime();
6547 }
6548
6549 prevdate = it.getDate();
6550 prevstate = it.getState();
6551 prevunknown = it.getUnknown();
6552 // console.log('if', prevdate, open / (1000 * 60 * 60), unknown / (1000 * 60 * 60));
6553 } else {
6554 // console.log('else', prevdate);
6555 if (typeof prevdate !== 'undefined') {
6556 if (prevunknown)
6557 unknown += it.getDate().getTime() - prevdate.getTime();
6558 else
6559 open += it.getDate().getTime() - prevdate.getTime();
6560 prevdate = undefined;
6561 }
6562 }
6563 }
6564
6565 if (typeof prevdate !== 'undefined') {
6566 if (prevunknown)
6567 unknown += to.getTime() - prevdate.getTime();
6568 else
6569 open += to.getTime() - prevdate.getTime();
6570 }
6571
6572 return [ open, unknown ];
6573 };
6574 /* }}} */
6575 /* }}} */
6576 /* }}} */
6577 };
6578}));
6579// vim: set ts=4 sw=4 tw=0 noet foldmarker={{{,}}} foldlevel=0 foldmethod=marker :
Note: See TracBrowser for help on using the repository browser.