/* * For information see https://github.com/ypid/opening_hours.js * and the doc directory which contains internal documentation and design. */ /* jshint laxbreak: true */ /* jshint boss: true */ /* jshint loopfunc: true */ (function (root, factory) { // constants (holidays, error correction) {{{ // holidays {{{ var holidays = { 'fr': { // {{{ 'PH': { // http://fr.wikipedia.org/wiki/F%C3%AAtes_et_jours_f%C3%A9ri%C3%A9s_en_France "Jour de l'an" : [ 1, 1 ], "Vendredi saint" : [ 'easter', -2, [ 'Moselle', 'Bas-Rhin', 'Haut-Rhin', 'Guadeloupe', 'Martinique', 'Polynésie française' ] ], "Lundi de Pâques" : [ 'easter', 1 ], "Saint-Pierre-Chanel" : [ 4, 28, [ 'Wallis-et-Futuna' ] ], "Fête du Travail" : [ 5, 1 ], "Fête de la Victoire" : [ 5, 8 ], "Abolition de l'esclavage" : [ 5, 22, [ 'Martinique' ] ], "Abolition de l'esclavage" : [ 5, 27, [ 'Guadeloupe' ] ], "Jeudi de l'Ascension" : [ 'easter', 39 ], "Lundi de Pentecôte" : [ 'easter', 50 ], "Abolition de l'esclavage" : [ 6, 10, [ 'Guyane' ] ], "Fête de l'autonomie" : [ 6, 29, [ 'Polynésie française' ] ], "Fête nationale" : [ 7, 14 ], "Fête Victor Schoelcher" : [ 7, 21, [ 'Guadeloupe', 'Martinique' ] ], "Fête du Territoire" : [ 7, 29, [ 'Wallis-et-Futuna' ] ], "Assomption" : [ 8, 15 ], "Fête de la citoyenneté" : [ 9, 24, [ 'Nouvelle-Calédonie' ] ], "Toussaint" : [ 11, 1 ], "Armistice" : [ 11, 11 ], "Abolition de l'esclavage" : [ 12, 20, [ 'Réunion' ] ], "Noël" : [ 12, 25 ], "Saint-Étienne " : [ 12, 26, [ 'Moselle', 'Bas-Rhin', 'Haut-Rhin' ] ] } }, // }}} 'de': { // {{{ 'PH': { // http://de.wikipedia.org/wiki/Feiertage_in_Deutschland 'Neujahrstag' : [ 1, 1 ], // month 1, day 1, whole Germany 'Heilige Drei Könige' : [ 1, 6, [ 'Baden-Württemberg', 'Bayern', 'Sachsen-Anhalt'] ], // only in the specified states 'Tag der Arbeit' : [ 5, 1 ], // whole Germany 'Karfreitag' : [ 'easter', -2 ], // two days before easter 'Ostersonntag' : [ 'easter', 0, [ 'Brandenburg'] ], 'Ostermontag' : [ 'easter', 1 ], 'Christi Himmelfahrt' : [ 'easter', 39 ], 'Pfingstsonntag' : [ 'easter', 49, [ 'Brandenburg'] ], 'Pfingstmontag' : [ 'easter', 50 ], 'Fronleichnam' : [ 'easter', 60, [ 'Baden-Württemberg', 'Bayern', 'Hessen', 'Nordrhein-Westfalen', 'Rheinland-Pfalz', 'Saarland' ] ], 'Mariä Himmelfahrt' : [ 8, 15, [ 'Saarland'] ], 'Tag der Deutschen Einheit' : [ 10, 3 ], 'Reformationstag' : [ 10, 31, [ 'Brandenburg', 'Mecklenburg-Vorpommern', 'Sachsen', 'Sachsen-Anhalt', 'Thüringen'] ], 'Allerheiligen' : [ 11, 1, [ 'Baden-Württemberg', 'Bayern', 'Nordrhein-Westfalen', 'Rheinland-Pfalz', 'Saarland' ] ], '1. Weihnachtstag' : [ 12, 25 ], '2. Weihnachtstag' : [ 12, 26 ], // 'Silvester' : [ 12, 31 ], // for testing }, 'Baden-Württemberg': { // does only apply in Baden-Württemberg // This more specific rule set overwrites the country wide one (they are just ignored). // You may use this instead of the country wide with some // additional holidays for some states, if one state // totally disagrees about how to do holidays … // 'PH': { // '2. Weihnachtstag' : [ 12, 26 ], // }, // school holiday normally variate between states 'SH': [ // generated by convert_ical_to_json // You may can adjust this script to use other resources (for other countries) too. { name: 'Osterferien', 2005: [ 3, 24, /* to */ 3, 24, 3, 29, /* to */ 4, 2 ], 2006: [ 4, 18, /* to */ 4, 22 ], 2007: [ 4, 2, /* to */ 4, 14 ], 2008: [ 3, 17, /* to */ 3, 28 ], 2009: [ 4, 9, /* to */ 4, 9, 4, 14, /* to */ 4, 17 ], 2010: [ 4, 1, /* to */ 4, 1, 4, 6, /* to */ 4, 10 ], 2011: [ 4, 21, /* to */ 4, 21, 4, 26, /* to */ 4, 30 ], 2012: [ 4, 2, /* to */ 4, 13 ], 2013: [ 3, 25, /* to */ 4, 5 ], 2014: [ 4, 14, /* to */ 4, 25 ], 2015: [ 3, 30, /* to */ 4, 10 ], 2016: [ 3, 29, /* to */ 4, 2 ], 2017: [ 4, 10, /* to */ 4, 21 ], }, { name: 'Pfingstferien', 2005: [ 5, 17, /* to */ 5, 28 ], 2006: [ 5, 29, /* to */ 6, 10 ], 2007: [ 5, 29, /* to */ 6, 9 ], 2008: [ 5, 13, /* to */ 5, 23 ], 2009: [ 5, 25, /* to */ 6, 6 ], 2010: [ 5, 25, /* to */ 6, 5 ], 2011: [ 6, 14, /* to */ 6, 25 ], 2012: [ 5, 29, /* to */ 6, 9 ], 2013: [ 5, 21, /* to */ 6, 1 ], 2014: [ 6, 10, /* to */ 6, 21 ], 2015: [ 5, 26, /* to */ 6, 6 ], 2016: [ 5, 17, /* to */ 5, 28 ], 2017: [ 6, 6, /* to */ 6, 16 ], }, { name: 'Sommerferien', 2005: [ 7, 28, /* to */ 9, 10 ], 2006: [ 8, 3, /* to */ 9, 16 ], 2007: [ 7, 26, /* to */ 9, 8 ], 2008: [ 7, 24, /* to */ 9, 6 ], 2009: [ 7, 30, /* to */ 9, 12 ], 2010: [ 7, 29, /* to */ 9, 11 ], 2011: [ 7, 28, /* to */ 9, 10 ], 2012: [ 7, 26, /* to */ 9, 8 ], 2013: [ 7, 25, /* to */ 9, 7 ], 2014: [ 7, 31, /* to */ 9, 13 ], 2015: [ 7, 30, /* to */ 9, 12 ], 2016: [ 7, 28, /* to */ 9, 10 ], 2017: [ 7, 27, /* to */ 9, 9 ], }, { name: 'Herbstferien', 2005: [ 11, 2, /* to */ 11, 4 ], 2006: [ 10, 30, /* to */ 11, 3 ], 2007: [ 10, 29, /* to */ 11, 3 ], 2008: [ 10, 27, /* to */ 10, 31 ], 2009: [ 10, 26, /* to */ 10, 31 ], 2010: [ 11, 2, /* to */ 11, 6 ], 2011: [ 10, 31, /* to */ 10, 31, 11, 2, /* to */ 11, 4 ], 2012: [ 10, 29, /* to */ 11, 2 ], 2013: [ 10, 28, /* to */ 10, 30 ], 2014: [ 10, 27, /* to */ 10, 30 ], 2015: [ 11, 2, /* to */ 11, 6 ], 2016: [ 11, 2, /* to */ 11, 4 ], }, { name: 'Weihnachtsferien', 2005: [ 12, 22, /* to */ 1, 5 ], 2006: [ 12, 27, /* to */ 1, 5 ], 2007: [ 12, 24, /* to */ 1, 5 ], 2008: [ 12, 22, /* to */ 1, 10 ], 2009: [ 12, 23, /* to */ 1, 9 ], 2010: [ 12, 23, /* to */ 1, 8 ], 2011: [ 12, 23, /* to */ 1, 5 ], 2012: [ 12, 24, /* to */ 1, 5 ], 2013: [ 12, 23, /* to */ 1, 4 ], 2014: [ 12, 22, /* to */ 1, 5 ], 2015: [ 12, 23, /* to */ 1, 9 ], 2016: [ 12, 23, /* to */ 1, 7 ], }, ], }, 'Mecklenburg-Vorpommern': { 'SH': [ { name: 'Winterferien', 2010: [ 2, 6, /* to */ 2, 20 ], 2011: [ 2, 7, /* to */ 2, 19 ], 2012: [ 2, 6, /* to */ 2, 17 ], 2013: [ 2, 4, /* to */ 2, 15 ], 2014: [ 2, 3, /* to */ 2, 15 ], 2015: [ 2, 2, /* to */ 2, 14 ], 2016: [ 2, 1, /* to */ 2, 13 ], 2017: [ 2, 6, /* to */ 2, 18 ], }, { name: 'Osterferien', 2010: [ 3, 29, /* to */ 4, 7 ], 2011: [ 4, 16, /* to */ 4, 27 ], 2012: [ 4, 2, /* to */ 4, 11 ], 2013: [ 3, 25, /* to */ 4, 3 ], 2014: [ 4, 14, /* to */ 4, 23 ], 2015: [ 3, 30, /* to */ 4, 8 ], 2016: [ 3, 21, /* to */ 3, 30 ], 2017: [ 4, 10, /* to */ 4, 19 ], }, { name: 'Pfingstferien', 2010: [ 5, 21, /* to */ 5, 22 ], 2011: [ 6, 10, /* to */ 6, 14 ], 2012: [ 5, 25, /* to */ 5, 29 ], 2013: [ 5, 17, /* to */ 5, 21 ], 2014: [ 6, 6, /* to */ 6, 10 ], 2015: [ 5, 22, /* to */ 5, 26 ], 2016: [ 5, 14, /* to */ 5, 17 ], 2017: [ 6, 2, /* to */ 6, 6 ], }, { name: 'Sommerferien', 2010: [ 7, 12, /* to */ 8, 21 ], 2011: [ 7, 4, /* to */ 8, 13 ], 2012: [ 6, 23, /* to */ 8, 4 ], 2013: [ 6, 22, /* to */ 8, 3 ], 2014: [ 7, 14, /* to */ 8, 23 ], 2015: [ 7, 20, /* to */ 8, 29 ], 2016: [ 7, 25, /* to */ 9, 3 ], 2017: [ 7, 24, /* to */ 9, 2 ], }, { name: 'Herbstferien', 2010: [ 10, 18, /* to */ 10, 23 ], 2011: [ 10, 17, /* to */ 10, 21 ], 2012: [ 10, 1, /* to */ 10, 5 ], 2013: [ 10, 14, /* to */ 10, 19 ], 2014: [ 10, 20, /* to */ 10, 25 ], 2015: [ 10, 24, /* to */ 10, 30 ], 2016: [ 10, 24, /* to */ 10, 28 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 23, /* to */ 12, 31 ], 2011: [ 12, 23, /* to */ 1, 3 ], 2012: [ 12, 21, /* to */ 1, 4 ], 2013: [ 12, 23, /* to */ 1, 3 ], 2014: [ 12, 22, /* to */ 1, 2 ], 2015: [ 12, 21, /* to */ 1, 2 ], 2016: [ 12, 22, /* to */ 1, 2 ], }, ], }, 'Hessen': { 'SH': [ { name: 'Osterferien', 2010: [ 3, 29, /* to */ 4, 10 ], 2011: [ 4, 18, /* to */ 4, 30 ], 2012: [ 4, 2, /* to */ 4, 14 ], 2013: [ 3, 25, /* to */ 4, 6 ], 2014: [ 4, 14, /* to */ 4, 26 ], 2015: [ 3, 30, /* to */ 4, 11 ], 2016: [ 3, 29, /* to */ 4, 9 ], 2017: [ 4, 3, /* to */ 4, 15 ], 2018: [ 3, 26, /* to */ 4, 7 ], }, { name: 'Sommerferien', 2010: [ 7, 5, /* to */ 8, 14 ], 2011: [ 6, 27, /* to */ 8, 5 ], 2012: [ 7, 2, /* to */ 8, 10 ], 2013: [ 7, 8, /* to */ 8, 16 ], 2014: [ 7, 28, /* to */ 9, 5 ], 2015: [ 7, 27, /* to */ 9, 5 ], 2016: [ 7, 18, /* to */ 8, 26 ], 2017: [ 7, 3, /* to */ 8, 11 ], }, { name: 'Herbstferien', 2010: [ 10, 11, /* to */ 10, 22 ], 2011: [ 10, 10, /* to */ 10, 22 ], 2012: [ 10, 15, /* to */ 10, 27 ], 2013: [ 10, 14, /* to */ 10, 26 ], 2014: [ 10, 20, /* to */ 11, 1 ], 2015: [ 10, 19, /* to */ 10, 31 ], 2016: [ 10, 17, /* to */ 10, 29 ], 2017: [ 10, 9, /* to */ 10, 21 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 20, /* to */ 1, 7 ], 2011: [ 12, 21, /* to */ 1, 6 ], 2012: [ 12, 24, /* to */ 1, 12 ], 2013: [ 12, 23, /* to */ 1, 11 ], 2014: [ 12, 22, /* to */ 1, 10 ], 2015: [ 12, 23, /* to */ 1, 9 ], 2016: [ 12, 22, /* to */ 1, 7 ], 2017: [ 12, 24, /* to */ 1, 13 ], }, ], }, 'Schleswig-Holstein': { 'SH': [ { name: 'Osterferien', 2010: [ 4, 3, /* to */ 4, 17 ], 2011: [ 4, 15, /* to */ 4, 30 ], 2012: [ 3, 30, /* to */ 4, 13 ], 2013: [ 3, 25, /* to */ 4, 9 ], 2014: [ 4, 16, /* to */ 5, 2 ], 2015: [ 4, 1, /* to */ 4, 17 ], 2016: [ 3, 24, /* to */ 4, 9 ], 2017: [ 4, 7, /* to */ 4, 21 ], }, { name: 'Sommerferien', 2010: [ 7, 12, /* to */ 8, 21 ], 2011: [ 7, 4, /* to */ 8, 13 ], 2012: [ 6, 25, /* to */ 8, 4 ], 2013: [ 6, 24, /* to */ 8, 3 ], 2014: [ 7, 14, /* to */ 8, 23 ], 2015: [ 7, 20, /* to */ 8, 29 ], 2016: [ 7, 25, /* to */ 9, 3 ], 2017: [ 7, 24, /* to */ 9, 2 ], }, { name: 'Pfingstferien', 2011: [ 6, 3, /* to */ 6, 4 ], 2012: [ 5, 18, /* to */ 5, 18 ], 2013: [ 5, 10, /* to */ 5, 10 ], 2014: [ 5, 30, /* to */ 5, 30 ], 2015: [ 5, 15, /* to */ 5, 15 ], 2016: [ 5, 6, /* to */ 5, 6 ], 2017: [ 5, 26, /* to */ 5, 26 ], }, { name: 'Herbstferien', 2010: [ 10, 11, /* to */ 10, 23 ], 2011: [ 10, 10, /* to */ 10, 22 ], 2012: [ 10, 4, /* to */ 10, 19 ], 2013: [ 10, 4, /* to */ 10, 18 ], 2014: [ 10, 13, /* to */ 10, 25 ], 2015: [ 10, 19, /* to */ 10, 31 ], 2016: [ 10, 17, /* to */ 10, 29 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 23, /* to */ 1, 7 ], 2011: [ 12, 23, /* to */ 1, 6 ], 2012: [ 12, 24, /* to */ 1, 5 ], 2013: [ 12, 23, /* to */ 1, 6 ], 2014: [ 12, 22, /* to */ 1, 6 ], 2015: [ 12, 21, /* to */ 1, 6 ], 2016: [ 12, 23, /* to */ 1, 6 ], }, ], }, 'Berlin': { 'SH': [ { name: 'Winterferien', 2010: [ 2, 1, /* to */ 2, 6 ], 2011: [ 1, 31, /* to */ 2, 5 ], 2012: [ 1, 30, /* to */ 2, 4 ], 2013: [ 2, 4, /* to */ 2, 9 ], 2014: [ 2, 3, /* to */ 2, 8 ], 2015: [ 2, 2, /* to */ 2, 7 ], 2016: [ 2, 1, /* to */ 2, 6 ], 2017: [ 1, 30, /* to */ 2, 4 ], }, { name: 'Osterferien', 2010: [ 3, 31, /* to */ 4, 10 ], 2011: [ 4, 18, /* to */ 4, 30 ], 2012: [ 4, 2, /* to */ 4, 14, 4, 30, /* to */ 4, 30 ], 2013: [ 3, 25, /* to */ 4, 6 ], 2014: [ 4, 14, /* to */ 4, 26, 5, 2, /* to */ 5, 2 ], 2015: [ 3, 30, /* to */ 4, 11 ], 2016: [ 3, 21, /* to */ 4, 2 ], 2017: [ 4, 10, /* to */ 4, 22 ], }, { name: 'Pfingstferien', 2010: [ 5, 14, /* to */ 5, 14, 5, 25, /* to */ 5, 25 ], 2011: [ 6, 3, /* to */ 6, 3 ], 2012: [ 5, 18, /* to */ 5, 18 ], 2013: [ 5, 10, /* to */ 5, 10, 5, 21, /* to */ 5, 21 ], 2014: [ 5, 30, /* to */ 5, 30 ], 2015: [ 5, 15, /* to */ 5, 15 ], 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 17 ], 2017: [ 5, 26, /* to */ 5, 26 ], }, { name: 'Sommerferien', 2010: [ 7, 7, /* to */ 8, 21 ], 2011: [ 6, 29, /* to */ 8, 12 ], 2012: [ 6, 20, /* to */ 8, 3 ], 2013: [ 6, 19, /* to */ 8, 2 ], 2014: [ 7, 9, /* to */ 8, 22 ], 2015: [ 7, 15, /* to */ 8, 28 ], 2016: [ 7, 20, /* to */ 9, 2 ], 2017: [ 7, 19, /* to */ 9, 1 ], }, { name: 'Herbstferien', 2010: [ 10, 11, /* to */ 10, 23 ], 2011: [ 10, 4, /* to */ 10, 14 ], 2012: [ 10, 1, /* to */ 10, 13 ], 2013: [ 9, 30, /* to */ 10, 12 ], 2014: [ 10, 20, /* to */ 11, 1 ], 2015: [ 10, 19, /* to */ 10, 31 ], 2016: [ 10, 17, /* to */ 10, 28 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 23, /* to */ 1, 1 ], 2011: [ 12, 23, /* to */ 1, 3 ], 2012: [ 12, 24, /* to */ 1, 4 ], 2013: [ 12, 23, /* to */ 1, 3 ], 2014: [ 12, 22, /* to */ 1, 2 ], 2015: [ 12, 23, /* to */ 1, 2 ], 2016: [ 12, 23, /* to */ 1, 3 ], }, ], }, 'Saarland': { 'SH': [ { name: 'Winterferien', 2010: [ 2, 15, /* to */ 2, 20 ], 2011: [ 3, 7, /* to */ 3, 12 ], 2012: [ 2, 20, /* to */ 2, 25 ], 2013: [ 2, 11, /* to */ 2, 16 ], 2014: [ 3, 3, /* to */ 3, 8 ], 2015: [ 2, 16, /* to */ 2, 21 ], }, { name: 'Osterferien', 2010: [ 3, 29, /* to */ 4, 10 ], 2011: [ 4, 18, /* to */ 4, 30 ], 2012: [ 4, 2, /* to */ 4, 14 ], 2013: [ 3, 25, /* to */ 4, 6 ], 2014: [ 4, 14, /* to */ 4, 26 ], 2015: [ 3, 30, /* to */ 4, 11 ], }, { name: 'Sommerferien', 2010: [ 7, 5, /* to */ 8, 14 ], 2011: [ 6, 24, /* to */ 8, 6 ], 2012: [ 7, 2, /* to */ 8, 14 ], 2013: [ 7, 8, /* to */ 8, 17 ], 2014: [ 7, 28, /* to */ 9, 6 ], 2015: [ 7, 27, /* to */ 9, 4 ], 2016: [ 7, 18, /* to */ 8, 26 ], 2017: [ 7, 3, /* to */ 8, 14 ], }, { name: 'Herbstferien', 2010: [ 10, 11, /* to */ 10, 23 ], 2011: [ 10, 4, /* to */ 10, 15 ], 2012: [ 10, 22, /* to */ 11, 3 ], 2013: [ 10, 21, /* to */ 11, 2 ], 2014: [ 10, 20, /* to */ 10, 31 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 20, /* to */ 1, 1 ], 2011: [ 12, 23, /* to */ 1, 4 ], 2012: [ 12, 24, /* to */ 1, 5 ], 2013: [ 12, 20, /* to */ 1, 4 ], 2014: [ 12, 22, /* to */ 1, 7 ], }, ], }, 'Bremen': { 'SH': [ { name: 'Winterferien', 2010: [ 2, 1, /* to */ 2, 2 ], 2011: [ 1, 31, /* to */ 2, 1 ], 2012: [ 1, 30, /* to */ 1, 31 ], 2013: [ 1, 31, /* to */ 2, 1 ], 2014: [ 1, 30, /* to */ 1, 31 ], 2015: [ 2, 2, /* to */ 2, 3 ], 2016: [ 1, 28, /* to */ 1, 29 ], 2017: [ 1, 30, /* to */ 1, 31 ], }, { name: 'Osterferien', 2010: [ 3, 19, /* to */ 4, 6 ], 2011: [ 4, 16, /* to */ 4, 30 ], 2012: [ 3, 26, /* to */ 4, 11, 4, 30, /* to */ 4, 30 ], 2013: [ 3, 16, /* to */ 4, 2 ], 2014: [ 4, 3, /* to */ 4, 22, 5, 2, /* to */ 5, 2 ], 2015: [ 3, 25, /* to */ 4, 10 ], 2016: [ 3, 18, /* to */ 4, 2 ], 2017: [ 4, 10, /* to */ 4, 22 ], }, { name: 'Pfingstferien', 2010: [ 5, 14, /* to */ 5, 14, 5, 25, /* to */ 5, 25 ], 2011: [ 6, 3, /* to */ 6, 3, 6, 14, /* to */ 6, 14 ], 2012: [ 5, 18, /* to */ 5, 18, 5, 29, /* to */ 5, 29 ], 2013: [ 5, 10, /* to */ 5, 10, 5, 21, /* to */ 5, 21 ], 2014: [ 5, 30, /* to */ 5, 30, 6, 10, /* to */ 6, 10 ], 2015: [ 5, 15, /* to */ 5, 15, 5, 26, /* to */ 5, 26 ], 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 17 ], 2017: [ 5, 26, /* to */ 5, 26, 6, 6, /* to */ 6, 6 ], }, { name: 'Sommerferien', 2010: [ 6, 24, /* to */ 8, 4 ], 2011: [ 7, 7, /* to */ 8, 17 ], 2012: [ 7, 23, /* to */ 8, 31 ], 2013: [ 6, 27, /* to */ 8, 7 ], 2014: [ 7, 31, /* to */ 9, 10 ], 2015: [ 7, 23, /* to */ 9, 2 ], 2016: [ 6, 23, /* to */ 8, 3 ], 2017: [ 6, 22, /* to */ 8, 2 ], }, { name: 'Herbstferien', 2010: [ 10, 9, /* to */ 10, 23 ], 2011: [ 10, 17, /* to */ 10, 29 ], 2012: [ 10, 22, /* to */ 11, 3 ], 2013: [ 10, 4, /* to */ 10, 18 ], 2014: [ 10, 27, /* to */ 11, 8 ], 2015: [ 10, 19, /* to */ 10, 31 ], 2016: [ 10, 4, /* to */ 10, 15 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 22, /* to */ 1, 5 ], 2011: [ 12, 23, /* to */ 1, 4 ], 2012: [ 12, 24, /* to */ 1, 5 ], 2013: [ 12, 23, /* to */ 1, 3 ], 2014: [ 12, 22, /* to */ 1, 5 ], 2015: [ 12, 23, /* to */ 1, 6 ], 2016: [ 12, 21, /* to */ 1, 6 ], }, ], }, 'Bayern': { 'SH': [ { name: 'Winterferien', 2010: [ 2, 15, /* to */ 2, 20 ], 2011: [ 3, 7, /* to */ 3, 11 ], 2012: [ 2, 20, /* to */ 2, 24 ], 2013: [ 2, 11, /* to */ 2, 15 ], 2014: [ 3, 3, /* to */ 3, 7 ], 2015: [ 2, 16, /* to */ 2, 20 ], 2016: [ 2, 8, /* to */ 2, 12 ], 2017: [ 2, 27, /* to */ 3, 3 ], }, { name: 'Osterferien', 2010: [ 3, 29, /* to */ 4, 10 ], 2011: [ 4, 18, /* to */ 4, 30 ], 2012: [ 4, 2, /* to */ 4, 14 ], 2013: [ 3, 25, /* to */ 4, 6 ], 2014: [ 4, 14, /* to */ 4, 26 ], 2015: [ 3, 30, /* to */ 4, 11 ], 2016: [ 3, 21, /* to */ 4, 1 ], 2017: [ 4, 10, /* to */ 4, 22 ], }, { name: 'Pfingstferien', 2010: [ 5, 25, /* to */ 6, 5 ], 2011: [ 6, 14, /* to */ 6, 25 ], 2012: [ 5, 29, /* to */ 6, 9 ], 2013: [ 5, 21, /* to */ 5, 31 ], 2014: [ 6, 10, /* to */ 6, 21 ], 2015: [ 5, 26, /* to */ 6, 5 ], 2016: [ 5, 17, /* to */ 5, 28 ], 2017: [ 6, 6, /* to */ 6, 16 ], }, { name: 'Sommerferien', 2010: [ 8, 2, /* to */ 9, 13 ], 2011: [ 7, 30, /* to */ 9, 12 ], 2012: [ 8, 1, /* to */ 9, 12 ], 2013: [ 7, 31, /* to */ 9, 11 ], 2014: [ 7, 30, /* to */ 9, 15 ], 2015: [ 8, 1, /* to */ 9, 14 ], 2016: [ 7, 30, /* to */ 9, 12 ], 2017: [ 7, 29, /* to */ 9, 11 ], }, { name: 'Herbstferien', 2010: [ 11, 2, /* to */ 11, 5 ], 2011: [ 10, 31, /* to */ 11, 5 ], 2012: [ 10, 29, /* to */ 11, 3 ], 2013: [ 10, 28, /* to */ 10, 31 ], 2014: [ 10, 27, /* to */ 10, 31 ], 2015: [ 11, 2, /* to */ 11, 7 ], 2016: [ 10, 31, /* to */ 11, 4 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 24, /* to */ 1, 7 ], 2011: [ 12, 27, /* to */ 1, 5 ], 2012: [ 12, 24, /* to */ 1, 5 ], 2013: [ 12, 23, /* to */ 1, 4 ], 2014: [ 12, 24, /* to */ 1, 5 ], 2015: [ 12, 24, /* to */ 1, 5 ], 2016: [ 12, 24, /* to */ 1, 5 ], }, ], }, 'Niedersachsen': { 'SH': [ { name: 'Winterferien', 2010: [ 2, 1, /* to */ 2, 2 ], 2011: [ 1, 31, /* to */ 2, 1 ], 2012: [ 1, 30, /* to */ 1, 31 ], 2013: [ 1, 31, /* to */ 2, 1 ], 2014: [ 1, 30, /* to */ 1, 31 ], 2015: [ 2, 2, /* to */ 2, 3 ], 2016: [ 1, 28, /* to */ 1, 29 ], 2017: [ 1, 30, /* to */ 1, 31 ], }, { name: 'Osterferien', 2010: [ 3, 19, /* to */ 4, 6 ], 2011: [ 4, 16, /* to */ 4, 30 ], 2012: [ 3, 26, /* to */ 4, 11, 4, 30, /* to */ 4, 30 ], 2013: [ 3, 16, /* to */ 4, 2 ], 2014: [ 4, 3, /* to */ 4, 22, 5, 2, /* to */ 5, 2 ], 2015: [ 3, 25, /* to */ 4, 10 ], 2016: [ 3, 18, /* to */ 4, 2 ], 2017: [ 4, 10, /* to */ 4, 22 ], }, { name: 'Pfingstferien', 2010: [ 5, 14, /* to */ 5, 14, 5, 25, /* to */ 5, 25 ], 2011: [ 6, 3, /* to */ 6, 3, 6, 14, /* to */ 6, 14 ], 2012: [ 5, 18, /* to */ 5, 18, 5, 29, /* to */ 5, 29 ], 2013: [ 5, 10, /* to */ 5, 10, 5, 21, /* to */ 5, 21 ], 2014: [ 5, 30, /* to */ 5, 30, 6, 10, /* to */ 6, 10 ], 2015: [ 5, 15, /* to */ 5, 15, 5, 26, /* to */ 5, 26 ], 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 17 ], 2017: [ 5, 26, /* to */ 5, 26, 6, 6, /* to */ 6, 6 ], }, { name: 'Sommerferien', 2010: [ 6, 24, /* to */ 8, 4 ], 2011: [ 7, 7, /* to */ 8, 17 ], 2012: [ 7, 23, /* to */ 8, 31 ], 2013: [ 6, 27, /* to */ 8, 7 ], 2014: [ 7, 31, /* to */ 9, 10 ], 2015: [ 7, 23, /* to */ 9, 2 ], 2016: [ 6, 23, /* to */ 8, 3 ], 2017: [ 6, 22, /* to */ 8, 2 ], }, { name: 'Herbstferien', 2010: [ 10, 9, /* to */ 10, 23 ], 2011: [ 10, 17, /* to */ 10, 29 ], 2012: [ 10, 22, /* to */ 11, 3 ], 2013: [ 10, 4, /* to */ 10, 18 ], 2014: [ 10, 27, /* to */ 11, 8 ], 2015: [ 10, 19, /* to */ 10, 31 ], 2016: [ 10, 4, /* to */ 10, 15 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 22, /* to */ 1, 5 ], 2011: [ 12, 23, /* to */ 1, 4 ], 2012: [ 12, 24, /* to */ 1, 5 ], 2013: [ 12, 23, /* to */ 1, 3 ], 2014: [ 12, 22, /* to */ 1, 5 ], 2015: [ 12, 23, /* to */ 1, 6 ], 2016: [ 12, 21, /* to */ 1, 6 ], }, ], }, 'Nordrhein-Westfalen': { 'SH': [ { name: 'Osterferien', 2010: [ 3, 27, /* to */ 4, 10 ], 2011: [ 4, 18, /* to */ 4, 30 ], 2012: [ 4, 2, /* to */ 4, 14 ], 2013: [ 3, 25, /* to */ 4, 6 ], 2014: [ 4, 14, /* to */ 4, 26 ], 2015: [ 3, 30, /* to */ 4, 11 ], 2016: [ 3, 21, /* to */ 4, 2 ], 2017: [ 4, 10, /* to */ 4, 22 ], }, { name: 'Pfingstferien', 2010: [ 5, 25, /* to */ 5, 25 ], 2012: [ 5, 29, /* to */ 5, 29 ], 2013: [ 5, 21, /* to */ 5, 21 ], 2014: [ 6, 10, /* to */ 6, 10 ], 2015: [ 5, 26, /* to */ 5, 26 ], 2016: [ 5, 17, /* to */ 5, 17 ], 2017: [ 6, 6, /* to */ 6, 6 ], }, { name: 'Sommerferien', 2010: [ 7, 15, /* to */ 8, 27 ], 2011: [ 7, 25, /* to */ 9, 6 ], 2012: [ 7, 9, /* to */ 8, 21 ], 2013: [ 7, 22, /* to */ 9, 3 ], 2014: [ 7, 7, /* to */ 8, 19 ], 2015: [ 6, 29, /* to */ 8, 11 ], 2016: [ 7, 11, /* to */ 8, 23 ], 2017: [ 7, 17, /* to */ 8, 29 ], }, { name: 'Herbstferien', 2010: [ 10, 11, /* to */ 10, 23 ], 2011: [ 10, 24, /* to */ 11, 5 ], 2012: [ 10, 8, /* to */ 10, 20 ], 2013: [ 10, 21, /* to */ 11, 2 ], 2014: [ 10, 6, /* to */ 10, 18 ], 2015: [ 10, 5, /* to */ 10, 17 ], 2016: [ 10, 10, /* to */ 10, 21 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 24, /* to */ 1, 8 ], 2011: [ 12, 23, /* to */ 1, 6 ], 2012: [ 12, 21, /* to */ 1, 4 ], 2013: [ 12, 23, /* to */ 1, 7 ], 2014: [ 12, 22, /* to */ 1, 6 ], 2015: [ 12, 23, /* to */ 1, 6 ], 2016: [ 12, 23, /* to */ 1, 6 ], }, ], }, 'Sachsen': { 'SH': [ { name: 'Winterferien', 2010: [ 2, 8, /* to */ 2, 20 ], 2011: [ 2, 12, /* to */ 2, 26 ], 2012: [ 2, 13, /* to */ 2, 25 ], 2013: [ 2, 4, /* to */ 2, 15 ], 2014: [ 2, 17, /* to */ 3, 1 ], 2015: [ 2, 9, /* to */ 2, 21 ], 2016: [ 2, 8, /* to */ 2, 20 ], 2017: [ 2, 13, /* to */ 2, 24 ], }, { name: 'Osterferien', 2010: [ 4, 1, /* to */ 4, 10 ], 2011: [ 4, 22, /* to */ 4, 30 ], 2012: [ 4, 6, /* to */ 4, 14 ], 2013: [ 3, 29, /* to */ 4, 6 ], 2014: [ 4, 18, /* to */ 4, 26 ], 2015: [ 4, 2, /* to */ 4, 11 ], 2016: [ 3, 25, /* to */ 4, 2 ], 2017: [ 4, 13, /* to */ 4, 22 ], }, { name: 'Pfingstferien', 2010: [ 5, 14, /* to */ 5, 14 ], 2011: [ 6, 3, /* to */ 6, 3 ], 2012: [ 5, 18, /* to */ 5, 18 ], 2013: [ 5, 10, /* to */ 5, 10, 5, 18, /* to */ 5, 22 ], 2014: [ 5, 30, /* to */ 5, 30 ], 2015: [ 5, 15, /* to */ 5, 15 ], 2016: [ 5, 6, /* to */ 5, 6 ], 2017: [ 5, 26, /* to */ 5, 26 ], }, { name: 'Sommerferien', 2010: [ 6, 28, /* to */ 8, 6 ], 2011: [ 7, 11, /* to */ 8, 19 ], 2012: [ 7, 23, /* to */ 8, 31 ], 2013: [ 7, 15, /* to */ 8, 23 ], 2014: [ 7, 21, /* to */ 8, 29 ], 2015: [ 7, 13, /* to */ 8, 21 ], 2016: [ 6, 27, /* to */ 8, 5 ], 2017: [ 6, 26, /* to */ 8, 4 ], }, { name: 'Herbstferien', 2010: [ 10, 4, /* to */ 10, 16 ], 2011: [ 10, 17, /* to */ 10, 28 ], 2012: [ 10, 22, /* to */ 11, 2 ], 2013: [ 10, 21, /* to */ 11, 1 ], 2014: [ 10, 20, /* to */ 10, 31 ], 2015: [ 10, 12, /* to */ 10, 24 ], 2016: [ 10, 3, /* to */ 10, 15 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 23, /* to */ 1, 1 ], 2011: [ 12, 23, /* to */ 1, 2 ], 2012: [ 12, 22, /* to */ 1, 2 ], 2013: [ 12, 21, /* to */ 1, 3 ], 2014: [ 12, 22, /* to */ 1, 3 ], 2015: [ 12, 21, /* to */ 1, 2 ], 2016: [ 12, 23, /* to */ 1, 2 ], }, ], }, 'Thüringen': { 'SH': [ { name: 'Winterferien', 2010: [ 2, 1, /* to */ 2, 6 ], 2011: [ 1, 31, /* to */ 2, 5 ], 2012: [ 2, 6, /* to */ 2, 11 ], 2013: [ 2, 18, /* to */ 2, 23 ], 2014: [ 2, 17, /* to */ 2, 22 ], 2015: [ 2, 2, /* to */ 2, 7 ], 2016: [ 2, 1, /* to */ 2, 6 ], 2017: [ 2, 6, /* to */ 2, 11 ], }, { name: 'Osterferien', 2010: [ 3, 29, /* to */ 4, 9 ], 2011: [ 4, 18, /* to */ 4, 30 ], 2012: [ 4, 2, /* to */ 4, 13 ], 2013: [ 3, 25, /* to */ 4, 6 ], 2014: [ 4, 19, /* to */ 5, 2 ], 2015: [ 3, 30, /* to */ 4, 11 ], 2016: [ 3, 24, /* to */ 4, 2 ], 2017: [ 4, 10, /* to */ 4, 21 ], }, { name: 'Sommerferien', 2010: [ 6, 24, /* to */ 8, 4 ], 2011: [ 7, 11, /* to */ 8, 19 ], 2012: [ 7, 23, /* to */ 8, 31 ], 2013: [ 7, 15, /* to */ 8, 23 ], 2014: [ 7, 21, /* to */ 8, 29 ], 2015: [ 7, 13, /* to */ 8, 21 ], 2016: [ 6, 27, /* to */ 8, 10 ], 2017: [ 6, 26, /* to */ 8, 9 ], }, { name: 'Pfingstferien', 2011: [ 6, 11, /* to */ 6, 14 ], 2012: [ 5, 25, /* to */ 5, 29 ], 2013: [ 5, 10, /* to */ 5, 10 ], 2014: [ 5, 30, /* to */ 5, 30 ], 2015: [ 5, 15, /* to */ 5, 15 ], 2016: [ 5, 6, /* to */ 5, 6 ], 2017: [ 5, 26, /* to */ 5, 26 ], }, { name: 'Herbstferien', 2010: [ 10, 9, /* to */ 10, 23 ], 2011: [ 10, 17, /* to */ 10, 28 ], 2012: [ 10, 22, /* to */ 11, 3 ], 2013: [ 10, 21, /* to */ 11, 2 ], 2014: [ 10, 6, /* to */ 10, 18 ], 2015: [ 10, 5, /* to */ 10, 17 ], 2016: [ 10, 10, /* to */ 10, 22 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 23, /* to */ 1, 1 ], 2011: [ 12, 23, /* to */ 1, 1 ], 2012: [ 12, 24, /* to */ 1, 5 ], 2013: [ 12, 23, /* to */ 1, 4 ], 2014: [ 12, 22, /* to */ 1, 3 ], 2015: [ 12, 23, /* to */ 1, 2 ], 2016: [ 12, 23, /* to */ 12, 31 ], }, ], }, 'Hamburg': { 'SH': [ { name: 'Winterferien', 2010: [ 1, 29, /* to */ 1, 29 ], 2011: [ 1, 31, /* to */ 1, 31 ], 2012: [ 1, 30, /* to */ 1, 30 ], 2013: [ 2, 1, /* to */ 2, 1 ], 2014: [ 1, 31, /* to */ 1, 31 ], 2015: [ 1, 30, /* to */ 1, 30 ], 2016: [ 1, 29, /* to */ 1, 29 ], 2017: [ 1, 30, /* to */ 1, 30 ], }, { name: 'Osterferien', 2010: [ 3, 8, /* to */ 3, 20 ], 2011: [ 3, 7, /* to */ 3, 18 ], 2012: [ 3, 5, /* to */ 3, 16 ], 2013: [ 3, 4, /* to */ 3, 15 ], 2014: [ 3, 3, /* to */ 3, 14 ], 2015: [ 3, 2, /* to */ 3, 13 ], 2016: [ 3, 7, /* to */ 3, 18 ], 2017: [ 3, 6, /* to */ 3, 17 ], }, { name: 'Pfingstferien', 2010: [ 5, 14, /* to */ 5, 22 ], 2011: [ 4, 26, /* to */ 4, 29, 6, 3, /* to */ 6, 3 ], 2012: [ 4, 30, /* to */ 5, 4, 5, 18, /* to */ 5, 18 ], 2013: [ 5, 2, /* to */ 5, 10 ], 2014: [ 4, 28, /* to */ 5, 2, 5, 30, /* to */ 5, 30 ], 2015: [ 5, 11, /* to */ 5, 15 ], 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 20 ], 2017: [ 5, 22, /* to */ 5, 26 ], }, { name: 'Sommerferien', 2010: [ 7, 8, /* to */ 8, 18 ], 2011: [ 6, 30, /* to */ 8, 10 ], 2012: [ 6, 21, /* to */ 8, 1 ], 2013: [ 6, 20, /* to */ 7, 31 ], 2014: [ 7, 10, /* to */ 8, 20 ], 2015: [ 7, 16, /* to */ 8, 26 ], 2016: [ 7, 21, /* to */ 8, 31 ], 2017: [ 7, 20, /* to */ 8, 30 ], }, { name: 'Herbstferien', 2010: [ 10, 4, /* to */ 10, 15 ], 2011: [ 10, 4, /* to */ 10, 14 ], 2012: [ 10, 1, /* to */ 10, 12 ], 2013: [ 9, 30, /* to */ 10, 11 ], 2014: [ 10, 13, /* to */ 10, 24 ], 2015: [ 10, 19, /* to */ 10, 30 ], 2016: [ 10, 17, /* to */ 10, 28 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 23, /* to */ 1, 3 ], 2011: [ 12, 27, /* to */ 1, 6 ], 2012: [ 12, 21, /* to */ 1, 4 ], 2013: [ 12, 19, /* to */ 1, 3 ], 2014: [ 12, 22, /* to */ 1, 6 ], 2015: [ 12, 21, /* to */ 1, 1 ], 2016: [ 12, 27, /* to */ 1, 6 ], }, ], }, 'Sachsen-Anhalt': { 'SH': [ { name: 'Winterferien', 2010: [ 2, 8, /* to */ 2, 13 ], 2011: [ 2, 5, /* to */ 2, 12 ], 2012: [ 2, 4, /* to */ 2, 11 ], 2013: [ 2, 1, /* to */ 2, 8 ], 2014: [ 2, 1, /* to */ 2, 12 ], 2015: [ 2, 2, /* to */ 2, 14 ], 2016: [ 2, 1, /* to */ 2, 10 ], 2017: [ 2, 4, /* to */ 2, 11 ], }, { name: 'Osterferien', 2010: [ 3, 29, /* to */ 4, 9 ], 2011: [ 4, 18, /* to */ 4, 27 ], 2012: [ 4, 2, /* to */ 4, 7 ], 2013: [ 3, 25, /* to */ 3, 30 ], 2014: [ 4, 14, /* to */ 4, 17 ], 2015: [ 4, 2, /* to */ 4, 2 ], 2016: [ 3, 24, /* to */ 3, 24 ], 2017: [ 4, 10, /* to */ 4, 13 ], }, { name: 'Pfingstferien', 2010: [ 5, 14, /* to */ 5, 22 ], 2011: [ 6, 14, /* to */ 6, 18 ], 2012: [ 5, 18, /* to */ 5, 25 ], 2013: [ 5, 10, /* to */ 5, 18 ], 2014: [ 5, 30, /* to */ 6, 7 ], 2015: [ 5, 15, /* to */ 5, 23 ], 2016: [ 5, 6, /* to */ 5, 14 ], 2017: [ 5, 26, /* to */ 5, 26 ], }, { name: 'Sommerferien', 2010: [ 6, 24, /* to */ 8, 4 ], 2011: [ 7, 11, /* to */ 8, 24 ], 2012: [ 7, 23, /* to */ 9, 5 ], 2013: [ 7, 15, /* to */ 8, 28 ], 2014: [ 7, 21, /* to */ 9, 3 ], 2015: [ 7, 13, /* to */ 8, 26 ], 2016: [ 6, 27, /* to */ 8, 10 ], 2017: [ 6, 26, /* to */ 8, 9 ], }, { name: 'Herbstferien', 2010: [ 10, 18, /* to */ 10, 23 ], 2011: [ 10, 17, /* to */ 10, 22 ], 2012: [ 10, 29, /* to */ 11, 2 ], 2013: [ 10, 21, /* to */ 10, 25 ], 2014: [ 10, 27, /* to */ 10, 30 ], 2015: [ 10, 17, /* to */ 10, 24 ], 2016: [ 10, 4, /* to */ 10, 15 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 22, /* to */ 1, 5 ], 2011: [ 12, 22, /* to */ 1, 7 ], 2012: [ 12, 19, /* to */ 1, 4 ], 2013: [ 12, 21, /* to */ 1, 3 ], 2014: [ 12, 22, /* to */ 1, 5 ], 2015: [ 12, 21, /* to */ 1, 5 ], 2016: [ 12, 19, /* to */ 1, 2 ], }, ], }, 'Rheinland-Pfalz': { 'SH': [ { name: 'Osterferien', 2010: [ 3, 26, /* to */ 4, 9 ], 2011: [ 4, 18, /* to */ 4, 29 ], 2012: [ 3, 29, /* to */ 4, 13 ], 2013: [ 3, 20, /* to */ 4, 5 ], 2014: [ 4, 11, /* to */ 4, 25 ], 2015: [ 3, 26, /* to */ 4, 10 ], 2016: [ 3, 18, /* to */ 4, 1 ], 2017: [ 4, 10, /* to */ 4, 21 ], }, { name: 'Sommerferien', 2010: [ 7, 5, /* to */ 8, 13 ], 2011: [ 6, 27, /* to */ 8, 5 ], 2012: [ 7, 2, /* to */ 8, 10 ], 2013: [ 7, 8, /* to */ 8, 16 ], 2014: [ 7, 28, /* to */ 9, 5 ], 2015: [ 7, 27, /* to */ 9, 4 ], 2016: [ 7, 18, /* to */ 8, 26 ], 2017: [ 7, 3, /* to */ 8, 11 ], }, { name: 'Herbstferien', 2010: [ 10, 11, /* to */ 10, 22 ], 2011: [ 10, 4, /* to */ 10, 14 ], 2012: [ 10, 1, /* to */ 10, 12 ], 2013: [ 10, 4, /* to */ 10, 18 ], 2014: [ 10, 20, /* to */ 10, 31 ], 2015: [ 10, 19, /* to */ 10, 30 ], 2016: [ 10, 10, /* to */ 10, 21 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 23, /* to */ 1, 7 ], 2011: [ 12, 22, /* to */ 1, 6 ], 2012: [ 12, 20, /* to */ 1, 4 ], 2013: [ 12, 23, /* to */ 1, 7 ], 2014: [ 12, 22, /* to */ 1, 7 ], 2015: [ 12, 23, /* to */ 1, 8 ], 2016: [ 12, 22, /* to */ 1, 6 ], }, ], }, 'Brandenburg': { 'SH': [ { name: 'Winterferien', 2010: [ 2, 1, /* to */ 2, 6 ], 2011: [ 1, 31, /* to */ 2, 5 ], 2012: [ 1, 30, /* to */ 2, 4 ], 2013: [ 2, 4, /* to */ 2, 9 ], 2014: [ 2, 3, /* to */ 2, 8 ], 2015: [ 2, 2, /* to */ 2, 7 ], 2016: [ 2, 1, /* to */ 2, 6 ], 2017: [ 1, 30, /* to */ 2, 4 ], }, { name: 'Osterferien', 2010: [ 3, 31, /* to */ 4, 10 ], 2011: [ 4, 20, /* to */ 4, 30 ], 2012: [ 4, 4, /* to */ 4, 14, 4, 30, /* to */ 4, 30 ], 2013: [ 3, 27, /* to */ 4, 6 ], 2014: [ 4, 16, /* to */ 4, 26, 5, 2, /* to */ 5, 2 ], 2015: [ 4, 1, /* to */ 4, 11 ], 2016: [ 3, 23, /* to */ 4, 2 ], 2017: [ 4, 12, /* to */ 4, 22 ], }, { name: 'Pfingstferien', 2010: [ 5, 14, /* to */ 5, 14 ], 2011: [ 6, 3, /* to */ 6, 3 ], 2012: [ 5, 18, /* to */ 5, 18 ], 2013: [ 5, 10, /* to */ 5, 10 ], 2014: [ 5, 30, /* to */ 5, 30 ], 2015: [ 5, 15, /* to */ 5, 15 ], 2016: [ 5, 6, /* to */ 5, 6, 5, 17, /* to */ 5, 17 ], 2017: [ 5, 26, /* to */ 5, 26 ], }, { name: 'Sommerferien', 2010: [ 7, 8, /* to */ 8, 21 ], 2011: [ 6, 30, /* to */ 8, 13 ], 2012: [ 6, 21, /* to */ 8, 3 ], 2013: [ 6, 20, /* to */ 8, 2 ], 2014: [ 7, 10, /* to */ 8, 22 ], 2015: [ 7, 16, /* to */ 8, 28 ], 2016: [ 7, 21, /* to */ 9, 3 ], 2017: [ 7, 20, /* to */ 9, 1 ], }, { name: 'Herbstferien', 2010: [ 10, 11, /* to */ 10, 23 ], 2011: [ 10, 4, /* to */ 10, 14 ], 2012: [ 10, 1, /* to */ 10, 13 ], 2013: [ 9, 30, /* to */ 10, 12, 11, 1, /* to */ 11, 1 ], 2014: [ 10, 20, /* to */ 11, 1 ], 2015: [ 10, 19, /* to */ 10, 30 ], 2016: [ 10, 17, /* to */ 10, 28 ], }, { name: 'Weihnachtsferien', 2010: [ 12, 23, /* to */ 1, 1 ], 2011: [ 12, 23, /* to */ 1, 3 ], 2012: [ 12, 24, /* to */ 1, 4 ], 2013: [ 12, 23, /* to */ 1, 3 ], 2014: [ 12, 22, /* to */ 1, 2 ], 2015: [ 12, 23, /* to */ 1, 2 ], 2016: [ 12, 23, /* to */ 1, 3 ], }, ], }, }, // }}} 'at': { // {{{ 'PH': { // http://de.wikipedia.org/wiki/Feiertage_in_%C3%96sterreich 'Neujahrstag' : [ 1, 1 ], 'Heilige Drei Könige' : [ 1, 6 ], // 'Josef' : [ 3, 19, [ 'Kärnten', 'Steiermark', 'Tirol', 'Vorarlberg' ] ], // 'Karfreitag' : [ 'easter', -2 ], 'Ostermontag' : [ 'easter', 1 ], 'Staatsfeiertag' : [ 5, 1 ], // 'Florian' : [ 5, 4, [ 'Oberösterreich' ] ], 'Christi Himmelfahrt' : [ 'easter', 39 ], 'Pfingstmontag' : [ 'easter', 50 ], 'Fronleichnam' : [ 'easter', 60 ], 'Mariä Himmelfahrt' : [ 8, 15 ], // 'Rupert' : [ 9, 24, [ 'Salzburg' ] ], // 'Tag der Volksabstimmung' : [ 10, 10, [ 'Kärnten' ] ], 'Nationalfeiertag' : [ 10, 26 ], 'Allerheiligen' : [ 11, 1 ], // 'Martin' : [ 11, 11, [ 'Burgenland' ] ], // 'Leopold' : [ 11, 15, [ 'Niederösterreich', 'Wien' ] ], 'Mariä Empfängnis' : [ 12, 8 ], // 'Heiliger Abend' : [ 12, 24 ], 'Christtag' : [ 12, 25 ], 'Stefanitag' : [ 12, 26 ], // 'Silvester' : [ 12, 31 ], }, }, // }}} 'ca': { // {{{ 'PH': { // https://en.wikipedia.org/wiki/Public_holidays_in_Canada "New Year's Day" : [ 1, 1 ], "Good Friday" : [ 'easter', -2 ], "Canada Day" : [ 'canadaDay', 0 ], "Labour Day" : [ 'firstSeptemberMonday', 0 ], "Christmas Day" : [ 12, 25 ] }, 'Alberta': { 'PH': { "New Year's Day" : [ 1, 1 ], "Alberta Family Day" : [ 'firstFebruaryMonday', 14 ], "Good Friday" : [ 'easter', -2 ], "Easter Monday" : [ 'easter', 1 ], "Victoria Day" : [ 'victoriaDay', 0 ], "Canada Day" : [ 'canadaDay', 0 ], "Heritage Day" : [ 'firstAugustMonday', 0 ], "Labour Day" : [ 'firstSeptemberMonday', 0 ], "Thanksgiving" : [ 'firstOctoberMonday', 7 ], "Remembrance Day" : [ 11, 11 ], "Christmas Day" : [ 12, 25 ], "Boxing Day" : [ 12, 26 ] }, }, 'British Columbia': { 'PH': { "New Year's Day" : [ 1, 1 ], "Family Day" : [ 'firstFebruaryMonday', 7 ], "Good Friday" : [ 'easter', -2 ], "Victoria Day" : [ 'victoriaDay', 0 ], "Canada Day" : [ 'canadaDay', 0 ], "British Columbia Day" : [ 'firstAugustMonday', 0 ], "Labour Day" : [ 'firstSeptemberMonday', 0 ], "Thanksgiving" : [ 'firstOctoberMonday', 7 ], "Remembrance Day" : [ 11, 11 ], "Christmas Day" : [ 12, 25 ] }, }, 'Manitoba': { 'PH': { "New Year's Day" : [ 1, 1 ], "Louis Riel Day" : [ 'firstFebruaryMonday', 14 ], "Good Friday" : [ 'easter', -2 ], "Victoria Day" : [ 'victoriaDay', 0 ], "Canada Day" : [ 'canadaDay', 0 ], "Civic Holiday" : [ 'firstAugustMonday', 0 ], "Labour Day" : [ 'firstSeptemberMonday', 0 ], "Thanksgiving" : [ 'firstOctoberMonday', 7 ], "Remembrance Day" : [ 11, 11 ], "Christmas Day" : [ 12, 25 ] }, }, 'New Brunswick': { 'PH': { "New Year's Day" : [ 1, 1 ], "Good Friday" : [ 'easter', -2 ], "Victoria Day" : [ 'victoriaDay', 0 ], "Canada Day" : [ 'canadaDay', 0 ], "New Brunswick Day" : [ 'firstAugustMonday', 0 ], "Labour Day" : [ 'firstSeptemberMonday', 0 ], "Thanksgiving" : [ 'firstOctoberMonday', 7 ], "Remembrance Day" : [ 11, 11 ], "Christmas Day" : [ 12, 25 ], "Boxing Day" : [ 12, 26 ] }, }, 'Newfoundland and Labrador': { 'PH': { "New Year's Day" : [ 1, 1 ], "Saint Patrick's Day" : [ 3, 17 ], "Good Friday" : [ 'easter', -2 ], "Saint George's Day" : [ 4, 23 ], "Discovery Day" : [ 6, 24 ], "Memorial Day" : [ 7, 1 ], "Orangemen's Day" : [ 7, 12 ], "Labour Day" : [ 'firstSeptemberMonday', 0 ], "Armistice Day" : [ 11, 11 ], "Christmas Day" : [ 12, 25 ] }, }, 'Northwest Territories': { 'PH': { "New Year's Day" : [ 1, 1 ], "Good Friday" : [ 'easter', -2 ], "Victoria Day" : [ 'victoriaDay', 0 ], "National Aboriginal Day" : [ 6, 21 ], "Canada Day" : [ 'canadaDay', 0 ], "Civic Holiday" : [ 'firstAugustMonday', 0 ], "Labour Day" : [ 'firstSeptemberMonday', 0 ], "Thanksgiving" : [ 'firstOctoberMonday', 7 ], "Remembrance Day" : [ 11, 11 ], "Christmas Day" : [ 12, 25 ] }, }, 'Nova Scotia': { 'PH': { "New Year's Day" : [ 1, 1 ], "Good Friday" : [ 'easter', -2 ], "Victoria Day" : [ 'victoriaDay', 0 ], "Canada Day" : [ 'canadaDay', 0 ], "Natal Day" : [ 'firstAugustMonday', 0 ], "Labour Day" : [ 'firstSeptemberMonday', 0 ], "Thanksgiving" : [ 'firstOctoberMonday', 7 ], "Remembrance Day" : [ 11, 11 ], "Christmas Day" : [ 12, 25 ], "Boxing Day" : [ 12, 26 ] }, }, 'Nunavut': { 'PH': { "New Year's Day" : [ 1, 1 ], "Good Friday" : [ 'easter', -2 ], "Victoria Day" : [ 'victoriaDay', 0 ], "Canada Day" : [ 'canadaDay', 0 ], "Nunavut Day" : [ 7, 9 ], "Civic Holiday" : [ 'firstAugustMonday', 0 ], "Labour Day" : [ 'firstSeptemberMonday', 0 ], "Thanksgiving" : [ 'firstOctoberMonday', 7 ], "Remembrance Day" : [ 11, 11 ], "Christmas Day" : [ 12, 25 ] }, }, 'Ontario': { 'PH': { "New Year's Day" : [ 1, 1 ], "Family Day" : [ 'firstFebruaryMonday', 14 ], "Good Friday" : [ 'easter', -2 ], "Victoria Day" : [ 'victoriaDay', 0 ], "Canada Day" : [ 'canadaDay', 0 ], "August Civic Public Holiday" : [ 'firstAugustMonday', 0 ], "Labour Day" : [ 'firstSeptemberMonday', 0 ], "Thanksgiving" : [ 'firstOctoberMonday', 7 ], "Remembrance Day" : [ 11, 11 ], "Christmas Day" : [ 12, 25 ], "Boxing Day" : [ 12, 26 ] }, }, 'Prince Edward Island': { 'PH': { "New Year's Day" : [ 1, 1 ], "Islander Day" : [ 'firstFebruaryMonday', 14 ], "Good Friday" : [ 'easter', -2 ], "Easter Monday" : [ 'easter', 1 ], "Victoria Day" : [ 'victoriaDay', 0 ], "Canada Day" : [ 'canadaDay', 0 ], "Civic Holiday" : [ 'firstAugustMonday', 0 ], "Gold Cup Parade Day" : [ 'firstAugustMonday', 18 ], "Labour Day" : [ 'firstSeptemberMonday', 0 ], "Thanksgiving" : [ 'firstOctoberMonday', 7 ], "Remembrance Day" : [ 11, 11 ], "Christmas Day" : [ 12, 25 ], "Boxing Day" : [ 12, 26 ] }, }, 'Quebec': { 'PH': { "Jour de l'an" : [ 1, 1 ], "Vendredi saint" : [ 'easter', -2 ], "Lundi de Pâques" : [ 'easter', 1 ], "Journée nationale des patriotes" : [ 'victoriaDay', 0 ], "Fête nationale du Québec" : [ 6, 24 ], "Fête du Canada" : [ 'canadaDay', 0 ], "Fête du Travail" : [ 'firstSeptemberMonday', 0 ], "Jour de l'Action de grâce" : [ 'firstOctoberMonday', 7 ], "Noël" : [ 12, 25 ] }, }, 'Saskatchewan': { 'PH': { "New Year's Day" : [ 1, 1 ], "Family Day" : [ 'firstFebruaryMonday', 14 ], "Good Friday" : [ 'easter', -2 ], "Victoria Day" : [ 'victoriaDay', 0 ], "Canada Day" : [ 'canadaDay', 0 ], "Saskatchewan Day" : [ 'firstAugustMonday', 0 ], "Labour Day" : [ 'firstSeptemberMonday', 0 ], "Thanksgiving" : [ 'firstOctoberMonday', 7 ], "Remembrance Day" : [ 11, 11 ], "Christmas Day" : [ 12, 25 ] }, }, 'Yukon': { 'PH': { "New Year's Day" : [ 1, 1 ], "Heritage Day" : [ 'lastFebruarySunday', -2 ], "Good Friday" : [ 'easter', -2 ], "Easter Monday" : [ 'easter', 1 ], "Victoria Day" : [ 'victoriaDay', 0 ], "Canada Day" : [ 'canadaDay', 0 ], "Discovery Day" : [ 'firstAugustMonday', 14 ], "Labour Day" : [ 'firstSeptemberMonday', 0 ], "Thanksgiving" : [ 'firstOctoberMonday', 7 ], "Remembrance Day" : [ 11, 11 ], "Christmas Day" : [ 12, 25 ], "Boxing Day" : [ 12, 26 ] }, }, }, // }}} 'ua': { // {{{ '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 "Новий рік" : [ 1, 1 ], "Різдво" : [ 1, 7 ], "Міжнародний жіночий день" : [ 3, 8 ], "Великдень" : [ 'orthodox easter', 1 ], "День Праці 1" : [ 5, 1 ], "День Праці 2" : [ 5, 2 ], "День Перемоги" : [ 5, 9 ], "День Конституції України" : [ 6, 28 ], "День Незалежності України" : [ 8, 24 ], } }, // }}} 'si': { // {{{ 'PH': { // http://www.vlada.si/o_sloveniji/politicni_sistem/prazniki/ 'novo leto' : [ 1, 1 ], 'Prešernov dan, slovenski kulturni praznik' : [ 2, 8 ], 'velikonočna nedelja' : [ 'easter', 0 ], 'velikonočni ponedeljek' : [ 'easter', 1 ], 'dan upora proti okupatorju' : [ 4, 27 ], 'praznik dela 1' : [ 5, 1 ], 'praznik dela 2' : [ 5, 2 ], 'binkoštna nedelja - binkošti' : [ 'easter', 49 ], 'dan državnosti' : [ 6, 25 ], 'Marijino vnebovzetje' : [ 8, 15 ], 'dan reformacije' : [ 10, 31 ], 'dan spomina na mrtve' : [ 11, 1 ], 'božič' : [ 12, 25 ], 'dan samostojnosti in enotnosti' : [ 12, 26 ], }, }, // }}} }; // }}} // error correction {{{ // Taken form http://www.netzwolf.info/j/osm/time_domain.js // Credits go to Netzwolf // // Key to word_error_correction is the token name except wrong_words var word_error_correction = { wrong_words: { /* {{{ */ 'Assuming "" for "".': { 'Frühling': 'Mar-May', 'Frühjahr': 'Mar-May', 'Sommer': 'Jun-Aug', 'Herbst': 'Sep-Nov', 'winter': 'Dec-Feb', }, '"" wird als "" interpertiert.': { 'spring': 'Mar-May', 'summer': 'Jun-Aug', 'autumn': 'Sep-Nov', // 'winter': 'Dec-Feb', // Same as in English. // morning: '08:00-12:00', // evening: '13:00-18:00', '_': '-', 'daytime': 'sunrise-sunset', }, 'Bitte benutze die englische Schreibweise "" für "".': { 'sommer': 'summer', 'werktag': 'Mo-Fr', 'werktags': 'Mo-Fr', }, 'Bitte benutze "" für "". Beispiel: "Mo-Fr 08:00-12:00; Tu off".': { 'ruhetag': 'off', 'ruhetage': 'off', 'geschlossen': 'off', 'geschl': 'off', // 'ausser': 'off', // 'außer': 'off', }, 'Neem de engelse afkorting "" voor "" alstublieft.': { 'gesloten': 'off', 'feestdag': 'PH', 'feestdagen': 'PH', }, 'Assuming "" for "". Please avoid using "workday": http://wiki.openstreetmap.org/wiki/Talk:Key:opening_hours#need_syntax_for_holidays_and_workingdays': { // Used around 260 times but the problem is, that work day might be different in other countries. 'wd': 'Mo-Fr', 'on work day': 'Mo-Fr', 'on work days': 'Mo-Fr', 'weekday': 'Mo-Fr', 'weekdays': 'Mo-Fr', 'vardagar': 'Mo-Fr', }, 'Please use something like "Mo off" instead "".': { 'except': 'off', }, 'Please omit "" or use a colon instead: "12:00-14:00".': { 'h': '', }, 'Please omit "".': { 'season': '', 'hs': '', 'hrs': '', 'hours': '', '·': '', }, 'Please omit "". The key must not be in the value.': { 'opening_hours=': '', }, 'Please omit "". You might want to express open end which can be specified as "12:00+" for example.': { 'from': '', }, 'You can use notation "" for "" in the case that you want to express open end times. Example: "12:00+".': { 'til late': '+', 'till late': '+', '-late': '+', '-open end': '+', '-openend': '+', }, 'Please use notation "" for "". If the times are unsure or vary consider a comment e.g. 12:00-14:00 "only on sunshine".': { '~': '-', '~': '-', }, 'Please use notation "" for "". Fallback rule: 12:00-14:00 || "call us"': { 'otherwise': '||', }, 'You can use notation "" for "" temporally if the syntax will still be valid.': { '?': 'unknown "please add this if known"', }, 'Please use notation "" for "". Although using "–" is typographical correct, the opening_hours syntax is defined with the normal hyphen. Correct typography should be done on application level …': { '–': '-', }, 'Please use notation "" for "".': { '→': '-', '−': '-', '=': '-', 'ー': '-', 'to': '-', 'до': '-', 'a': '-', // language unknown 'as': '-', // language unknown 'á': '-', // language unknown 'ás': '-', // language unknown 'às': '-', // language unknown 'ate': '-', // language unknown 'till': '-', 'til': '-', 'until': '-', 'through': '-', 'and': ',', '&': ',', // '/': ',', // Can not be corrected as / is a valid token ':': ':', '°°': ':00', 'always': '24/7', 'nonstop': '24/7', '24x7': '24/7', 'anytime': '24/7', 'all day': '24/7', 'daily': 'Mo-Su', 'everyday': 'Mo-Su', 'every day': 'Mo-Su', 'all days': 'Mo-Su', '7j/7': 'Mo-Su', // I guess that it means that '7/7': 'Mo-Su', // I guess that it means that /* {{{ * Fixing this causes to ignore the following warning: "There should be no * reason to differ more than 6 days from a constrained * weekdays. If so tell us …". * The following mistake is expected to occur more often. */ '7days': 'Mo-Su', '7 days': 'Mo-Su', // }}} '7 days a week': 'Mo-Su', '7 days/week': 'Mo-Su', '24 hours 7 days a week': '24/7', '24 hours': '00:00-24:00', 'midday': '12:00', 'midnight': '00:00', 'holiday': 'PH', 'holidays': 'PH', 'public holidays': 'PH', 'public holiday': 'PH', 'day after public holiday': 'PH +1 day', 'one day after public holiday': 'PH +1 day', 'day before public holiday': 'PH -1 day', 'one day before public holiday': 'PH -1 day', 'school holiday': 'SH', 'school holidays': 'SH', // summerholiday: 'SH', // summerholidays: 'SH', /* Not implemented {{{ */ // 'day after school holiday': 'SH +1 day', // 'one day after school holiday': 'SH +1 day', // 'day before school holiday': 'SH -1 day', // 'one day before school holiday': 'SH -1 day', /* }}} */ 'weekend': 'Sa,Su', 'weekends': 'Sa,Su', 'daylight': 'sunrise-sunset', }, 'Please use notation "" for "". Those characters look very similar but are not the same!': { 'оff': 'off', // Russian o }, 'Please use time format in 24 hours notation (""). If PM is used you might have to convert the hours to the 24 hours notation.': { 'pm': '', 'рм': '', 'am': '', 'ам': '', }, 'Bitte verzichte auf "".': { 'uhr': '', 'geöffnet': '', 'zwischen': '', }, 'Bitte verzichte auf "". Sie möchten eventuell eine Öffnungszeit ohne vorgegebenes Ende (Open End) angeben. Beispiel: "12:00+"': { 'ab': '', 'von': '', }, '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 "" für "".': { 'damen': 'open "Damen"', 'herren': 'open "Herren"', }, 'Bitte benutze die Schreibweise "" für "".': { 'bis': '-', 'täglich': 'Mo-Su', 'schulferien': 'SH', 'sonn-/feiertag': 'PH,Su', 'sonn-/feiertags': 'PH,Su', 'an sonn- und feiertagen': 'PH,Su', 'nur sonn-/feiertags': 'PH,Su', }, 'Bitte benutze die Schreibweise "" für "". 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.': { '„': '"', '“': '"', '”': '"', }, 'Please use notation "" for "". 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.': { '«': '"', '»': '"', '‚': '"', '‘': '"', '’': '"', '「': '"', '」': '"', '『': '"', '』': '"', }, 'Please use notation "" for "". The used quote signs are not defined in the specification. See http://wiki.openstreetmap.org/wiki/Key:opening_hours:specification.': { "'": '"', }, 'You might want to use comments instead of brackets (which are not valid in this context). If you do, replace "" with "".': { // '(': '"', // ')': '"', }, 'Bitte benutze die Schreibweise "" als Ersatz für "und" bzw. "u.".': { 'und': ',', 'u': ',', }, 'Bitte benutze die englische Abkürzung "" für "".': { 'feiertag': 'PH', 'feiertags': 'PH', 'feiertage': 'PH', 'feiertagen': 'PH' }, 'S\'il vous plaît utiliser "" pour "".': { 'fermé': 'off', 'et': ',', 'à': '-', 'jours fériés': 'PH', } }, /* }}} */ month: { /* {{{ */ 'default': { 'jan': 0, 'feb': 1, 'mar': 2, 'apr': 3, 'may': 4, 'jun': 5, 'jul': 6, 'aug': 7, 'sep': 8, 'oct': 9, 'nov': 10, 'dec': 11, }, 'Please use the English abbreviation "" for "".': { 'jänner': 0, // Austria 'january': 0, 'february': 1, 'march': 2, 'april': 3, // 'may': 4, 'june': 5, 'july': 6, 'august': 7, 'september': 8, 'sept': 8, 'october': 9, 'november': 10, 'december': 11, }, 'Bitte benutze die englische Abkürzung "" für "".': { 'januar': 0, 'februar': 1, 'märz': 2, 'maerz': 2, 'mai': 4, 'juni': 5, 'juli': 6, 'okt': 9, 'oktober': 9, 'dez': 11, 'dezember': 11, }, 'S\'il vous plaît utiliser l\'abréviation "" pour "".': { 'janvier': 0, 'février': 1, 'fév': 1, 'mars': 2, 'avril': 3, 'avr': 3, 'mai': 4, 'juin': 5, 'juillet': 6, 'août': 7, 'aoû': 7, 'septembre': 8, 'octobre': 9, 'novembre': 10, 'décembre': 11, }, 'Neem de engelse afkorting "" voor "" alstublieft.': { 'januari': 0, 'februari': 1, 'maart': 2, 'mei': 4, 'augustus': 7, } }, /* }}} */ calcday: { 'default': { 'day': 'day', 'days': 'days', }, }, weekday: { // {{{ Good source: http://www.omniglot.com/language/time/days.htm */ 'default': { 'su': 0, 'mo': 1, 'tu': 2, 'we': 3, 'th': 4, 'fr': 5, 'sa': 6, }, 'Assuming "" for ""': { 'm': 1, 'w': 3, 'f': 5, }, 'Please use the abbreviation "" for "".': { 'sun': 0, 'sunday': 0, 'sundays': 0, 'mon': 1, 'monday': 1, 'mondays': 1, 'tue': 2, 'tues': 2, // Used here: http://www.westerhambeauty.co.uk/contact.php 'tuesday': 2, 'tuesdays': 2, 'wed': 3, 'weds': 3, 'wednesday': 3, 'wednesdays': 3, 'thu': 4, 'thur': 4, 'thurs': 4, 'thursday': 4, 'thursdays': 4, 'fri': 5, 'friday': 5, 'fridays': 5, 'sat': 6, 'saturday': 6, 'saturdays': 6, }, 'Bitte benutze die englische Abkürzung "" für "". Could also mean Saturday in Polish …': { 'so': 0, }, 'Bitte benutze die englische Abkürzung "" für "".': { 'son': 0, 'sonntag': 0, 'sonn-': 0, 'sonntags': 0, 'montag': 1, 'montags': 1, 'di': 2, 'die': 2, 'dienstag': 2, 'dienstags': 2, 'mi': 3, 'mit': 3, 'mittwoch': 3, 'mittwochs': 3, 'do': 4, 'don': 4, 'donnerstag': 4, 'donnerstags': 4, 'fre': 5, 'freitag': 5, 'freitags': 5, 'sam': 6, 'samstag': 6, 'samstags': 6, }, 'S\'il vous plaît utiliser l\'abréviation "" pour "".': { 'dim': 0, 'dimanche': 0, 'lu': 1, 'lun': 1, 'lundi': 1, 'mardi': 2, 'mer': 3, 'mercredi': 3, 'je': 4, 'jeu': 4, 'jeudi': 4, 've': 5, 'ven': 5, 'vendredi': 5, 'samedi': 6, }, 'Neem de engelse afkorting "" voor "" alstublieft.': { 'zo': 0, 'zon': 0, 'zontag': 0, // correct? 'zondag': 0, 'maandag': 1, 'din': 2, 'dinsdag': 2, 'wo': 3, 'woe': 3, 'woensdag': 3, 'donderdag': 4, 'vr': 5, 'vri': 5, 'vrijdag': 5, 'za': 6, 'zat': 6, 'zaterdag': 6, }, 'Please use the English abbreviation "" for "".': { // FIXME: Translate to Czech. 'neděle': 0, 'ne': 0, 'pondělí': 1, 'po': 1, 'úterý': 2, 'út': 2, 'středa': 3, 'st': 3, 'čtvrtek': 4, 'čt': 4, 'pátek': 5, 'pá': 5, 'sobota': 6, }, 'Please use the English abbreviation "" (Spanish) for "".': { 'martes': 0, 'miércoles': 1, 'jueves': 2, 'viernes': 3, 'sábado': 4, 'domingo': 5, 'lunes': 6, }, 'Please use the English abbreviation "" (Indonesian) for "".': { 'selasa': 0, 'rabu': 1, 'kami': 2, 'jumat': 3, 'sabtu': 4, 'minggu': 5, 'senin': 6, }, 'Please use the English abbreviation "" (Swedish) for "".': { 'söndag': 0, 'söndagar': 0, 'måndag': 1, 'ma': 1, 'tisdag': 2, 'onsdag': 3, // Same in Danish 'torsdag': 4, 'fredag': 5, 'lördag': 6, 'lördagar': 6, }, 'Please use the English abbreviation "" (Polish) for "".': { 'niedziela': 0, 'niedz': 0, 'n': 0, 'ndz': 0, 'poniedziałek': 1, 'poniedzialek': 1, 'pon': 1, 'pn': 1, 'wtorek': 2, 'wt': 2, 'środa': 3, 'sroda': 3, 'śr': 3, 'sr': 3, 'czwartek': 4, 'czw': 4, 'cz': 4, 'piątek': 5, 'piatek': 5, 'pt': 5, 'sobota': 6, 'sob': 6, // 'so': 6 // abbreviation also used in German }, 'Please use the English abbreviation "" (Russian) for "".': { 'воскресенье' : 0, 'Вс' : 0, "voskresen'ye": 0, 'понедельник' : 1, 'Пн' : 1, "ponedel'nik" : 1, 'вторник' : 2, 'vtornik' : 2, 'среда' : 3, 'sreda' : 3, 'четверг' : 4, 'chetverk' : 4, 'пятница' : 5, 'pyatnitsa' : 5, 'суббота' : 6, 'subbota' : 6, }, 'Please use the English abbreviation "" (Danish) for "".': { 'søndag' : 0, 'mandag' : 1, 'tirsdag': 2, 'onsdag' : 3, // Same in Swedish 'torsdag': 4, 'fredag' : 5, 'lørdag' : 6, }, }, /* }}} */ timevar: { /* {{{ Special time variables which actual value depends on the date and the position of the facility. */ 'default': { 'sunrise': 'sunrise', 'sunset': 'sunset', 'dawn': 'dawn', 'dusk': 'dusk', }, 'Please use notation "" for "".': { 'sundown': 'sunset', }, 'Bitte benutze die Schreibweise "" für "".': { 'morgendämmerung': 'dawn', 'abenddämmerung': 'dusk', 'sonnenaufgang': 'sunrise', 'sonnenuntergang': 'sunset', }, }, /* }}} */ 'event': { // variable events 'default': { 'easter': 'easter', }, 'Bitte benutze die Schreibweise "" für "".': { 'ostern': 'easter', }, }, }; // }}} // }}} // make the library accessible for the outside world {{{ if (typeof exports === 'object') { // For nodejs var SunCalc = require('suncalc'); module.exports = factory(SunCalc, holidays, word_error_correction); } else { // For browsers root.opening_hours = factory(root.SunCalc, holidays, word_error_correction); } /// }}} }(this, function (SunCalc, holidays, word_error_correction) { return function(value, nominatiomJSON, oh_mode) { // short constants {{{ var word_value_replacement = { // If the correct values can not be calculated. dawn : 60 * 5 + 30, sunrise : 60 * 6, sunset : 60 * 18, dusk : 60 * 18 + 30, }; var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; var weekdays = ['Su','Mo','Tu','We','Th','Fr','Sa']; var default_prettify_conf = { // Update README.md if changed. 'zero_pad_hour': true, // enforce ("%02d", hour) 'one_zero_if_hour_zero': false, // only one zero "0" if hour is zero "0" 'leave_off_closed': true, // leave keywords "off" and "closed" as is 'keyword_for_off_closed': 'off', // use given keyword instead of "off" or "closed" 'rule_sep_string': ' ', // separate rules by string 'print_semicolon': true, // print token which separates normal rules '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 'sep_one_day_between': ',', // separator which should be used 'zero_pad_month_and_week_numbers': false, // Format week (e.g. `week 01`) and month day numbers (e.g. `Jan 01`) with "%02d". }; var minutes_in_day = 60 * 24; var msec_in_day = 1000 * 60 * minutes_in_day; var msec_in_week = msec_in_day * 7; var library_name = 'opening_hours.js'; var repository_url = 'https://github.com/ypid/' + library_name; var issues_url = repository_url + '/issues?state=open'; // }}} // constructor parameters {{{ // Evaluate additional information which can be given. They are // required to reasonably calculate 'sunrise' and to use the correct // holidays. var location_cc, location_state, lat, lon; if (typeof nominatiomJSON != 'undefined') { if (typeof nominatiomJSON.address != 'undefined' && typeof nominatiomJSON.address.state != 'undefined') { // country_code will be tested later … location_cc = nominatiomJSON.address.country_code; location_state = nominatiomJSON.address.state; } if (typeof nominatiomJSON.lon != 'undefined') { // lat will be tested later … lat = nominatiomJSON.lat; lon = nominatiomJSON.lon; } } // 0: time ranges (default), tags: opening_hours, lit, … // 1: points in time // 2: both (time ranges and points in time), tags: collection_times, service_times if (typeof oh_mode == 'undefined') { oh_mode = 0; } else if (!(typeof oh_mode == 'number' && (oh_mode === 0 || oh_mode == 1 || oh_mode == 2))) { throw 'The third constructor parameter is oh_mode and must be a number (0, 1 or 2)'; } // }}} // Tokenize value and generate selector functions. {{{ if (value.match(/^(?:\s*;?\s*)+$/)) throw 'Value contains nothing meaningful which can be parsed'; var parsing_warnings = []; // Elements are fed into function formatWarnErrorMessage(nrule, at, message) var done_with_warnings = false; // The functions which throw warnings can be called multiple times. var done_with_selector_reordering = false; var done_with_selector_reordering_warnings = false; var tokens = tokenize(value); // console.log(JSON.stringify(tokens, null, ' ')); var prettified_value = ''; var week_stable = true; var rules = []; var new_tokens = []; for (var nrule = 0; nrule < tokens.length; nrule++) { if (tokens[nrule][0].length === 0) { // Rule does contain nothing useful e.g. second rule of '10:00-12:00;' (empty) which needs to be handled. parsing_warnings.push([nrule, -1, 'This rule does not contain anything useful. Please remove this empty rule.' + (nrule == tokens.length - 1 && nrule > 0 && !tokens[nrule][1] ? ' Might it be possible that you are a programmer and adding a semicolon after each statement is hardwired in your muscle memory ;) ?' + ' The thing is that the semicolon in the opening_hours syntax is defined as rule separator.' + ' So for compatibility reasons you should omit this last semicolon.': '') ]); continue; } var continue_at = 0; var next_rule_is_additional = false; do { if (continue_at == tokens[nrule][0].length) break; // Additional rule does contain nothing useful e.g. second rule of '10:00-12:00,' (empty) which needs to be handled. var selectors = { // Time selectors time: [], // Temporary array of selectors from time wrapped to the next day wraptime: [], // Date selectors weekday: [], holiday: [], week: [], month: [], monthday: [], year: [], // Array with non-empty date selector types, with most optimal ordering date: [], fallback: tokens[nrule][1], additional: continue_at ? true : false, meaning: true, unknown: false, comment: undefined, build_from_token_rule: undefined, }; selectors.build_from_token_rule = [ nrule, continue_at, new_tokens.length ]; continue_at = parseGroup(tokens[nrule][0], continue_at, selectors, nrule); if (typeof continue_at == 'object') { continue_at = continue_at[0]; } else { continue_at = 0; } // console.log('Current tokens: ' + JSON.stringify(tokens[nrule], null, ' ')); new_tokens.push( [ tokens[nrule][0].slice( selectors.build_from_token_rule[1], continue_at === 0 ? tokens[nrule][0].length : continue_at ), tokens[nrule][1], tokens[nrule][2], ] ); if (next_rule_is_additional && new_tokens.length > 1) { // Move 'rule separator' from last token of last rule to first token of this rule. new_tokens[new_tokens.length - 1][0].unshift(new_tokens[new_tokens.length - 2][0].pop()); } next_rule_is_additional = continue_at === 0 ? false : true; if (selectors.year.length > 0) selectors.date.push(selectors.year); if (selectors.holiday.length > 0) selectors.date.push(selectors.holiday); if (selectors.month.length > 0) selectors.date.push(selectors.month); if (selectors.monthday.length > 0) selectors.date.push(selectors.monthday); if (selectors.week.length > 0) selectors.date.push(selectors.week); if (selectors.weekday.length > 0) selectors.date.push(selectors.weekday); // console.log('weekday: ' + JSON.stringify(selectors.weekday, null, '\t')); rules.push(selectors); // This handles selectors with time ranges wrapping over midnight (e.g. 10:00-02:00) // it generates wrappers for all selectors and creates a new rule. if (selectors.wraptime.length > 0) { var wrapselectors = { time: selectors.wraptime, date: [], meaning: selectors.meaning, unknown: selectors.unknown, comment: selectors.comment, wrapped: true, // build_from_token_rule: selectors.build_from_token_rule, // Not (yet) needed. }; for (var dselg = 0; dselg < selectors.date.length; dselg++) { wrapselectors.date.push([]); for (var dsel = 0; dsel < selectors.date[dselg].length; dsel++) { wrapselectors.date[wrapselectors.date.length-1].push( generateDateShifter(selectors.date[dselg][dsel], -msec_in_day) ); } } rules.push(wrapselectors); } } while (continue_at); } // console.log(JSON.stringify(tokens, null, ' ')); // console.log(JSON.stringify(new_tokens, null, ' ')); // }}} /* Format warning or error message for the user. {{{ * * :param nrule: Rule number starting with zero. * :param at: Token position at which the issue occurred. * :param message: Human readable string with the message. * :returns: String with position of the warning or error marked for the user. */ function formatWarnErrorMessage(nrule, at, message) { // FIXME: Change to new_tokens. if (typeof nrule == 'number') { var pos = 0; if (nrule == -1) { // Usage of rule index not required because we do have access to value.length. pos = value.length - at; } else { // Issue accrued at a later time, position in string needs to be reconstructed. if (typeof tokens[nrule][0][at] == 'undefined') { if (typeof tokens[nrule][0] && at == -1) { pos = value.length; if (typeof tokens[nrule+1] == 'object' && typeof tokens[nrule+1][2] == 'number') { pos -= tokens[nrule+1][2]; } else if (typeof tokens[nrule][2] == 'number') { pos -= tokens[nrule][2]; } } else { // Given position is invalid. // formatLibraryBugMessage('Bug in warning generation code which could not determine the exact position of the warning or error in value.'); pos = value.length; if (typeof tokens[nrule][2] != 'undefined') { // Fallback: Point to last token in the rule which caused the problem. // Run real_test regularly to fix the problem before a user is confronted with it. pos -= tokens[nrule][2]; console.warn('Last token for rule: ' + tokens[nrule]); console.log(value.substring(0, pos) + ' <--- (' + message + ')'); console.log('\n'); } { console.warn('tokens[nrule][2] is undefined. This is ok if nrule is the last rule.'); } } } else { pos = value.length; if (typeof tokens[nrule][0][at+1] != 'undefined') { pos -= tokens[nrule][0][at+1][2]; } else if (typeof tokens[nrule][2] != 'undefined') { pos -= tokens[nrule][2]; } } } return value.substring(0, pos) + ' <--- (' + message + ')'; } else if (typeof nrule == 'string') { return nrule.substring(0, at) + ' <--- (' + message + ')'; } } // }}} /* Format internal library error message. {{{ * * :param message: Human readable string with the error message. * :returns: Error message for the user. */ function formatLibraryBugMessage(message) { if (typeof message == 'undefined') message = ''; else message = ' ' + message; message = 'An error occurred during evaluation of the value "' + value + '".' + ' Please file a bug report here: ' + issues_url + '.' + message; console.log(message); return message; } // }}} /* Tokenize input stream. {{{ * * :param value: Raw opening_hours value. * :returns: Tokenized list object. Complex structure. Check the * internal documentation in the doc directory for details. */ function tokenize(value) { var all_tokens = []; var curr_rule_tokens = []; var last_rule_fallback_terminated = false; while (value !== '') { // console.log("Parsing value: " + value); var tmp; if (tmp = value.match(/^week\b/i)) { // Reserved keywords. curr_rule_tokens.push([tmp[0].toLowerCase(), tmp[0].toLowerCase(), value.length ]); value = value.substr(tmp[0].length); } else if (tmp = value.match(/^(?:off\b|closed\b|open\b|unknown\b)/i)) { // Reserved keywords. curr_rule_tokens.push([tmp[0].toLowerCase(), 'state', value.length ]); value = value.substr(tmp[0].length); } else if (tmp = value.match(/^24\/7/i)) { // Reserved keyword. curr_rule_tokens.push([tmp[0], tmp[0], value.length ]); value = value.substr(tmp[0].length); } else if (tmp = value.match(/^(?:PH|SH)/i)) { // special day name (holidays) curr_rule_tokens.push([tmp[0].toUpperCase(), 'holiday', value.length ]); value = value.substr(2); } 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? )?late|open[ ]?end)|(?:(?:one )?day (?:before|after) )?(?:school|public) holidays?|days?\b|до|рм|ам|jours fériés|on work days?|(?: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)) { /* Handle all remaining words and specific other characters with error tolerance. * * à|á: Word boundary does not work with unicode chars: 'test à test'.match(/\bà\b/i) * https://stackoverflow.com/questions/10590098/javascript-regexp-word-boundaries-unicode-characters * Order in the regular expression capturing group is important in some cases. * * mo|tu|we|th|fr|sa|su|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec: Prefer defended keywords * if used in cases like 'mo12:00-14:00' (when keyword is followed by number). */ var correct_val = returnCorrectWordOrToken(tmp[1].toLowerCase(), value.length); // console.log('Error tolerance for string "' + tmp[1] + '" returned "' + correct_val + '".'); if (typeof correct_val == 'object') { curr_rule_tokens.push([ correct_val[0], correct_val[1], value.length ]); value = value.substr(tmp[0].length); } else if (typeof correct_val == 'string') { if (tmp[1].toLowerCase() == 'pm') { var hours_token_at = curr_rule_tokens.length - 1; var hours_token; if (hours_token_at >= 0) { if (hours_token_at -2 >= 0 && matchTokens( curr_rule_tokens, hours_token_at - 2, 'number', 'timesep', 'number' ) ) { hours_token_at -= 2; hours_token = curr_rule_tokens[hours_token_at]; } else if (matchTokens(curr_rule_tokens, hours_token_at, 'number')) { hours_token = curr_rule_tokens[hours_token_at]; } if (typeof hours_token == 'object' && hours_token[0] <= 12) { hours_token[0] += 12; curr_rule_tokens[hours_token_at] = hours_token; } } } var correct_tokens = tokenize(correct_val)[0]; if (correct_tokens[1] === true) { // last_rule_fallback_terminated throw formatLibraryBugMessage(); } for (var i = 0; i < correct_tokens[0].length; i++) { curr_rule_tokens.push([correct_tokens[0][i][0], correct_tokens[0][i][1], value.length]); // value.length - tmp[0].length does not have the desired effect for all test cases. } value = value.substr(tmp[0].length); // value = correct_val + value.substr(tmp[0].length); // Does not work because it would generate the wrong length for formatWarnErrorMessage. } else { // other single-character tokens curr_rule_tokens.push([value[0].toLowerCase(), value[0].toLowerCase(), value.length - 1 ]); value = value.substr(1); } } else if (tmp = value.match(/^\d+/)) { // number if (Number(tmp[0]) > 1900) { // Assumed to be a year number. curr_rule_tokens.push([tmp[0], 'year', value.length ]); if (Number(tmp[0]) >= 2100) // Probably an error parsing_warnings.push([ -1, value.length - 1, 'The number ' + Number(tmp[0]) + ' will be interpreted as year.' + ' This is probably not intended. Times can be specified as "12:00".' ]); } else { curr_rule_tokens.push([Number(tmp[0]), 'number', value.length ]); } value = value.substr(tmp[0].length); } else if (tmp = value.match(/^"([^"]+)"/)) { // Comment following the specification. // Any character is allowed inside the comment except " itself. curr_rule_tokens.push([tmp[1], 'comment', value.length ]); value = value.substr(tmp[0].length); } else if (tmp = value.match(/^(["'„“‚‘’«「『])([^"'“”‘’»」』;|]*)(["'”“‘’»」』])/)) { // Comments with error tolerance. // The comments still have to be somewhat correct meaning // the start and end quote signs used have to be // appropriate. So “testing„ will not match as it is not a // quote but rather something unknown which the user should // fix first. // console.log('Matched: ' + JSON.stringify(tmp)); for (var pos = 1; pos <= 3; pos += 2) { // console.log('Pos: ' + pos + ', substring: ' + tmp[pos]); var correct_val = returnCorrectWordOrToken(tmp[pos], value.length - (pos == 3 ? tmp[1].length + tmp[2].length : 0) ); if (typeof correct_val != 'string' && tmp[pos] != '"') { throw formatLibraryBugMessage( 'A character for error tolerance was allowed in the regular expression' + ' but is not covered by word_error_correction' + ' which is needed to format a proper message for the user.' ); } } curr_rule_tokens.push([tmp[2], 'comment', value.length ]); value = value.substr(tmp[0].length); } else if (value.match(/^;/)) { // semicolon terminates rule // next tokens belong to a new rule all_tokens.push([ curr_rule_tokens, last_rule_fallback_terminated, value.length ]); value = value.substr(1); curr_rule_tokens = []; last_rule_fallback_terminated = false; } else if (value.match(/^\|\|/)) { // || terminates rule // Next tokens belong to a fallback rule. if (curr_rule_tokens.length === 0) throw formatWarnErrorMessage(-1, value.length - 2, 'Rule before fallback rule does not contain anything useful'); all_tokens.push([ curr_rule_tokens, last_rule_fallback_terminated, value.length ]); curr_rule_tokens = []; // curr_rule_tokens = [ [ '||', 'rule separator', value.length ] ]; // FIXME: Use this. Unknown bug needs to be solved in the process. value = value.substr(2); last_rule_fallback_terminated = true; } else if (value.match(/^(?:␣|\s)/)) { // Using "␣" as space is not expected to be a normal // mistake. Just ignore it to make using taginfo easier. value = value.substr(1); } else if (tmp = value.match(/^\s+/)) { // whitespace is ignored value = value.substr(tmp[0].length); } else if (value.match(/^[:.]/)) { // time separator if (value[0] == '.' && !done_with_warnings) parsing_warnings.push([ -1, value.length - 1, 'Please use ":" as hour/minute-separator' ]); curr_rule_tokens.push([ ':', 'timesep', value.length ]); value = value.substr(1); } else { // other single-character tokens curr_rule_tokens.push([value[0].toLowerCase(), value[0].toLowerCase(), value.length ]); value = value.substr(1); } } all_tokens.push([ curr_rule_tokens, last_rule_fallback_terminated ]); return all_tokens; } // }}} /* error correction/tolerance function {{{ * Go through word_error_correction hash and get correct value back. * * :param word: Wrong Word or character. * :param value_length: Current value_length (used for warnings). * :returns: * * (valid) opening_hours sub string. * * object with [ internal_value, token_name ] if value is correct. * * undefined if word could not be found (and thus is not be corrected). */ function returnCorrectWordOrToken(word, value_length) { for (var token_name in word_error_correction) { for (var comment in word_error_correction[token_name]) { for (var old_val in word_error_correction[token_name][comment]) { if (old_val == word) { var val = word_error_correction[token_name][comment][old_val]; if (comment == 'default') { // Return internal representation of word. return [ val, token_name ]; } else if (token_name == 'wrong_words' && !done_with_warnings) { // Replace wrong words or characters with correct ones. // This will return a string which is then being tokenized. parsing_warnings.push([ -1, value_length - old_val.length, comment.replace(//, old_val).replace(//, val) ]); return val; } else { // Get correct string value from the 'default' hash and generate warning. var correct_abbr; for (correct_abbr in word_error_correction[token_name]['default']) { if (word_error_correction[token_name]['default'][correct_abbr] == val) break; } if (typeof correct_abbr == 'undefined') { throw formatLibraryBugMessage('Including the stacktrace.'); } if (token_name != 'timevar') { // Everything else than timevar: // E.g. 'Mo' start with a upper case letter. // It just looks better. correct_abbr = correct_abbr.charAt(0).toUpperCase() + correct_abbr.slice(1); } if (!done_with_warnings) parsing_warnings.push([ -1, value_length - old_val.length, comment.replace(//, old_val).replace(//, correct_abbr) ]); return [ val, token_name ]; } } } } } return undefined; } // }}} /* return warnings as list {{{ * * :param it: Iterator object if available (optional). * :returns: Warnings as list with one warning per element. */ function getWarnings(it) { if (!done_with_warnings && typeof it == 'object') { /* getWarnings was called in a state without critical errors. * We can do extended tests. */ /* Place all tests in this function if an additional (high * level) test is added and this does not require to rewrite * big parts of (sub) selector parsers only to get the * position. If that is the case, then rather place the test * code in the (sub) selector parser function directly. */ var wide_range_selectors = [ 'year', 'month', 'week', 'holiday' ]; var small_range_selectors = [ 'weekday', 'time', '24/7', 'state', 'comment']; // How many times was a selector_type used per rule? {{{ var used_selectors = []; var used_selectors_types_array = []; var has_token = {}; for (var nrule = 0; nrule < new_tokens.length; nrule++) { if (new_tokens[nrule][0].length === 0) continue; // Rule does contain nothing useful e.g. second rule of '10:00-12:00;' (empty) which needs to be handled. var selector_start_end_type = [ 0, 0, undefined ], prettified_group_value = []; // console.log(new_tokens[nrule][0]); used_selectors[nrule] = {}; used_selectors_types_array[nrule] = []; do { selector_start_end_type = getSelectorRange(new_tokens[nrule][0], selector_start_end_type[1]); // console.log(selector_start_end_type, new_tokens[nrule][0].length); if (selector_start_end_type[0] == selector_start_end_type[1] && new_tokens[nrule][0][selector_start_end_type[0]][0] == '24/7' ) { has_token['24/7'] = true; } if (typeof used_selectors[nrule][selector_start_end_type[2]] != 'object') { used_selectors[nrule][selector_start_end_type[2]] = [ selector_start_end_type[1] ]; } else { used_selectors[nrule][selector_start_end_type[2]].push(selector_start_end_type[1]); } used_selectors_types_array[nrule].push(selector_start_end_type[2]); selector_start_end_type[1]++; } while (selector_start_end_type[1] < new_tokens[nrule][0].length); } // console.log('used_selectors: ' + JSON.stringify(used_selectors, null, ' ')); // }}} for (var nrule = 0; nrule < used_selectors.length; nrule++) { /* Check if more than one not connected selector of the same type is used in one rule {{{ */ for (var selector_type in used_selectors[nrule]) { // console.log(selector_type + ' use at: ' + used_selectors[nrule][selector_type].length); if (used_selectors[nrule][selector_type].length > 1) { parsing_warnings.push([nrule, used_selectors[nrule][selector_type][used_selectors[nrule][selector_type].length - 1], 'You have used ' + used_selectors[nrule][selector_type].length + (selector_type.match(/^(?:comment|state)/) ? ' ' + selector_type + (selector_type == 'state' ? ' keywords' : 's') + ' in one rule.' + ' You may only use one in one rule.' : ' not connected ' + selector_type + (selector_type.match(/^(?:month|weekday)$/) ? 's' : ' ranges') + ' in one rule.' + ' This is probably an error.' + ' Equal selector types can (and should) always be written in conjunction separated by comma or something.' + ' Example for time ranges "12:00-13:00,15:00-18:00".' + ' Example for weekdays "Mo-We,Fr".' ) + ' Rules can be separated by ";".' ] ); done_with_selector_reordering = true; // Correcting the selector order makes no sense if this kind of issue exists. } } /* }}} */ /* Check if change default state rule is not the first rule {{{ */ if ( typeof used_selectors[nrule].state === 'object' && Object.keys(used_selectors[nrule]).length === 1 ) { if (nrule !== 0) { parsing_warnings.push([nrule, new_tokens[nrule][0].length - 1, "This rule which changes the default state (which is closed) for all following rules is not the first rule." + " The rule will overwrite all previous rules." + " It can be legitimate to change the default state to open for example" + " and then only specify for which times the facility is closed." ]); } /* }}} */ /* Check if a rule (with state open) has no time selector {{{ */ } else if (typeof used_selectors[nrule].time === 'undefined') { if ( ( typeof used_selectors[nrule].state === 'object' && new_tokens[nrule][0][used_selectors[nrule].state[0]][0] === 'open' && typeof used_selectors[nrule].comment === 'undefined' ) || ( typeof used_selectors[nrule].comment === 'undefined' && typeof used_selectors[nrule].state === 'undefined' ) && typeof used_selectors[nrule]['24/7'] === 'undefined' ) { parsing_warnings.push([nrule, new_tokens[nrule][0].length - 1, "This rule is not very explicit because there is no time selector being used." + " Please add a time selector to this rule or use a comment to make it more explicit." ]); } } /* }}} */ /* Check if empty comment was given {{{ */ if (typeof used_selectors[nrule].comment === 'object' && new_tokens[nrule][0][used_selectors[nrule].comment[0]][0].length === 0 ) { parsing_warnings.push([nrule, used_selectors[nrule].comment[0], "You have used an empty comment." + " Please either write something in the comment or use the keyword unknown instead." ]); } /* }}} */ /* Check if rule with closed|off modifier is additional {{{ */ /* FIXME: Enable this test. */ if (typeof new_tokens[nrule][0][0] === 'object' && new_tokens[nrule][0][0][0] === ',' && new_tokens[nrule][0][0][1] === 'rule separator' && typeof used_selectors[nrule].state === 'object' && ( new_tokens[nrule][0][used_selectors[nrule].state[0]][0] === 'closed' || new_tokens[nrule][0][used_selectors[nrule].state[0]][0] === 'off' ) ) { // parsing_warnings.push([nrule, new_tokens[nrule][0].length - 1, // "This rule will be evaluated as closed but it was specified as additional rule." // + " It is enough to specify this rule as normal rule using the \";\" character." // + " See https://wiki.openstreetmap.org/wiki/Key:opening_hours:specification#explain:rule_modifier:closed." // ]); } /* }}} */ /* Check for valid use of {{{ */ for (var i = 0; i < used_selectors_types_array[nrule].length - 1; i++) { var selector_type = used_selectors_types_array[nrule][i]; var next_selector_type = used_selectors_types_array[nrule][i+1]; if ( ( wide_range_selectors.indexOf(selector_type) != -1 && wide_range_selectors.indexOf(next_selector_type) != -1 ) || ( small_range_selectors.indexOf(selector_type) != -1 && small_range_selectors.indexOf(next_selector_type) != -1) ) { if (new_tokens[nrule][0][used_selectors[nrule][selector_type][0]][0] == ':') { parsing_warnings.push([nrule, used_selectors[nrule][selector_type][0], "You have used the optional symbol in the wrong place." + " Please check the syntax specification to see where it could be used or remove it." ]); } } } /* }}} */ } /* Check if 24/7 is used and it does not mean 24/7 because there are other rules {{{ */ var has_advanced = it.advance(); if (has_advanced === true && has_token['24/7'] && !done_with_warnings) { parsing_warnings.push([ -1, 0, 'You used 24/7 in a way that is probably not interpreted as "24 hours 7 days a week".' // Probably because of: "24/7; 12:00-14:00 open", ". Needs extra testing. + ' For correctness you might want to use "open" or "closed"' + ' for this rule and then write your exceptions which should achieve the same goal and is more clear' + ' e.g. "open; Mo 12:00-14:00 off".']); } /* }}} */ prettifyValue(); } done_with_warnings = true; var warnings = []; // FIXME: Sort based on parsing_warnings[1], tricky … for (var i = 0; i < parsing_warnings.length; i++) { warnings.push( formatWarnErrorMessage(parsing_warnings[i][0], parsing_warnings[i][1], parsing_warnings[i][2]) ); } return warnings; } /* Helpers for getWarnings {{{ */ /* Check if token is the begin of a selector and why. {{{ * * :param tokens: List of token objects. * :param at: Position where to start. * :returns: * * false the current token is not the begin of a selector. * * Position in token array from where the decision was made that * the token is the start of a selector. */ function tokenIsTheBeginOfSelector(tokens, at) { if (typeof tokens[at][3] == 'string') { return 3; } else if (tokens[at][1] == 'comment' || tokens[at][1] == 'state' || tokens[at][1] == '24/7' || tokens[at][1] == 'rule separator' ){ return 1; } else { return false; } } /* }}} */ /* Get start and end position of a selector. {{{ * For example this value 'Mo-We,Fr' will return the position of the * token lexeme 'Mo' and 'Fr' e.g. there indexes [ 0, 4 ] in the * selector array of tokens. * * :param tokens: List of token objects. * :param at: Position where to start. * :returns: Array: * 0. Index of first token in selector array of tokens. * 1. Index of last token in selector array of tokens. * 2. Selector type. */ function getSelectorRange(tokens, at) { var selector_start = at, selector_end, pos_in_token_array; for (; selector_start >= 0; selector_start--) { pos_in_token_array = tokenIsTheBeginOfSelector(tokens, selector_start); if (pos_in_token_array) break; } selector_end = selector_start; if (pos_in_token_array === 1) { // Selector consists of a single token. // Include tailing colon. if (selector_end + 1 < tokens.length && tokens[selector_end + 1][0] == ':') selector_end++; return [ selector_start, selector_end, tokens[selector_start][pos_in_token_array] ]; } for (selector_end++; selector_end < tokens.length ; selector_end++) { if (tokenIsTheBeginOfSelector(tokens, selector_end)) return [ selector_start, selector_end - 1, tokens[selector_start][pos_in_token_array] ]; } return [ selector_start, selector_end - 1, tokens[selector_start][pos_in_token_array] ]; } /* }}} */ /* }}} */ /* }}} */ /* Prettify raw value from user. {{{ * The value is generated by putting the tokens back together to a string. * * :param argument_hash: Hash which can contain: * 'conf': Configuration hash. * 'get_internals: If true export internal data structures. * 'rule_index: Only prettify the rule with this index. * :returns: Prettified value string or object if get_internals is true. */ function prettifyValue(argument_hash) { var user_conf = {}, get_internals = false, rule_index; if (typeof argument_hash != 'undefined') { if (typeof argument_hash.conf === 'object') user_conf = argument_hash.conf; if (typeof argument_hash.rule_index === 'number') rule_index = argument_hash.rule_index; if (argument_hash.get_internals === true) get_internals = true; } for (var key in default_prettify_conf) { if (typeof user_conf[key] == 'undefined') user_conf[key] = default_prettify_conf[key]; } prettified_value = ''; var prettified_value_array = []; for (var nrule = 0; nrule < new_tokens.length; nrule++) { if (new_tokens[nrule][0].length === 0) continue; // Rule does contain nothing useful e.g. second rule of '10:00-12:00;' (empty) which needs to be handled. if (typeof rule_index == 'number') { if (rule_index != nrule) continue; } else { if (nrule !== 0) prettified_value += ( new_tokens[nrule][1] ? user_conf.rule_sep_string + '|| ' : ( new_tokens[nrule][0][0][1] == 'rule separator' ? ',' : ( user_conf.print_semicolon ? ';' : '' ) ) + user_conf.rule_sep_string); } var selector_start_end_type = [ 0, 0, undefined ], prettified_group_value = []; // console.log(new_tokens[nrule][0]); var count = 0; do { selector_start_end_type = getSelectorRange(new_tokens[nrule][0], selector_start_end_type[1]); // console.log(selector_start_end_type, new_tokens[nrule][0].length, count); if (count > 50) { throw formatLibraryBugMessage('infinite loop'); } if (selector_start_end_type[2] != 'rule separator') { prettified_group_value.push( [ selector_start_end_type, prettifySelector( new_tokens[nrule][0], selector_start_end_type[0], selector_start_end_type[1], selector_start_end_type[2], user_conf ), ] ); } selector_start_end_type[1]++; count++; // console.log(selector_start_end_type, new_tokens[nrule][0].length, count); } while (selector_start_end_type[1] < new_tokens[nrule][0].length); // console.log('Prettified value: ' + JSON.stringify(prettified_group_value, null, ' ')); var not_sorted_prettified_group_value = prettified_group_value.slice(); if (!done_with_selector_reordering) { prettified_group_value.sort( function (a, b) { var selector_order = [ 'year', 'month', 'week', 'holiday', 'weekday', 'time', '24/7', 'state', 'comment']; return selector_order.indexOf(a[0][2]) - selector_order.indexOf(b[0][2]); } ); } var old_prettified_value_length = prettified_value.length; prettified_value += prettified_group_value.map( function (array) { return array[1]; } ).join(' '); prettified_value_array.push( prettified_group_value ); if (!done_with_selector_reordering_warnings) { for (var i = 0, l = not_sorted_prettified_group_value.length; i < l; i++) { if (not_sorted_prettified_group_value[i] != prettified_group_value[i]) { // console.log(i + ': ' + prettified_group_value[i][0][2]); var length = i + old_prettified_value_length; // i: Number of spaces in string. for (var x = 0; x <= i; x++) { length += prettified_group_value[x][1].length; // console.log('Length: ' + length + ' ' + prettified_group_value[x][1]); } // console.log(length); parsing_warnings.push([ prettified_value, length, 'The selector "' + prettified_group_value[i][0][2] + '" was switched with' + ' the selector "' + not_sorted_prettified_group_value[i][0][2] + '"' + ' for readablitity and compatibiltity reasons.' ]); } } } } done_with_selector_reordering_warnings = true; // console.log(JSON.stringify(prettified_value_array, null, ' ')); if (get_internals) { return [ prettified_value_array, new_tokens ]; } else { return prettified_value; } } // }}} /* Check selector array of tokens for specific token name pattern. {{{ * * :param tokens: List of token objects. * :param at: Position at which the matching should begin. * :param token_name(s): One or many token_name strings which have to match in that order. * :returns: true if token_name(s) match in order false otherwise. */ function matchTokens(tokens, at /*, matches... */) { if (at + arguments.length - 2 > tokens.length) return false; for (var i = 0; i < arguments.length - 2; i++) { if (tokens[at + i][1] !== arguments[i + 2]) return false; } return true; } // }}} /* Generate selector wrapper with time offset {{{ * * :param func: Generated selector code function. * :param shirt: Time to shift in milliseconds. * :param token_name(s): One or many token_name strings which have to match in that order. * :returns: See selector code. */ function generateDateShifter(func, shift) { return function(date) { var res = func(new Date(date.getTime() + shift)); if (typeof res[1] === 'undefined') return res; return [ res[0], new Date(res[1].getTime() - shift) ]; }; } // }}} /* Top-level parser {{{ * * :param tokens: List of token objects. * :param at: Position where to start. * :param selectors: Reference to selector object. * :param nrule: Rule number starting with 0. * :returns: See selector code. */ function parseGroup(tokens, at, selectors, nrule) { var rule_modifier_specified = false; // console.log(tokens); // useful for debugging of tokenize while (at < tokens.length) { // console.log('Parsing at position', at +':', tokens[at]); if (matchTokens(tokens, at, 'weekday')) { at = parseWeekdayRange(tokens, at, selectors); } else if (matchTokens(tokens, at, '24/7')) { selectors.time.push(function(date) { return [true]; }); // Not needed. If there is no selector it automatically matches everything. // WRONG: This only works if there is no other selector in this selector group ... at++; } else if (matchTokens(tokens, at, 'holiday')) { if (matchTokens(tokens, at+1, ',')) at = parseHoliday(tokens, at, selectors, true); else at = parseHoliday(tokens, at, selectors, false); week_stable = false; } else if (matchTokens(tokens, at, 'month', 'number') || matchTokens(tokens, at, 'month', 'weekday') || matchTokens(tokens, at, 'year', 'month', 'number') || matchTokens(tokens, at, 'year', 'event') || matchTokens(tokens, at, 'event')) { at = parseMonthdayRange(tokens, at, nrule); week_stable = false; } else if (matchTokens(tokens, at, 'year')) { at = parseYearRange(tokens, at); week_stable = false; } else if (matchTokens(tokens, at, 'month')) { at = parseMonthRange(tokens, at); // week_stable = false; // Decided based on the actual value/tokens. } else if (matchTokens(tokens, at, 'week')) { tokens[at][3] = 'week'; at = parseWeekRange(tokens, at); } else if (at !== 0 && at != tokens.length - 1 && tokens[at][0] == ':') { /* Ignore colon if they appear somewhere else than as time separator. * Except the start or end of the value. * This provides compatibility with the syntax proposed by Netzwolf: * http://wiki.openstreetmap.org/wiki/Key:opening_hours:specification#separator_for_readability * Check for valid use of is implemented in function getWarnings(). */ if (!done_with_warnings && matchTokens(tokens, at-1, 'holiday')) parsing_warnings.push([nrule, at, 'Please don’t use ":" after ' + tokens[at-1][1] + '.']); at++; } else if (matchTokens(tokens, at, 'number', 'timesep') || matchTokens(tokens, at, 'timevar') || matchTokens(tokens, at, '(', 'timevar') || matchTokens(tokens, at, 'number', '-')) { at = parseTimeRange(tokens, at, selectors, false); } else if (matchTokens(tokens, at, 'state')) { if (tokens[at][0] == 'open') { selectors.meaning = true; } else if (tokens[at][0] == 'closed' || tokens[at][0] == 'off') { selectors.meaning = false; } else { selectors.meaning = false; selectors.unknown = true; } rule_modifier_specified = true; at++; if (typeof tokens[at] == 'object' && tokens[at][0] == ',') // additional rule at = [ at + 1 ]; } else if (matchTokens(tokens, at, 'comment')) { selectors.comment = tokens[at][0]; if (!rule_modifier_specified) { // Then it is unknown. Either with unknown explicitly // specified or just a comment. selectors.meaning = false; selectors.unknown = true; } rule_modifier_specified = true; at++; if (typeof tokens[at] == 'object' && tokens[at][0] == ',') // additional rule at = [ at + 1 ]; } else if ((at === 0 || at == tokens.length - 1) && matchTokens(tokens, at, 'rule separator')) { at++; console.log("value: " + nrule); // throw formatLibraryBugMessage('Not implemented yet.'); } else { var warnings = getWarnings(); throw formatWarnErrorMessage(nrule, at, 'Unexpected token: "' + tokens[at][1] + '" This means that the syntax is not valid at that point or it is currently not supported.') + (warnings ? ' ' + warnings.join('; ') : ''); } if (typeof at == 'object') { // additional rule tokens[at[0] - 1][1] = 'rule separator'; break; } } return at; } function get_last_token_pos_in_token_group(tokens, at, last_at) { for (at++; at < last_at; at++) { if (typeof tokens[at] != 'undefined') { if (typeof tokens[at][3] == 'string' || tokens[at][1] == 'comment' || tokens[at][1] == 'state'){ return at - 1; } } } return last_at; } // }}} // helper functions for sub parser {{{ /* For given date, returns date moved to the start of the day with an offset specified in minutes. {{{ * For example, if date is 2014-05-19_18:17:12, dateAtDayMinutes would * return 2014-05-19_02:00:00 for minutes=120. * * :param date: Date object. * :param minutes: Minutes used as offset starting from midnight of current day. * :returns: Moved date object. */ function dateAtDayMinutes(date, minutes) { return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, minutes); } // }}} /* For given date, returns date moved to the specific day of week {{{ * * :param date: Date object. * :param weekday: Integer number for day of week. Starting with zero (Sunday). * :returns: Moved date object. */ function dateAtNextWeekday(date, weekday) { var delta = weekday - date.getDay(); return new Date(date.getFullYear(), date.getMonth(), date.getDate() + delta + (delta < 0 ? 7 : 0)); } // }}} /* Function to determine whether an array contains a value {{{ * Source: http://stackoverflow.com/a/1181586 * * :param needle: Element to find. * :returns: Index of element if present, if not present returns -1. */ function indexOf(needle) { if(typeof Array.prototype.indexOf === 'function') { indexOf = Array.prototype.indexOf; } else { indexOf = function(needle) { var i = -1, index = -1; for(i = 0; i < this.length; i++) { if(this[i] === needle) { index = i; break; } } return index; }; } return indexOf.call(this, needle); } // }}} /* Numeric list parser (1,2,3-4,-1) {{{ * Used in weekday parser above. * * :param tokens: List of token objects. * :param at: Position where to start. * :param func: Function func(from, to, at). * :returns: Position at which the token does not belong to the list any more. */ function parseNumRange(tokens, at, func) { for (; at < tokens.length; at++) { if (matchTokens(tokens, at, 'number', '-', 'number')) { // Number range func(tokens[at][0], tokens[at+2][0], at); at += 3; } else if (matchTokens(tokens, at, '-', 'number')) { // Negative number func(-tokens[at+1][0], -tokens[at+1][0], at); at += 2; } else if (matchTokens(tokens, at, 'number')) { // Single number func(tokens[at][0], tokens[at][0], at); at++; } else { throw formatWarnErrorMessage(nrule, at + matchTokens(tokens, at, '-'), 'Unexpected token in number range: ' + tokens[at][1]); } if (!matchTokens(tokens, at, ',')) break; } return at; } // }}} /* List parser for constrained weekdays in month range {{{ * e.g. Su[-1] which selects the last Sunday of the month. * * :param tokens: List of token objects. * :param at: Position where to start. * :returns: Array: * 0. Constrained weekday number. * 1. Position at which the token does not belong to the list any more (after ']' token). */ function getConstrainedWeekday(tokens, at) { var number = 0; var endat = parseNumRange(tokens, at, function(from, to, at) { // bad number if (from === 0 || from < -5 || from > 5) throw formatWarnErrorMessage(nrule, at, 'Number between -5 and 5 (except 0) expected'); if (from == to) { if (number !== 0) throw formatWarnErrorMessage(nrule, at, 'You can not use more than one constrained weekday in a month range'); number = from; } else { throw formatWarnErrorMessage(nrule, at+2, 'You can not use a range of constrained weekdays in a month range'); } }); if (!matchTokens(tokens, endat, ']')) throw formatWarnErrorMessage(nrule, endat, '"]" expected.'); return [ number, endat + 1 ]; } // }}} // Check if period is ok. Period 0 or 1 don’t make much sense. function checkPeriod(at, period, period_type, parm_string) { if (done_with_warnings) return; if (period === 0) { throw formatWarnErrorMessage(nrule, at, 'You can not use '+ period_type +' ranges with period equals zero.'); } else if (period === 1) { if (typeof parm_string == 'string' && parm_string == 'no_end_year') parsing_warnings.push([nrule, at, 'Please don’t use '+ period_type +' ranges with period equals one.' + ' If you want to express that a facility is open starting from a year without limit use "+".']); else parsing_warnings.push([nrule, at, 'Please don’t use '+ period_type +' ranges with period equals one.']); } } /* Get date moved to constrained weekday (and moved for add_days. {{{ * E.g. used for 'Aug Su[-1] -1 day'. * * :param year: Year as integer. * :param month: Month as integer starting with zero. * :param weekday: Integer number for day of week. Starting with zero (Sunday). * :param constrained_weekday: Position where to start. * :returns: Date object. */ function getDateForConstrainedWeekday(year, month, weekday, constrained_weekday, add_days) { var tmp_date = dateAtNextWeekday( new Date(year, month + (constrained_weekday[0] > 0 ? 0 : 1), 1), weekday); tmp_date.setDate(tmp_date.getDate() + (constrained_weekday[0] + (constrained_weekday[0] > 0 ? -1 : 0)) * 7); if (typeof add_days != 'undefined' && add_days[1]) tmp_date.setDate(tmp_date.getDate() + add_days[0]); return tmp_date; } // }}} /* Check if date is valid. {{{ * * :param month: Month as integer starting with zero. * :param date: Day of month as integer. * :returns: undefined. There is no real return value. This function just throws an exception if something is wrong. */ function checkIfDateIsValid(month, day, nrule, at) { // May use this instead. The problem is that this does not give feedback as precise as the code which is used in this function. // var testDate = new Date(year, month, day); // if (testDate.getDate() != day || testDate.getMonth() != month || testDate.getFullYear() != year) { // console.error('date not valid'); // } // https://en.wikipedia.org/wiki/Month#Julian_and_Gregorian_calendars if (day < 1 || day > 31) throw formatWarnErrorMessage(nrule, at, 'Day must be between 1 and 31.'); if ((month==3 || month==5 || month==8 || month==10) && day==31) throw formatWarnErrorMessage(nrule, at, 'Month ' + months[month] + " doesn't have 31 days.!"); if (month == 1 && day == 30) throw formatWarnErrorMessage(nrule, at, 'Month ' + months[1]+ " either has 28 or 29 days (leap years)."); } // }}} // }}} /* Time range parser (10:00-12:00,14:00-16:00) {{{ * * :param tokens: List of token objects. * :param at: Position where to start. * :param selectors: Reference to selector object. * :param extended_open_end: Used for combined time range with open end. * extended_open_end: