Changeset 7514 in josm for trunk


Ignore:
Timestamp:
2014-09-08T23:45:36+02:00 (10 years ago)
Author:
Don-vip
Message:

fix #10494 - Update to version 3.0.0 of opening_hours.js

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/data/validator/opening_hours.js

    r7174 r7514  
     1/*
     2 * For information see https://github.com/ypid/opening_hours.js
     3 * and the doc directory which contains internal documentation and design.
     4 */
     5/* jshint laxbreak: true */
     6/* jshint boss: true */
     7/* jshint loopfunc: true */
     8
    19(function (root, factory) {
    210        // constants (holidays, error correction) {{{
    311        // holidays {{{
    412        var holidays = {
    5                 'fr': {
     13                'fr': { // {{{
    614                        'PH': { // http://fr.wikipedia.org/wiki/F%C3%AAtes_et_jours_f%C3%A9ri%C3%A9s_en_France
    7                                 "Jour de l'an"              : [  1,  1 ],
    8                                 "Vendredi saint"            : [  'easter', -2, [ 'Moselle', 'Bas-Rhin', 'Haut-Rhin', 'Guadeloupe', 'Martinique', 'Polynésie française' ] ],
    9                                 "Lundi de Pâques"           : [  'easter', 1 ],
    10                                 "Saint-Pierre-Chanel"       : [  4, 28, [ 'Wallis-et-Futuna' ] ],
    11                                 "Fête du Travail"           : [  5,  1 ],
    12                                 "Fête de la Victoire"       : [  5,  8 ],
    13                                 "Abolition de l'esclavage"  : [  5, 22, [ 'Martinique' ] ],
    14                                 "Abolition de l'esclavage"  : [  5, 27, [ 'Guadeloupe' ] ],
    15                                 "Jeudi de l'Ascension"      : [  'easter', 39 ],
    16                                 "Lundi de Pentecôte"        : [  'easter', 50 ],
    17                                 "Abolition de l'esclavage"  : [  6, 10, [ 'Guyane' ] ],
    18                                 "Fête de l'autonomie"       : [  6, 29, [ 'Polynésie française' ] ],
    19                                 "Fête nationale"            : [  7, 14 ],
    20                                 "Fête Victor Schoelcher"    : [  7, 21, [ 'Guadeloupe', 'Martinique' ] ],
    21                                 "Fête du Territoire"        : [  7, 29, [ 'Wallis-et-Futuna' ] ],
    22                                 "Assomption"                : [  8, 15 ],
    23                                 "Fête de la citoyenneté"    : [  9, 24, [ 'Nouvelle-Calédonie' ] ],
    24                                 "Toussaint"                 : [ 11,  1 ],
    25                                 "Armistice"                 : [ 11, 11 ],
    26                                 "Abolition de l'esclavage"  : [ 12, 20, [ 'Réunion' ] ],
    27                                 "Noël"                      : [ 12, 25 ],
    28                                 "Saint-Étienne "            : [ 12, 26, [ 'Moselle', 'Bas-Rhin', 'Haut-Rhin' ] ]
     15                                "Jour de l'an"             : [  1,  1 ],
     16                                "Vendredi saint"           : [  'easter', -2, [ 'Moselle', 'Bas-Rhin', 'Haut-Rhin', 'Guadeloupe', 'Martinique', 'Polynésie française' ] ],
     17                                "Lundi de Pâques"          : [  'easter', 1 ],
     18                                "Saint-Pierre-Chanel"      : [  4, 28, [ 'Wallis-et-Futuna' ] ],
     19                                "Fête du Travail"          : [  5,  1 ],
     20                                "Fête de la Victoire"      : [  5,  8 ],
     21                                "Abolition de l'esclavage" : [  5, 22, [ 'Martinique' ] ],
     22                                "Abolition de l'esclavage" : [  5, 27, [ 'Guadeloupe' ] ],
     23                                "Jeudi de l'Ascension"     : [  'easter', 39 ],
     24                                "Lundi de Pentecôte"       : [  'easter', 50 ],
     25                                "Abolition de l'esclavage" : [  6, 10, [ 'Guyane' ] ],
     26                                "Fête de l'autonomie"      : [  6, 29, [ 'Polynésie française' ] ],
     27                                "Fête nationale"           : [  7, 14 ],
     28                                "Fête Victor Schoelcher"   : [  7, 21, [ 'Guadeloupe', 'Martinique' ] ],
     29                                "Fête du Territoire"       : [  7, 29, [ 'Wallis-et-Futuna' ] ],
     30                                "Assomption"               : [  8, 15 ],
     31                                "Fête de la citoyenneté"   : [  9, 24, [ 'Nouvelle-Calédonie' ] ],
     32                                "Toussaint"                : [ 11,  1 ],
     33                                "Armistice"                : [ 11, 11 ],
     34                                "Abolition de l'esclavage" : [ 12, 20, [ 'Réunion' ] ],
     35                                "Noël"                     : [ 12, 25 ],
     36                                "Saint-Étienne "           : [ 12, 26, [ 'Moselle', 'Bas-Rhin', 'Haut-Rhin' ] ]
    2937                        }
    30                 },
    31                 'de': {
     38                }, // }}}
     39                'de': { // {{{
    3240                        'PH': { // http://de.wikipedia.org/wiki/Feiertage_in_Deutschland
    3341                                'Neujahrstag'               : [  1,  1 ], // month 1, day 1, whole Germany
     
    4755                                '1. Weihnachtstag'          : [ 12, 25 ],
    4856                                '2. Weihnachtstag'          : [ 12, 26 ],
    49                                 // 'Silvester'                 : [ 12, 31 ], // for testing
     57                                // 'Silvester'              : [ 12, 31 ], // for testing
    5058                        },
    5159                        'Baden-Württemberg': { // does only apply in Baden-Württemberg
     
    5361                                // You may use this instead of the country wide with some
    5462                                // additional holidays for some states, if one state
    55                                 // totally disagrees about how to do public holidays …
     63                                // totally disagrees about how to do holidays …
    5664                                // 'PH': {
    57                                 //      '2. Weihnachtstag'          : [ 12, 26 ],
     65                                //     '2. Weihnachtstag'          : [ 12, 26 ],
    5866                                // },
    5967
     
    10761084                                ],
    10771085                        },
    1078                 },
    1079                 'at': {
     1086                }, // }}}
     1087                'at': { // {{{
    10801088                        'PH': { // http://de.wikipedia.org/wiki/Feiertage_in_%C3%96sterreich
    1081                                 'Neujahrstag'               : [  1,  1 ],
    1082                                 'Heilige Drei Könige'       : [  1,  6 ],
    1083                                 // 'Josef'                     : [  3, 19, [ 'Kärnten', 'Steiermark', 'Tirol', 'Vorarlberg' ] ],
    1084                                 // 'Karfreitag'                : [ 'easter', -2 ],
    1085                                 'Ostermontag'               : [ 'easter',  1 ],
    1086                                 'Staatsfeiertag'            : [  5,  1 ],
    1087                                 // 'Florian'                   : [  5,  4, [ 'Oberösterreich' ] ],
    1088                                 'Christi Himmelfahrt'       : [ 'easter', 39 ],
    1089                                 'Pfingstmontag'             : [ 'easter', 50 ],
    1090                                 'Fronleichnam'              : [ 'easter', 60 ],
    1091                                 'Mariä Himmelfahrt'         : [  8, 15 ],
    1092                                 // 'Rupert'                    : [  9, 24, [ 'Salzburg' ] ],
    1093                                 // 'Tag der Volksabstimmung'   : [ 10, 10, [ 'Kärnten' ] ],
    1094                                 'Nationalfeiertag'          : [ 10, 26 ],
    1095                                 'Allerheiligen'             : [ 11,  1 ],
    1096                                 // 'Martin'                    : [ 11, 11, [ 'Burgenland' ] ],
    1097                                 // 'Leopold'                   : [ 11, 15, [ 'Niederösterreich', 'Wien' ] ],
    1098                                 'Mariä Empfängnis'          : [ 12,  8 ],
    1099                                 // 'Heiliger Abend'            : [ 12, 24 ],
    1100                                 'Christtag'                 : [ 12, 25 ],
    1101                                 'Stefanitag'                : [ 12, 26 ],
    1102                                 // 'Silvester'                 : [ 12, 31 ],
     1089                                'Neujahrstag'                : [  1,  1 ],
     1090                                'Heilige Drei Könige'        : [  1,  6 ],
     1091                                // 'Josef'                   : [  3, 19, [ 'Kärnten', 'Steiermark', 'Tirol', 'Vorarlberg' ] ],
     1092                                // 'Karfreitag'              : [ 'easter', -2 ],
     1093                                'Ostermontag'                : [ 'easter',  1 ],
     1094                                'Staatsfeiertag'             : [  5,  1 ],
     1095                                // 'Florian'                 : [  5,  4, [ 'Oberösterreich' ] ],
     1096                                'Christi Himmelfahrt'        : [ 'easter', 39 ],
     1097                                'Pfingstmontag'              : [ 'easter', 50 ],
     1098                                'Fronleichnam'               : [ 'easter', 60 ],
     1099                                'Mariä Himmelfahrt'          : [  8, 15 ],
     1100                                // 'Rupert'                  : [  9, 24, [ 'Salzburg' ] ],
     1101                                // 'Tag der Volksabstimmung' : [ 10, 10, [ 'Kärnten' ] ],
     1102                                'Nationalfeiertag'           : [ 10, 26 ],
     1103                                'Allerheiligen'              : [ 11,  1 ],
     1104                                // 'Martin'                  : [ 11, 11, [ 'Burgenland' ] ],
     1105                                // 'Leopold'                 : [ 11, 15, [ 'Niederösterreich', 'Wien' ] ],
     1106                                'Mariä Empfängnis'           : [ 12,  8 ],
     1107                                // 'Heiliger Abend'          : [ 12, 24 ],
     1108                                'Christtag'                  : [ 12, 25 ],
     1109                                'Stefanitag'                 : [ 12, 26 ],
     1110                                // 'Silvester'               : [ 12, 31 ],
    11031111                        },
    1104                 },
    1105                 'ca': {
     1112                }, // }}}
     1113                'ca': { // {{{
    11061114                        'PH': { // https://en.wikipedia.org/wiki/Public_holidays_in_Canada
    1107                                 "New Year's Day"            : [  1,  1 ],
    1108                                 "Good Friday"               : [  'easter', -2 ],
    1109                                 "Canada Day"                : [  'canadaDay', 0 ],
    1110                                 "Labour Day"                : [  'firstSeptemberMonday', 0 ],
    1111                                 "Christmas Day"             : [ 12, 25 ]
     1115                                "New Year's Day" : [  1,  1 ],
     1116                                "Good Friday"    : [  'easter', -2 ],
     1117                                "Canada Day"     : [  'canadaDay', 0 ],
     1118                                "Labour Day"     : [  'firstSeptemberMonday', 0 ],
     1119                                "Christmas Day"  : [ 12, 25 ]
    11121120                        },
    11131121                        'Alberta': {
    11141122                                'PH': {
    1115                                         "New Year's Day"            : [  1,  1 ],
    1116                                         "Alberta Family Day"        : [  'firstFebruaryMonday', 14 ],
    1117                                         "Good Friday"               : [  'easter', -2 ],
    1118                                         "Easter Monday"             : [  'easter', 1 ],
    1119                                         "Victoria Day"              : [  'victoriaDay', 0 ],
    1120                                         "Canada Day"                : [  'canadaDay', 0 ],
    1121                                         "Heritage Day"              : [  'firstAugustMonday', 0 ],
    1122                                         "Labour Day"                : [  'firstSeptemberMonday', 0 ],
    1123                                         "Thanksgiving"              : [  'firstOctoberMonday', 7 ],
    1124                                         "Remembrance Day"           : [ 11, 11 ],
    1125                                         "Christmas Day"             : [ 12, 25 ],
    1126                                         "Boxing Day"                : [ 12, 26 ]
     1123                                        "New Year's Day"     : [  1,  1 ],
     1124                                        "Alberta Family Day" : [  'firstFebruaryMonday', 14 ],
     1125                                        "Good Friday"        : [  'easter', -2 ],
     1126                                        "Easter Monday"      : [  'easter', 1 ],
     1127                                        "Victoria Day"       : [  'victoriaDay', 0 ],
     1128                                        "Canada Day"         : [  'canadaDay', 0 ],
     1129                                        "Heritage Day"       : [  'firstAugustMonday', 0 ],
     1130                                        "Labour Day"         : [  'firstSeptemberMonday', 0 ],
     1131                                        "Thanksgiving"       : [  'firstOctoberMonday', 7 ],
     1132                                        "Remembrance Day"    : [ 11, 11 ],
     1133                                        "Christmas Day"      : [ 12, 25 ],
     1134                                        "Boxing Day"         : [ 12, 26 ]
    11271135                                },
    11281136                        },
    11291137                        'British Columbia': {
    11301138                                'PH': {
    1131                                         "New Year's Day"            : [  1,  1 ],
    1132                                         "Family Day"                : [  'firstFebruaryMonday', 7 ],
    1133                                         "Good Friday"               : [  'easter', -2 ],
    1134                                         "Victoria Day"              : [  'victoriaDay', 0 ],
    1135                                         "Canada Day"                : [  'canadaDay', 0 ],
    1136                                         "British Columbia Day"      : [  'firstAugustMonday', 0 ],
    1137                                         "Labour Day"                : [  'firstSeptemberMonday', 0 ],
    1138                                         "Thanksgiving"              : [  'firstOctoberMonday', 7 ],
    1139                                         "Remembrance Day"           : [ 11, 11 ],
    1140                                         "Christmas Day"             : [ 12, 25 ]
     1139                                        "New Year's Day"       : [  1,  1 ],
     1140                                        "Family Day"           : [  'firstFebruaryMonday', 7 ],
     1141                                        "Good Friday"          : [  'easter', -2 ],
     1142                                        "Victoria Day"         : [  'victoriaDay', 0 ],
     1143                                        "Canada Day"           : [  'canadaDay', 0 ],
     1144                                        "British Columbia Day" : [  'firstAugustMonday', 0 ],
     1145                                        "Labour Day"           : [  'firstSeptemberMonday', 0 ],
     1146                                        "Thanksgiving"         : [  'firstOctoberMonday', 7 ],
     1147                                        "Remembrance Day"      : [ 11, 11 ],
     1148                                        "Christmas Day"        : [ 12, 25 ]
    11411149                                },
    11421150                        },
    11431151                        'Manitoba': {
    11441152                                'PH': {
    1145                                         "New Year's Day"            : [  1,  1 ],
    1146                                         "Louis Riel Day"            : [  'firstFebruaryMonday', 14 ],
    1147                                         "Good Friday"               : [  'easter', -2 ],
    1148                                         "Victoria Day"              : [  'victoriaDay', 0 ],
    1149                                         "Canada Day"                : [  'canadaDay', 0 ],
    1150                                         "Civic Holiday"             : [  'firstAugustMonday', 0 ],
    1151                                         "Labour Day"                : [  'firstSeptemberMonday', 0 ],
    1152                                         "Thanksgiving"              : [  'firstOctoberMonday', 7 ],
    1153                                         "Remembrance Day"           : [ 11, 11 ],
    1154                                         "Christmas Day"             : [ 12, 25 ]
     1153                                        "New Year's Day"  : [  1,  1 ],
     1154                                        "Louis Riel Day"  : [  'firstFebruaryMonday', 14 ],
     1155                                        "Good Friday"     : [  'easter', -2 ],
     1156                                        "Victoria Day"    : [  'victoriaDay', 0 ],
     1157                                        "Canada Day"      : [  'canadaDay', 0 ],
     1158                                        "Civic Holiday"   : [  'firstAugustMonday', 0 ],
     1159                                        "Labour Day"      : [  'firstSeptemberMonday', 0 ],
     1160                                        "Thanksgiving"    : [  'firstOctoberMonday', 7 ],
     1161                                        "Remembrance Day" : [ 11, 11 ],
     1162                                        "Christmas Day"   : [ 12, 25 ]
    11551163                                },
    11561164                        },
    11571165                        'New Brunswick': {
    11581166                                'PH': {
    1159                                         "New Year's Day"            : [  1,  1 ],
    1160                                         "Good Friday"               : [  'easter', -2 ],
    1161                                         "Victoria Day"              : [  'victoriaDay', 0 ],
    1162                                         "Canada Day"                : [  'canadaDay', 0 ],
    1163                                         "New Brunswick Day"         : [  'firstAugustMonday', 0 ],
    1164                                         "Labour Day"                : [  'firstSeptemberMonday', 0 ],
    1165                                         "Thanksgiving"              : [  'firstOctoberMonday', 7 ],
    1166                                         "Remembrance Day"           : [ 11, 11 ],
    1167                                         "Christmas Day"             : [ 12, 25 ],
    1168                                         "Boxing Day"                : [ 12, 26 ]
     1167                                        "New Year's Day"    : [  1,  1 ],
     1168                                        "Good Friday"       : [  'easter', -2 ],
     1169                                        "Victoria Day"      : [  'victoriaDay', 0 ],
     1170                                        "Canada Day"        : [  'canadaDay', 0 ],
     1171                                        "New Brunswick Day" : [  'firstAugustMonday', 0 ],
     1172                                        "Labour Day"        : [  'firstSeptemberMonday', 0 ],
     1173                                        "Thanksgiving"      : [  'firstOctoberMonday', 7 ],
     1174                                        "Remembrance Day"   : [ 11, 11 ],
     1175                                        "Christmas Day"     : [ 12, 25 ],
     1176                                        "Boxing Day"        : [ 12, 26 ]
    11691177                                },
    11701178                        },
    11711179                        'Newfoundland and Labrador': {
    11721180                                'PH': {
    1173                                         "New Year's Day"            : [  1,  1 ],
    1174                                         "Saint Patrick's Day"       : [  3, 17 ],
    1175                                         "Good Friday"               : [  'easter', -2 ],
    1176                                         "Saint George's Day"        : [  4, 23 ],
    1177                                         "Discovery Day"             : [  6, 24 ],
    1178                                         "Memorial Day"              : [  7, 1 ],
    1179                                         "Orangemen's Day"           : [  7, 12 ],
    1180                                         "Labour Day"                : [  'firstSeptemberMonday', 0 ],
    1181                                         "Armistice Day"             : [ 11, 11 ],
    1182                                         "Christmas Day"             : [ 12, 25 ]
     1181                                        "New Year's Day"      : [  1,  1 ],
     1182                                        "Saint Patrick's Day" : [  3, 17 ],
     1183                                        "Good Friday"         : [  'easter', -2 ],
     1184                                        "Saint George's Day"  : [  4, 23 ],
     1185                                        "Discovery Day"       : [  6, 24 ],
     1186                                        "Memorial Day"        : [  7, 1 ],
     1187                                        "Orangemen's Day"     : [  7, 12 ],
     1188                                        "Labour Day"          : [  'firstSeptemberMonday', 0 ],
     1189                                        "Armistice Day"       : [ 11, 11 ],
     1190                                        "Christmas Day"       : [ 12, 25 ]
    11831191                                },
    11841192                        },
    11851193                        'Northwest Territories': {
    11861194                                'PH': {
    1187                                         "New Year's Day"            : [  1,  1 ],
    1188                                         "Good Friday"               : [  'easter', -2 ],
    1189                                         "Victoria Day"              : [  'victoriaDay', 0 ],
    1190                                         "National Aboriginal Day"   : [  6, 21 ],
    1191                                         "Canada Day"                : [  'canadaDay', 0 ],
    1192                                         "Civic Holiday"             : [  'firstAugustMonday', 0 ],
    1193                                         "Labour Day"                : [  'firstSeptemberMonday', 0 ],
    1194                                         "Thanksgiving"              : [  'firstOctoberMonday', 7 ],
    1195                                         "Remembrance Day"           : [ 11, 11 ],
    1196                                         "Christmas Day"             : [ 12, 25 ]
     1195                                        "New Year's Day"          : [  1,  1 ],
     1196                                        "Good Friday"             : [  'easter', -2 ],
     1197                                        "Victoria Day"            : [  'victoriaDay', 0 ],
     1198                                        "National Aboriginal Day" : [  6, 21 ],
     1199                                        "Canada Day"              : [  'canadaDay', 0 ],
     1200                                        "Civic Holiday"           : [  'firstAugustMonday', 0 ],
     1201                                        "Labour Day"              : [  'firstSeptemberMonday', 0 ],
     1202                                        "Thanksgiving"            : [  'firstOctoberMonday', 7 ],
     1203                                        "Remembrance Day"         : [ 11, 11 ],
     1204                                        "Christmas Day"           : [ 12, 25 ]
    11971205                                },
    11981206                        },
    11991207                        'Nova Scotia': {
    12001208                                'PH': {
    1201                                         "New Year's Day"            : [  1,  1 ],
    1202                                         "Good Friday"               : [  'easter', -2 ],
    1203                                         "Victoria Day"              : [  'victoriaDay', 0 ],
    1204                                         "Canada Day"                : [  'canadaDay', 0 ],
    1205                                         "Natal Day"                 : [  'firstAugustMonday', 0 ],
    1206                                         "Labour Day"                : [  'firstSeptemberMonday', 0 ],
    1207                                         "Thanksgiving"              : [  'firstOctoberMonday', 7 ],
    1208                                         "Remembrance Day"           : [ 11, 11 ],
    1209                                         "Christmas Day"             : [ 12, 25 ],
    1210                                         "Boxing Day"                : [ 12, 26 ]
     1209                                        "New Year's Day"  : [  1,  1 ],
     1210                                        "Good Friday"     : [  'easter', -2 ],
     1211                                        "Victoria Day"    : [  'victoriaDay', 0 ],
     1212                                        "Canada Day"      : [  'canadaDay', 0 ],
     1213                                        "Natal Day"       : [  'firstAugustMonday', 0 ],
     1214                                        "Labour Day"      : [  'firstSeptemberMonday', 0 ],
     1215                                        "Thanksgiving"    : [  'firstOctoberMonday', 7 ],
     1216                                        "Remembrance Day" : [ 11, 11 ],
     1217                                        "Christmas Day"   : [ 12, 25 ],
     1218                                        "Boxing Day"      : [ 12, 26 ]
    12111219                                },
    12121220                        },
    12131221                        'Nunavut': {
    12141222                                'PH': {
    1215                                         "New Year's Day"            : [  1,  1 ],
    1216                                         "Good Friday"               : [  'easter', -2 ],
    1217                                         "Victoria Day"              : [  'victoriaDay', 0 ],
    1218                                         "Canada Day"                : [  'canadaDay', 0 ],
    1219                                         "Nunavut Day"               : [  7, 9 ],
    1220                                         "Civic Holiday"             : [  'firstAugustMonday', 0 ],
    1221                                         "Labour Day"                : [  'firstSeptemberMonday', 0 ],
    1222                                         "Thanksgiving"              : [  'firstOctoberMonday', 7 ],
    1223                                         "Remembrance Day"           : [ 11, 11 ],
    1224                                         "Christmas Day"             : [ 12, 25 ]
     1223                                        "New Year's Day"  : [  1,  1 ],
     1224                                        "Good Friday"     : [  'easter', -2 ],
     1225                                        "Victoria Day"    : [  'victoriaDay', 0 ],
     1226                                        "Canada Day"      : [  'canadaDay', 0 ],
     1227                                        "Nunavut Day"     : [  7, 9 ],
     1228                                        "Civic Holiday"   : [  'firstAugustMonday', 0 ],
     1229                                        "Labour Day"      : [  'firstSeptemberMonday', 0 ],
     1230                                        "Thanksgiving"    : [  'firstOctoberMonday', 7 ],
     1231                                        "Remembrance Day" : [ 11, 11 ],
     1232                                        "Christmas Day"   : [ 12, 25 ]
    12251233                                },
    12261234                        },
     
    12421250                        'Prince Edward Island': {
    12431251                                'PH': {
    1244                                         "New Year's Day"            : [  1,  1 ],
    1245                                         "Islander Day"              : [  'firstFebruaryMonday', 14 ],
    1246                                         "Good Friday"               : [  'easter', -2 ],
    1247                                         "Easter Monday"             : [  'easter', 1 ],
    1248                                         "Victoria Day"              : [  'victoriaDay', 0 ],
    1249                                         "Canada Day"                : [  'canadaDay', 0 ],
    1250                                         "Civic Holiday"             : [  'firstAugustMonday', 0 ],
    1251                                         "Gold Cup Parade Day"       : [  'firstAugustMonday', 18 ],
    1252                                         "Labour Day"                : [  'firstSeptemberMonday', 0 ],
    1253                                         "Thanksgiving"              : [  'firstOctoberMonday', 7 ],
    1254                                         "Remembrance Day"           : [ 11, 11 ],
    1255                                         "Christmas Day"             : [ 12, 25 ],
    1256                                         "Boxing Day"                : [ 12, 26 ]
     1252                                        "New Year's Day"      : [  1,  1 ],
     1253                                        "Islander Day"        : [  'firstFebruaryMonday', 14 ],
     1254                                        "Good Friday"         : [  'easter', -2 ],
     1255                                        "Easter Monday"       : [  'easter', 1 ],
     1256                                        "Victoria Day"        : [  'victoriaDay', 0 ],
     1257                                        "Canada Day"          : [  'canadaDay', 0 ],
     1258                                        "Civic Holiday"       : [  'firstAugustMonday', 0 ],
     1259                                        "Gold Cup Parade Day" : [  'firstAugustMonday', 18 ],
     1260                                        "Labour Day"          : [  'firstSeptemberMonday', 0 ],
     1261                                        "Thanksgiving"        : [  'firstOctoberMonday', 7 ],
     1262                                        "Remembrance Day"     : [ 11, 11 ],
     1263                                        "Christmas Day"       : [ 12, 25 ],
     1264                                        "Boxing Day"          : [ 12, 26 ]
    12571265                                },
    12581266                        },
     
    12721280                        'Saskatchewan': {
    12731281                                'PH': {
    1274                                         "New Year's Day"            : [  1,  1 ],
    1275                                         "Family Day"                : [  'firstFebruaryMonday', 14 ],
    1276                                         "Good Friday"               : [  'easter', -2 ],
    1277                                         "Victoria Day"              : [  'victoriaDay', 0 ],
    1278                                         "Canada Day"                : [  'canadaDay', 0 ],
    1279                                         "Saskatchewan Day"          : [  'firstAugustMonday', 0 ],
    1280                                         "Labour Day"                : [  'firstSeptemberMonday', 0 ],
    1281                                         "Thanksgiving"              : [  'firstOctoberMonday', 7 ],
    1282                                         "Remembrance Day"           : [ 11, 11 ],
    1283                                         "Christmas Day"             : [ 12, 25 ]
     1282                                        "New Year's Day"   : [  1,  1 ],
     1283                                        "Family Day"       : [  'firstFebruaryMonday', 14 ],
     1284                                        "Good Friday"      : [  'easter', -2 ],
     1285                                        "Victoria Day"     : [  'victoriaDay', 0 ],
     1286                                        "Canada Day"       : [  'canadaDay', 0 ],
     1287                                        "Saskatchewan Day" : [  'firstAugustMonday', 0 ],
     1288                                        "Labour Day"       : [  'firstSeptemberMonday', 0 ],
     1289                                        "Thanksgiving"     : [  'firstOctoberMonday', 7 ],
     1290                                        "Remembrance Day"  : [ 11, 11 ],
     1291                                        "Christmas Day"    : [ 12, 25 ]
    12841292                                },
    12851293                        },
    12861294                        'Yukon': {
    12871295                                'PH': {
    1288                                         "New Year's Day"            : [  1,  1 ],
    1289                                         "Heritage Day"              : [  'lastFebruarySunday',  -2 ],
    1290                                         "Good Friday"               : [  'easter', -2 ],
    1291                                         "Easter Monday"             : [  'easter', 1 ],
    1292                                         "Victoria Day"              : [  'victoriaDay', 0 ],
    1293                                         "Canada Day"                : [  'canadaDay', 0 ],
    1294                                         "Discovery Day"             : [  'firstAugustMonday', 14 ],
    1295                                         "Labour Day"                : [  'firstSeptemberMonday', 0 ],
    1296                                         "Thanksgiving"              : [  'firstOctoberMonday', 7 ],
    1297                                         "Remembrance Day"           : [ 11, 11 ],
    1298                                         "Christmas Day"             : [ 12, 25 ],
    1299                                         "Boxing Day"                : [ 12, 26 ]
     1296                                        "New Year's Day"  : [  1,  1 ],
     1297                                        "Heritage Day"    : [  'lastFebruarySunday',  -2 ],
     1298                                        "Good Friday"     : [  'easter', -2 ],
     1299                                        "Easter Monday"   : [  'easter', 1 ],
     1300                                        "Victoria Day"    : [  'victoriaDay', 0 ],
     1301                                        "Canada Day"      : [  'canadaDay', 0 ],
     1302                                        "Discovery Day"   : [  'firstAugustMonday', 14 ],
     1303                                        "Labour Day"      : [  'firstSeptemberMonday', 0 ],
     1304                                        "Thanksgiving"    : [  'firstOctoberMonday', 7 ],
     1305                                        "Remembrance Day" : [ 11, 11 ],
     1306                                        "Christmas Day"   : [ 12, 25 ],
     1307                                        "Boxing Day"      : [ 12, 26 ]
    13001308                                },
    13011309                        },
    1302                 },
    1303                 'ua': {
     1310                }, // }}}
     1311                'ua': { // {{{
    13041312                        'PH': { // http://uk.wikipedia.org/wiki/%D0%A1%D0%B2%D1%8F%D1%82%D0%B0_%D1%82%D0%B0_%D0%BF%D0%B0%D0%BC%27%D1%8F%D1%82%D0%BD%D1%96_%D0%B4%D0%BD%D1%96_%D0%B2_%D0%A3%D0%BA%D1%80%D0%B0%D1%97%D0%BD%D1%96
    1305                                 "Новий рік"                  : [  1,  1 ],
    1306                                 "Різдво"                     : [  1,  7 ],
    1307                                 "Міжнародний жіночий день"   : [  3,  8 ],
    1308                                 "Великдень"                  : [ 'orthodox easter',  1 ],
    1309                                 "День Праці 1"               : [  5,  1 ],
    1310                                 "День Праці 2"               : [  5,  2 ],
    1311                                 "День Перемоги"              : [  5,  9 ],
    1312                                 "День Конституції України"   : [  6, 28 ],
    1313                                 "День Незалежності України"  : [  8, 24 ],
     1313                                "Новий рік"                 : [  1,  1 ],
     1314                                "Різдво"                    : [  1,  7 ],
     1315                                "Міжнародний жіночий день"  : [  3,  8 ],
     1316                                "Великдень"                 : [ 'orthodox easter',  1 ],
     1317                                "День Праці 1"              : [  5,  1 ],
     1318                                "День Праці 2"              : [  5,  2 ],
     1319                                "День Перемоги"             : [  5,  9 ],
     1320                                "День Конституції України"  : [  6, 28 ],
     1321                                "День Незалежності України" : [  8, 24 ],
    13141322                        }
    1315                 },
    1316                 'si': {
     1323                }, // }}}
     1324                'si': { // {{{
    13171325                        'PH': { // http://www.vlada.si/o_sloveniji/politicni_sistem/prazniki/
    1318                                 'novo leto'                                  : [  1,  1 ],
    1319                                 'Prešernov dan, slovenski kulturni praznik'  : [  2,  8 ],
    1320                                 'velikonočna nedelja'                        : [ 'easter',  0 ],
    1321                                 'velikonočni ponedeljek'                     : [ 'easter',  1 ],
    1322                                 'dan upora proti okupatorju'                 : [  4,  27 ],
    1323                                 'praznik dela 1'                               : [  5, 1 ],
    1324                                 'praznik dela 2'                               : [  5, 2 ],
    1325                                 'binkoštna nedelja - binkošti'               : [ 'easter',  49 ],
    1326                                 'dan državnosti'                             : [  6, 25 ],
    1327                                 'Marijino vnebovzetje'                       : [  8, 15 ],
    1328                                 'dan reformacije'                            : [ 10, 31 ],
    1329                                 'dan spomina na mrtve'                       : [ 11,  1 ],
    1330                                 'božič'                                      : [ 12, 25 ],
    1331                                 'dan samostojnosti in enotnosti'             : [ 12, 26 ],
     1326                                'novo leto'                                 : [  1,  1 ],
     1327                                'Prešernov dan, slovenski kulturni praznik' : [  2,  8 ],
     1328                                'velikonočna nedelja'                       : [ 'easter',  0 ],
     1329                                'velikonočni ponedeljek'                    : [ 'easter',  1 ],
     1330                                'dan upora proti okupatorju'                : [  4,  27 ],
     1331                                'praznik dela 1'                            : [  5, 1 ],
     1332                                'praznik dela 2'                            : [  5, 2 ],
     1333                                'binkoštna nedelja - binkošti'              : [ 'easter',  49 ],
     1334                                'dan državnosti'                            : [  6, 25 ],
     1335                                'Marijino vnebovzetje'                      : [  8, 15 ],
     1336                                'dan reformacije'                           : [ 10, 31 ],
     1337                                'dan spomina na mrtve'                      : [ 11,  1 ],
     1338                                'božič'                                     : [ 12, 25 ],
     1339                                'dan samostojnosti in enotnosti'            : [ 12, 26 ],
    13321340                        },
    1333                 },
     1341                }, // }}}
    13341342        };
    13351343        // }}}
     
    13411349        // Key to word_error_correction is the token name except wrong_words
    13421350        var word_error_correction = {
    1343                 wrong_words: {
    1344                         'Assuming "<ok>" for "<ko>"': {
    1345                                 spring:  'Mar-May',
    1346                                 summer:  'Jun-Aug',
    1347                                 autumn:  'Sep-Nov',
    1348                                 winter:  'Dec-Feb',
     1351                wrong_words: { /* {{{ */
     1352                        'Assuming "<ok>" for "<ko>".': {
     1353                                'Frühling':  'Mar-May',
     1354                                'Frühjahr':  'Mar-May',
     1355                                'Sommer':    'Jun-Aug',
     1356                                'Herbst':    'Sep-Nov',
     1357                                'winter':    'Dec-Feb',
     1358                        }, '"<ko>" wird als "<ok>" interpertiert.': {
     1359                                'spring':  'Mar-May',
     1360                                'summer':  'Jun-Aug',
     1361                                'autumn':  'Sep-Nov',
     1362                                // 'winter':  'Dec-Feb', // Same as in English.
    13491363                                // morning: '08:00-12:00',
    13501364                                // evening: '13:00-18:00',
     
    13521366                                'daytime': 'sunrise-sunset',
    13531367                        }, 'Bitte benutze die englische Schreibweise "<ok>" für "<ko>".': {
    1354                                 sommer: 'summer',
     1368                                'sommer':  'summer',
    13551369                                'werktag':  'Mo-Fr',
    13561370                                'werktags': 'Mo-Fr',
    1357                         }, 'Bitte benutze "<ok>" für "<ko>". Beispiel: "Mo-Fr 08:00-12:00; Tu off"': {
    1358                                 ruhetag:     'off',
    1359                                 ruhetage:    'off',
    1360                                 geschlossen: 'off',
    1361                                 ausser:      'off',
    1362                                 außer:       'off',
     1371                        }, 'Bitte benutze "<ok>" für "<ko>". Beispiel: "Mo-Fr 08:00-12:00; Tu off".': {
     1372                                'ruhetag':     'off',
     1373                                'ruhetage':    'off',
     1374                                'geschlossen': 'off',
     1375                                'geschl':      'off',
     1376                                // 'ausser':      'off',
     1377                                // 'außer':       'off',
    13631378                        }, 'Neem de engelse afkorting "<ok>" voor "<ko>" alstublieft.': {
    1364                                 'gesloten':  'off',
    1365                                 'feestdag':  'PH',
     1379                                'gesloten':   'off',
     1380                                'feestdag':   'PH',
     1381                                'feestdagen': 'PH',
    13661382                        }, 'Assuming "<ok>" for "<ko>". Please avoid using "workday": http://wiki.openstreetmap.org/wiki/Talk:Key:opening_hours#need_syntax_for_holidays_and_workingdays': {
    1367                                 //      // Used around 260 times but the problem is, that work day might be different in other countries.
    1368                                 'wd':       'Mo-Fr',
    1369                                 'weekday':  'Mo-Fr',
    1370                                 'weekdays': 'Mo-Fr',
    1371                                 'vardagar': 'Mo-Fr',
    1372                         }, 'Please use notation something like "Mo off" instead "<ko>".': {
    1373                                 except: 'off',
    1374                         }, 'Please ommit "<ko>" or use a colon instead: "12:00-14:00".': {
    1375                                 h: '',
    1376                         }, 'Please ommit "<ko>".': {
    1377                                 season: '',
    1378                                 hs:     '',
    1379                                 hrs:    '',
    1380                                 hours:  '',
    1381                         }, 'Please ommit "<ko>". The key must not be in the value.': {
     1383                                // Used around 260 times but the problem is, that work day might be different in other countries.
     1384                                'wd':           'Mo-Fr',
     1385                                'on work day':  'Mo-Fr',
     1386                                'on work days': 'Mo-Fr',
     1387                                'weekday':      'Mo-Fr',
     1388                                'weekdays':     'Mo-Fr',
     1389                                'vardagar':     'Mo-Fr',
     1390                        }, 'Please use something like "Mo off" instead "<ko>".': {
     1391                                'except': 'off',
     1392                        }, 'Please omit "<ko>" or use a colon instead: "12:00-14:00".': {
     1393                                'h': '',
     1394                        }, 'Please omit "<ko>".': {
     1395                                'season': '',
     1396                                'hs':     '',
     1397                                'hrs':    '',
     1398                                'hours':  '',
     1399                                '·':      '',
     1400                        }, 'Please omit "<ko>". The key must not be in the value.': {
    13821401                                'opening_hours=': '',
    1383                         }, 'Please ommit "<ko>". You might want to express open end which can be specified as "12:00+" for example.': {
    1384                                 from: '',
    1385                         }, 'You can use notation "<ok>" for "<ko>". You might want to express open end which can be specified as "12:00+" for example.': {
     1402                        }, 'Please omit "<ko>". You might want to express open end which can be specified as "12:00+" for example.': {
     1403                                'from': '',
     1404                        }, 'You can use notation "<ok>" for "<ko>" in the case that you want to express open end times. Example: "12:00+".': {
     1405                                'til late': '+',
     1406                                'till late': '+',
    13861407                                '-late': '+',
     1408                                '-open end': '+',
     1409                                '-openend': '+',
    13871410                        }, 'Please use notation "<ok>" for "<ko>". If the times are unsure or vary consider a comment e.g. 12:00-14:00 "only on sunshine".': {
    13881411                                '~':  '-',
     
    13921415                        }, 'You can use notation "<ok>" for "<ko>" temporally if the syntax will still be valid.': {
    13931416                                '?':  'unknown "please add this if known"',
     1417                        }, 'Please use notation "<ok>" for "<ko>". Although using "–" is typographical correct, the opening_hours syntax is defined with the normal hyphen. Correct typography should be done on application level …': {
     1418                                '–':  '-',
    13941419                        }, 'Please use notation "<ok>" for "<ko>".': {
    1395                                 '→':  '-',
    1396                                 '–':  '-',
    1397                                 '−':  '-',
    1398                                 '=':  '-',
    1399                                 'ー': '-',
    1400                                 to:   '-',
    1401                                 'до': '-',
    1402                                 a:    '-', // language unknown
    1403                                 as:   '-', // language unknown
    1404                                 'á':  '-', // language unknown
    1405                                 'ás': '-', // language unknown
    1406                                 'à':  '-', // language unknown
    1407                                 'às': '-', // language unknown
    1408                                 'ate':  '-', // language unknown
    1409                                 'till': '-',
    1410                                 'til':  '-',
    1411                                 'until': '-',
    1412                                 'through': '-',
    1413                                 and:  ',',
    1414                                 '&':  ',',
    1415                                 ':':  ':',
    1416                                 '°°':  ':00',
    1417                                 'daily':     'Mo-Su',
    1418                                 'everyday':  'Mo-Su',
    1419                                 'every day': 'Mo-Su',
    1420                                 always:    '24/7',
    1421                                 nonstop:   '24/7',
    1422                                 '24x7':    '24/7',
    1423                                 'anytime': '24/7',
    1424                                 'all day': '24/7',
    1425                                 'all days': 'Mo-Su',
    1426                                 'every day': 'Mo-Su',
    1427                                 '7days':   'Mo-Su',
    1428                                 '7j/7':    'Mo-Su', // I guess that it means that
    1429                                 '7/7':     'Mo-Su', // I guess that it means that
    1430                                 '7 days':  'Mo-Su',
    1431                                 '7 days a week': 'Mo-Su',
    1432                                 'midnight': '00:00',
    1433                                 holiday:  'PH',
    1434                                 holidays: 'PH',
     1420                                '→':               '-',
     1421                                '−':               '-',
     1422                                '=':               '-',
     1423                                'ー':              '-',
     1424                                'to':              '-',
     1425                                'до':              '-',
     1426                                'a':               '-', // language unknown
     1427                                'as':              '-', // language unknown
     1428                                'á':               '-', // language unknown
     1429                                'ás':              '-', // language unknown
     1430                                'às':              '-', // language unknown
     1431                                'ate':             '-', // language unknown
     1432                                'till':            '-',
     1433                                'til':             '-',
     1434                                'until':           '-',
     1435                                'through':         '-',
     1436                                'and':             ',',
     1437                                '&':               ',',
     1438                                // '/':               ',', // Can not be corrected as / is a valid token
     1439                                ':':              ':',
     1440                                '°°':              ':00',
     1441                                'always':          '24/7',
     1442                                'nonstop':         '24/7',
     1443                                '24x7':            '24/7',
     1444                                'anytime':         '24/7',
     1445                                'all day':         '24/7',
     1446                                'daily':           'Mo-Su',
     1447                                'everyday':        'Mo-Su',
     1448                                'every day':       'Mo-Su',
     1449                                'all days':        'Mo-Su',
     1450                                '7j/7':            'Mo-Su', // I guess that it means that
     1451                                '7/7':             'Mo-Su', // I guess that it means that
     1452                                /* {{{
     1453                                 * Fixing this causes to ignore the following warning: "There should be no
     1454                                 * reason to differ more than 6 days from a constrained
     1455                                 * weekdays. If so tell us …".
     1456                                 * The following mistake is expected to occur more often.
     1457                                 */
     1458                                '7days':           'Mo-Su',
     1459                                '7 days':          'Mo-Su',
     1460                                // }}}
     1461                                '7 days a week':   'Mo-Su',
     1462                                '7 days/week':     'Mo-Su',
     1463                                '24 hours 7 days a week':   '24/7',
     1464                                '24 hours':                '00:00-24:00',
     1465                                'midday':          '12:00',
     1466                                'midnight':        '00:00',
     1467                                'holiday':         'PH',
     1468                                'holidays':        'PH',
    14351469                                'public holidays': 'PH',
    1436                                 'public holiday': 'PH',
     1470                                'public holiday':  'PH',
     1471                                'day after public holiday':      'PH +1 day',
     1472                                'one day after public holiday':  'PH +1 day',
     1473                                'day before public holiday':     'PH -1 day',
     1474                                'one day before public holiday': 'PH -1 day',
     1475                                'school holiday':  'SH',
     1476                                'school holidays': 'SH',
    14371477                                // summerholiday:  'SH',
    14381478                                // summerholidays: 'SH',
    1439                                 weekend:  'Sa,Su',
    1440                                 weekends: 'Sa,Su',
    1441                                 'daylight': 'sunrise-sunset',
    1442                                 'оff': 'off', // Russian o
     1479                                /* Not implemented {{{ */
     1480                                // 'day after school holiday':      'SH +1 day',
     1481                                // 'one day after school holiday':  'SH +1 day',
     1482                                // 'day before school holiday':     'SH -1 day',
     1483                                // 'one day before school holiday': 'SH -1 day',
     1484                                /* }}} */
     1485                                'weekend':         'Sa,Su',
     1486                                'weekends':        'Sa,Su',
     1487                                'daylight':        'sunrise-sunset',
     1488                        }, 'Please use notation "<ok>" for "<ko>". Those characters look very similar but are not the same!': {
     1489                                'оff':             'off', // Russian o
    14431490                        }, 'Please use time format in 24 hours notation ("<ko>"). If PM is used you might have to convert the hours to the 24 hours notation.': {
    14441491                                'pm': '',
     
    14491496                                'uhr': '',
    14501497                                'geöffnet': '',
    1451                         }, 'Bitte verzichte auf "<ko>". Sie möchten eventuell eine Öffnungszeit ohne vorgegebenes Ende angeben. Beispiel: "12:00+"': {
    1452                                 ab:  '',
    1453                                 von: '',
     1498                                'zwischen': '',
     1499                        }, 'Bitte verzichte auf "<ko>". Sie möchten eventuell eine Öffnungszeit ohne vorgegebenes Ende (Open End) angeben. Beispiel: "12:00+"': {
     1500                                'ab':  '',
     1501                                'von': '',
     1502                        }, 'Es sieht so aus also möchten Sie zusätzliche Einschränkungen für eine Öffnungszeit geben. Falls sich dies nicht mit der Syntax ausdrücken lässt können Kommentare verwendet werden. Zusätzlich sollte eventuell das Schlüsselwort `open` benutzt werden. Bitte probiere "<ok>" für "<ko>".': {
     1503                                'damen':  'open "Damen"',
     1504                                'herren': 'open "Herren"',
    14541505                        }, 'Bitte benutze die Schreibweise "<ok>" für "<ko>".': {
    1455                                 bis: '-',
    1456                                 'täglich': 'Mo-Su',
     1506                                'bis':         '-',
     1507                                'täglich':     'Mo-Su',
     1508                                'schulferien': 'SH',
     1509                                'sonn-/feiertag':      'PH,Su',
     1510                                'sonn-/feiertags':     'PH,Su',
     1511                                'an sonn- und feiertagen': 'PH,Su',
     1512                                'nur sonn-/feiertags': 'PH,Su',
     1513                        }, 'Bitte benutze die Schreibweise "<ok>" für "<ko>". Es ist war typografisch korrekt aber laut der Spezifikation für opening_hours nicht erlaubt. Siehe auch: http://wiki.openstreetmap.org/wiki/DE:Key:opening_hours:specification.': {
     1514                                '„': '"',
     1515                                '“': '"',
     1516                                '”': '"',
     1517                        }, 'Please use notation "<ok>" for "<ko>". The used quote signs might be typographically correct but are not defined in the specification. See http://wiki.openstreetmap.org/wiki/Key:opening_hours:specification.': {
     1518                                '«': '"',
     1519                                '»': '"',
     1520                                '‚': '"',
     1521                                '‘': '"',
     1522                                '’': '"',
     1523                                '「': '"',
     1524                                '」': '"',
     1525                                '『': '"',
     1526                                '』': '"',
     1527                        }, 'Please use notation "<ok>" for "<ko>". The used quote signs are not defined in the specification. See http://wiki.openstreetmap.org/wiki/Key:opening_hours:specification.': {
     1528                                "'": '"',
     1529                        }, 'You might want to use comments instead of brackets (which are not valid in this context). If you do, replace "<ok>" with "<ko>".': {
     1530                                // '(': '"',
     1531                                // ')': '"',
    14571532                        }, 'Bitte benutze die Schreibweise "<ok>" als Ersatz für "und" bzw. "u.".': {
    1458                                 und: ',',
    1459                                 u:   ',',
     1533                                'und': ',',
     1534                                'u':   ',',
    14601535                        }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>".': {
    1461                                 feiertag:   'PH',
    1462                                 feiertags:  'PH',
    1463                                 feiertage:  'PH',
    1464                                 feiertagen: 'PH'
     1536                                'feiertag':   'PH',
     1537                                'feiertags':  'PH',
     1538                                'feiertage':  'PH',
     1539                                'feiertagen': 'PH'
    14651540                        }, 'S\'il vous plaît utiliser "<ok>" pour "<ko>".': {
    1466                                 'fermé': 'off',
    1467                                 'et':    ',',
    1468                                 'à':     '-',
     1541                                'fermé':        'off',
     1542                                'et':           ',',
     1543                                'à':            '-',
    14691544                                'jours fériés': 'PH',
    1470                         }, 'Neem de engelse afkorting "<ok>" voor "<ko>" alstublieft.': {
    1471                                 feestdag:   'PH',
    1472                                 feestdagen: 'PH',
    14731545                        }
    1474                 },
    1475 
    1476                 month: {
     1546                }, /* }}} */
     1547
     1548                month: { /* {{{ */
    14771549                        'default': {
    1478                                 jan:  0,
    1479                                 feb:  1,
    1480                                 mar:  2,
    1481                                 apr:  3,
    1482                                 may:  4,
    1483                                 jun:  5,
    1484                                 jul:  6,
    1485                                 aug:  7,
    1486                                 sep:  8,
    1487                                 oct:  9,
    1488                                 nov: 10,
    1489                                 dec: 11,
     1550                                'jan':  0,
     1551                                'feb':  1,
     1552                                'mar':  2,
     1553                                'apr':  3,
     1554                                'may':  4,
     1555                                'jun':  5,
     1556                                'jul':  6,
     1557                                'aug':  7,
     1558                                'sep':  8,
     1559                                'oct':  9,
     1560                                'nov': 10,
     1561                                'dec': 11,
    14901562                        }, 'Please use the English abbreviation "<ok>" for "<ko>".': {
    14911563                                'jänner':   0, // Austria
    1492                                 january:    0,
    1493                                 february:   1,
    1494                                 march:      2,
    1495                                 april:      3,
    1496                                 // may:     4,
    1497                                 june:       5,
    1498                                 july:       6,
    1499                                 august:     7,
    1500                                 september:  8,
    1501                                 sept:       8,
    1502                                 october:    9,
    1503                                 november:  10,
    1504                                 december:  11,
     1564                                'january':    0,
     1565                                'february':   1,
     1566                                'march':      2,
     1567                                'april':      3,
     1568                                // 'may':     4,
     1569                                'june':       5,
     1570                                'july':       6,
     1571                                'august':     7,
     1572                                'september':  8,
     1573                                'sept':       8,
     1574                                'october':    9,
     1575                                'november':  10,
     1576                                'december':  11,
    15051577                        }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>".': {
    1506                                 januar:    0,
    1507                                 februar:   1,
     1578                                'januar':    0,
     1579                                'februar':   1,
    15081580                                'märz':    2,
    1509                                 maerz:     2,
    1510                                 mai:       4,
    1511                                 juni:      5,
    1512                                 juli:      6,
    1513                                 okt:       9,
    1514                                 oktober:   9,
    1515                                 dez:      11,
    1516                                 dezember: 11,
     1581                                'maerz':     2,
     1582                                'mai':       4,
     1583                                'juni':      5,
     1584                                'juli':      6,
     1585                                'okt':       9,
     1586                                'oktober':   9,
     1587                                'dez':      11,
     1588                                'dezember': 11,
    15171589                        }, 'S\'il vous plaît utiliser l\'abréviation "<ok>" pour "<ko>".': {
    1518                                 janvier:    0,
    1519                                 février:    1,
    1520                                 fév:        1,
    1521                                 mars:       2,
    1522                                 avril:      3,
    1523                                 avr:        3,
    1524                                 mai:        4,
    1525                                 juin:       5,
    1526                                 juillet:    6,
    1527                                 août:       7,
    1528                                 aoû:        7,
    1529                                 septembre:  8,
    1530                                 octobre:    9,
    1531                                 novembre:  10,
    1532                                 décembre:  11,
     1590                                'janvier':    0,
     1591                                'février':    1,
     1592                                'fév':        1,
     1593                                'mars':       2,
     1594                                'avril':      3,
     1595                                'avr':        3,
     1596                                'mai':        4,
     1597                                'juin':       5,
     1598                                'juillet':    6,
     1599                                'août':       7,
     1600                                'aoû':        7,
     1601                                'septembre':  8,
     1602                                'octobre':    9,
     1603                                'novembre':  10,
     1604                                'décembre':  11,
    15331605                        }, 'Neem de engelse afkorting "<ok>" voor "<ko>" alstublieft.': {
    1534                                 januari:  0,
    1535                                 februari: 1,
    1536                                 maart:    2,
    1537                                 mei:      4,
    1538                                 augustus: 7,
     1606                                'januari':  0,
     1607                                'februari': 1,
     1608                                'maart':    2,
     1609                                'mei':      4,
     1610                                'augustus': 7,
    15391611                        }
     1612                }, /* }}} */
     1613
     1614                calcday: {
     1615                        'default': {
     1616                                'day': 'day',
     1617                                'days': 'days',
     1618                        },
    15401619                },
    15411620
    1542                 weekday: { // good source: http://www.omniglot.com/language/time/days.htm
     1621                weekday: { // {{{ Good source: http://www.omniglot.com/language/time/days.htm */
    15431622                        'default': {
    1544                                 su: 0,
    1545                                 mo: 1,
    1546                                 tu: 2,
    1547                                 we: 3,
    1548                                 th: 4,
    1549                                 fr: 5,
    1550                                 sa: 6,
     1623                                'su': 0,
     1624                                'mo': 1,
     1625                                'tu': 2,
     1626                                'we': 3,
     1627                                'th': 4,
     1628                                'fr': 5,
     1629                                'sa': 6,
    15511630                        }, 'Assuming "<ok>" for "<ko>"': {
    1552                                 m:          1,
    1553                                 w:          3,
    1554                                 f:          5,
     1631                                'm':          1,
     1632                                'w':          3,
     1633                                'f':          5,
    15551634                        }, 'Please use the abbreviation "<ok>" for "<ko>".': {
    1556                                 sun:        0,
    1557                                 sunday:     0,
    1558                                 sundays:    0,
    1559                                 mon:        1,
    1560                                 monday:     1,
    1561                                 mondays:    1,
    1562                                 tue:        2,
    1563                                 tuesday:    2,
    1564                                 tuesdays:   2,
    1565                                 wed:        3,
    1566                                 wednesday:  3,
    1567                                 wednesdays: 3,
    1568                                 thu:        4,
    1569                                 thur:       4,
    1570                                 thursday:   4,
    1571                                 thursdays:  4,
    1572                                 fri:        5,
    1573                                 friday:     5,
    1574                                 fridays:    5,
    1575                                 sat:        6,
    1576                                 saturday:   6,
    1577                                 saturdays:  6,
     1635                                'sun':        0,
     1636                                'sunday':     0,
     1637                                'sundays':    0,
     1638                                'mon':        1,
     1639                                'monday':     1,
     1640                                'mondays':    1,
     1641                                'tue':        2,
     1642                                'tues':       2, // Used here: http://www.westerhambeauty.co.uk/contact.php
     1643                                'tuesday':    2,
     1644                                'tuesdays':   2,
     1645                                'wed':        3,
     1646                                'weds':       3,
     1647                                'wednesday':  3,
     1648                                'wednesdays': 3,
     1649                                'thu':        4,
     1650                                'thur':       4,
     1651                                'thurs':      4,
     1652                                'thursday':   4,
     1653                                'thursdays':  4,
     1654                                'fri':        5,
     1655                                'friday':     5,
     1656                                'fridays':    5,
     1657                                'sat':        6,
     1658                                'saturday':   6,
     1659                                'saturdays':  6,
    15781660                        }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>". Could also mean Saturday in Polish …': {
    1579                                 so:         0,
     1661                                'so':         0,
    15801662                        }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>".': {
    1581                                 son:         0,
    1582                                 sonntag:     0,
     1663                                'son':         0,
     1664                                'sonntag':     0,
    15831665                                'sonn-':     0,
    1584                                 sonntags:    0,
    1585                                 montag:      1,
    1586                                 montags:     1,
    1587                                 di:          2,
    1588                                 die:         2,
    1589                                 dienstag:    2,
    1590                                 dienstags:   2,
    1591                                 mi:          3,
    1592                                 mit:         3,
    1593                                 mittwoch:    3,
    1594                                 mittwochs:   3,
     1666                                'sonntags':    0,
     1667                                'montag':      1,
     1668                                'montags':     1,
     1669                                'di':          2,
     1670                                'die':         2,
     1671                                'dienstag':    2,
     1672                                'dienstags':   2,
     1673                                'mi':          3,
     1674                                'mit':         3,
     1675                                'mittwoch':    3,
     1676                                'mittwochs':   3,
    15951677                                'do':        4,
    1596                                 don:         4,
    1597                                 donnerstag:  4,
    1598                                 donnerstags: 4,
    1599                                 fre:         5,
    1600                                 freitag:     5,
    1601                                 freitags:    5,
    1602                                 sam:         6,
    1603                                 samstag:     6,
    1604                                 samstags:    6,
     1678                                'don':         4,
     1679                                'donnerstag':  4,
     1680                                'donnerstags': 4,
     1681                                'fre':         5,
     1682                                'freitag':     5,
     1683                                'freitags':    5,
     1684                                'sam':         6,
     1685                                'samstag':     6,
     1686                                'samstags':    6,
    16051687                        }, 'S\'il vous plaît utiliser l\'abréviation "<ok>" pour "<ko>".': {
    1606                                 dim:      0,
    1607                                 dimanche: 0,
    1608                                 lu:       1,
    1609                                 lun:      1,
    1610                                 lundi:    1,
    1611                                 mardi:    2,
    1612                                 mer:      3,
    1613                                 mercredi: 3,
    1614                                 je:       4,
    1615                                 jeu:      4,
    1616                                 jeudi:    4,
    1617                                 ve:       5,
    1618                                 ven:      5,
    1619                                 vendredi: 5,
    1620                                 samedi:   6,
     1688                                'dim':      0,
     1689                                'dimanche': 0,
     1690                                'lu':       1,
     1691                                'lun':      1,
     1692                                'lundi':    1,
     1693                                'mardi':    2,
     1694                                'mer':      3,
     1695                                'mercredi': 3,
     1696                                'je':       4,
     1697                                'jeu':      4,
     1698                                'jeudi':    4,
     1699                                've':       5,
     1700                                'ven':      5,
     1701                                'vendredi': 5,
     1702                                'samedi':   6,
    16211703                        }, 'Neem de engelse afkorting "<ok>" voor "<ko>" alstublieft.': {
    1622                                 zo:        0,
    1623                                 zon:       0,
    1624                                 zontag:    0, // correct?
    1625                                 zondag:    0,
    1626                                 maandag:   1,
    1627                                 din:       2,
    1628                                 dinsdag:   2,
    1629                                 wo:        3,
    1630                                 woe:       3,
    1631                                 woensdag:  3,
    1632                                 donderdag: 4,
    1633                                 vr:        5,
    1634                                 vri:       5,
    1635                                 vrijdag:   5,
    1636                                 za:        6,
    1637                                 zat:       6,
    1638                                 zaterdag:  6,
     1704                                'zo':        0,
     1705                                'zon':       0,
     1706                                'zontag':    0, // correct?
     1707                                'zondag':    0,
     1708                                'maandag':   1,
     1709                                'din':       2,
     1710                                'dinsdag':   2,
     1711                                'wo':        3,
     1712                                'woe':       3,
     1713                                'woensdag':  3,
     1714                                'donderdag': 4,
     1715                                'vr':        5,
     1716                                'vri':       5,
     1717                                'vrijdag':   5,
     1718                                'za':        6,
     1719                                'zat':       6,
     1720                                'zaterdag':  6,
    16391721                        }, 'Please use the English abbreviation "<ok>" for "<ko>".': { // FIXME: Translate to Czech.
    16401722                                'neděle':  0,
     
    16511733                                'pá':      5,
    16521734                                'sobota':  6,
    1653                         }, 'Please use the English abbreviation "<ok>" for "<ko>".': {
    1654                                 // Spanish.
     1735                        }, 'Please use the English abbreviation "<ok>" (Spanish) for "<ko>".': {
    16551736                                'martes':    0,
    16561737                                'miércoles': 1,
     
    16601741                                'domingo':   5,
    16611742                                'lunes':     6,
    1662                                 // Indonesian.
     1743                        }, 'Please use the English abbreviation "<ok>" (Indonesian) for "<ko>".': {
    16631744                                'selasa': 0,
    16641745                                'rabu':   1,
     
    16681749                                'minggu': 5,
    16691750                                'senin':  6,
    1670                                 // Swedish
     1751                        }, 'Please use the English abbreviation "<ok>" (Swedish) for "<ko>".': {
    16711752                                'söndag':   0,
    16721753                                'söndagar': 0,
     
    16741755                                'ma':       1,
    16751756                                'tisdag':   2,
    1676                                 'onsdag':   3,
     1757                                'onsdag':   3, // Same in Danish
    16771758                                'torsdag':  4,
    16781759                                'fredag':   5,
    16791760                                'lördag':   6,
    16801761                                'lördagar': 6,
    1681                                 // Polish
     1762                        }, 'Please use the English abbreviation "<ok>" (Polish) for "<ko>".': {
    16821763                                'niedziela': 0, 'niedz': 0, 'n': 0, 'ndz': 0,
    16831764                                'poniedziałek': 1, 'poniedzialek': 1, 'pon': 1, 'pn': 1,
     
    16871768                                'piątek': 5, 'piatek': 5, 'pt': 5,
    16881769                                'sobota': 6, 'sob': 6, // 'so': 6 // abbreviation also used in German
    1689                                 // Russian
     1770                        }, 'Please use the English abbreviation "<ok>" (Russian) for "<ko>".': {
    16901771                                'воскресенье' : 0,
    16911772                                'Вс'          : 0,
     
    17041785                                'суббота'     : 6,
    17051786                                'subbota'     : 6,
    1706                                 // Danish
     1787                        }, 'Please use the English abbreviation "<ok>" (Danish) for "<ko>".': {
    17071788                                'søndag' : 0,
    17081789                                'mandag' : 1,
    17091790                                'tirsdag': 2,
    1710                                 'onsdag' : 3,
     1791                                'onsdag' : 3, // Same in Swedish
    17111792                                'torsdag': 4,
    17121793                                'fredag' : 5,
    17131794                                'lørdag' : 6,
    17141795                        },
    1715                 },
    1716 
    1717                 timevar: { // Special time variables which actual value depends on the date and the position of the facility.
     1796                }, /* }}} */
     1797
     1798                timevar: { /* {{{ Special time variables which actual value depends on the date and the position of the facility. */
    17181799                        'default': {
    17191800                                'sunrise': 'sunrise',
     
    17291810                                'sonnenuntergang': 'sunset',
    17301811                        },
    1731                 },
     1812                }, /* }}} */
    17321813
    17331814                'event': { // variable events
     
    17551836        return function(value, nominatiomJSON, oh_mode) {
    17561837                // short constants {{{
    1757                 var word_value_replacement = { // if the correct values can not be calculated
     1838                var word_value_replacement = { // If the correct values can not be calculated.
    17581839                        dawn    : 60 * 5 + 30,
    17591840                        sunrise : 60 * 6,
     
    17611842                        dusk    : 60 * 18 + 30,
    17621843                };
    1763                 var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
     1844                var months   = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
    17641845                var weekdays = ['Su','Mo','Tu','We','Th','Fr','Sa'];
    17651846                var default_prettify_conf = {
    1766                         'leading_zero_hour': true,       // enforce leading zero
     1847                        // Update README.md if changed.
     1848                        'zero_pad_hour': true,           // enforce ("%02d", hour)
    17671849                        'one_zero_if_hour_zero': false,  // only one zero "0" if hour is zero "0"
    1768                         'leave_off_closed': true,        // leave keywords of and closed as is
     1850                        'leave_off_closed': true,        // leave keywords "off" and "closed" as is
    17691851                        'keyword_for_off_closed': 'off', // use given keyword instead of "off" or "closed"
    1770                         'block_sep_string': ' ',         // separate blocks by string
    1771                         'print_semicolon': true,         // print token which separates normal blocks
     1852                        'rule_sep_string': ' ',          // separate rules by string
     1853                        'print_semicolon': true,         // print token which separates normal rules
    17721854                        'leave_weekday_sep_one_day_betw': true, // use the separator (either "," or "-" which is used to separate days which follow to each other like Sa,Su or Su-Mo
    1773                         'sep_one_day_between': ',' // separator which should be used
     1855                        'sep_one_day_between': ',',      // separator which should be used
     1856                        'zero_pad_month_and_week_numbers': false, // Format week (e.g. `week 01`) and month day numbers (e.g. `Jan 01`) with "%02d".
    17741857                };
    17751858
     
    17771860                var msec_in_day    = 1000 * 60 * minutes_in_day;
    17781861                var msec_in_week   = msec_in_day * 7;
    1779                 // }}}
    1780 
    1781                 // The big picture -- How does this library work? {{{
    1782                 //======================================================================
    1783                 // Constructor - entry to parsing code
    1784                 //======================================================================
    1785                 // Terminology:
    1786                 //
    1787                 // Mo-Fr 10:00-11:00; Th 10:00-12:00
    1788                 // \_____block_____/  \____block___/
    1789                 //
    1790                 // The README refers to blocks as rules, which is more intuitive but less clear.
    1791                 // Because of that only the README uses the term rule in that context.
    1792                 // In all internal parts of this project, the term block is used.
    1793                 //
    1794                 // Mo-Fr Jan 10:00-11:00
    1795                 // \__/  \_/ \_________/
    1796                 // selectors (left to right: weekday, month, time)
    1797                 //
    1798                 // Logic:
    1799                 // - Tokenize
    1800                 // Foreach block:
    1801                 //   - Run top-level (block) parser
    1802                 //     - Which calls sub parser for specific selector types
    1803                 //       - Which produce selector functions
     1862
     1863                var library_name   = 'opening_hours.js';
     1864                var repository_url = 'https://github.com/ypid/' + library_name;
     1865                var issues_url     = repository_url + '/issues?state=open';
    18041866                // }}}
    18051867
     
    18271889                if (typeof oh_mode == 'undefined') {
    18281890                        oh_mode = 0;
    1829                 } else if (!(typeof oh_mode == 'number' && (oh_mode == 0 || oh_mode == 1 || oh_mode == 2))) {
    1830                         throw 'The third constructor parameter is oh_mode and must be a number (0, 1 or 2)'
     1891                } else if (!(typeof oh_mode == 'number' && (oh_mode === 0 || oh_mode == 1 || oh_mode == 2))) {
     1892                        throw 'The third constructor parameter is oh_mode and must be a number (0, 1 or 2)';
    18311893                }
    18321894                // }}}
    18331895
    1834                 // put tokenized blocks into list {{{
    1835                 if (value.match(/^(\s*;?\s*)+$/))
     1896                // Tokenize value and generate selector functions. {{{
     1897                if (value.match(/^(?:\s*;?\s*)+$/))
    18361898                        throw 'Value contains nothing meaningful which can be parsed';
    18371899
    1838                 var parsing_warnings = [];
     1900                var parsing_warnings = []; // Elements are fed into function formatWarnErrorMessage(nrule, at, message)
    18391901                var done_with_warnings = false; // The functions which throw warnings can be called multiple times.
    1840                 var has_token = {};
     1902                var done_with_selector_reordering = false;
     1903                var done_with_selector_reordering_warnings = false;
    18411904                var tokens = tokenize(value);
    1842                 // console.log(JSON.stringify(tokens, null, '\t'));
     1905                // console.log(JSON.stringify(tokens, null, '    '));
    18431906                var prettified_value = '';
    1844                 var used_subparsers = {}; // Used sub parsers for one block, will be reset for each block. Declared in global namespace, because it is manipulation inside various sub parsers.
    18451907                var week_stable = true;
    18461908
    1847                 var blocks = [];
    1848 
    1849                 for (var nblock = 0; nblock < tokens.length; nblock++) {
    1850                         if (tokens[nblock][0].length == 0) continue;
    1851                         // Block does contain nothing useful e.g. second block of '10:00-12:00;' (empty) which needs to be handled.
     1909                var rules = [];
     1910                var new_tokens = [];
     1911
     1912                for (var nrule = 0; nrule < tokens.length; nrule++) {
     1913                        if (tokens[nrule][0].length === 0) {
     1914                                // Rule does contain nothing useful e.g. second rule of '10:00-12:00;' (empty) which needs to be handled.
     1915                                parsing_warnings.push([nrule, -1,
     1916                                        'This rule does not contain anything useful. Please remove this empty rule.'
     1917                                        + (nrule == tokens.length - 1 && nrule > 0 && !tokens[nrule][1] ?
     1918                                                ' Might it be possible that you are a programmer and adding a semicolon after each statement is hardwired in your muscle memory ;) ?'
     1919                                                + ' The thing is that the semicolon in the opening_hours syntax is defined as rule separator.'
     1920                                                + ' So for compatibility reasons you should omit this last semicolon.': '')
     1921                                        ]);
     1922                                continue;
     1923                        }
    18521924
    18531925                        var continue_at = 0;
     1926                        var next_rule_is_additional = false;
    18541927                        do {
    1855                                 if (continue_at == tokens[nblock][0].length) break;
    1856                                 // Additional block does contain nothing useful e.g. second block of '10:00-12:00,' (empty) which needs to be handled.
     1928                                if (continue_at == tokens[nrule][0].length) break;
     1929                                // Additional rule does contain nothing useful e.g. second rule of '10:00-12:00,' (empty) which needs to be handled.
    18571930
    18581931                                var selectors = {
     
    18741947                                        date: [],
    18751948
    1876                                         fallback: tokens[nblock][1],
     1949                                        fallback: tokens[nrule][1],
    18771950                                        additional: continue_at ? true : false,
    18781951                                        meaning: true,
    18791952                                        unknown: false,
    18801953                                        comment: undefined,
    1881                                         build_from_token_block: undefined,
     1954                                        build_from_token_rule: undefined,
    18821955                                };
    18831956
    1884                                 selectors.build_from_token_block = [ nblock, continue_at ];
    1885                                 continue_at = parseGroup(tokens[nblock][0], continue_at, selectors, nblock);
    1886                                 if (typeof continue_at == 'object')
     1957                                selectors.build_from_token_rule = [ nrule, continue_at, new_tokens.length ];
     1958                                continue_at = parseGroup(tokens[nrule][0], continue_at, selectors, nrule);
     1959                                if (typeof continue_at == 'object') {
    18871960                                        continue_at = continue_at[0];
    1888                                 else
     1961                                } else {
    18891962                                        continue_at = 0;
     1963                                }
     1964
     1965                                // console.log('Current tokens: ' + JSON.stringify(tokens[nrule], null, '    '));
     1966
     1967                                new_tokens.push(
     1968                                        [
     1969                                                tokens[nrule][0].slice(
     1970                                                        selectors.build_from_token_rule[1],
     1971                                                        continue_at === 0
     1972                                                                ? tokens[nrule][0].length
     1973                                                                : continue_at
     1974                                                ),
     1975                                                tokens[nrule][1],
     1976                                                tokens[nrule][2],
     1977                                        ]
     1978                                );
     1979
     1980                                if (next_rule_is_additional && new_tokens.length > 1) {
     1981                                        // Move 'rule separator' from last token of last rule to first token of this rule.
     1982                                        new_tokens[new_tokens.length - 1][0].unshift(new_tokens[new_tokens.length - 2][0].pop());
     1983                                }
     1984
     1985                                next_rule_is_additional = continue_at === 0 ? false : true;
    18901986
    18911987                                if (selectors.year.length > 0)
     
    19031999
    19042000                                // console.log('weekday: ' + JSON.stringify(selectors.weekday, null, '\t'));
    1905                                 blocks.push(selectors);
     2001                                rules.push(selectors);
    19062002
    19072003                                // This handles selectors with time ranges wrapping over midnight (e.g. 10:00-02:00)
    1908                                 // it generates wrappers for all selectors and creates a new block.
     2004                                // it generates wrappers for all selectors and creates a new rule.
    19092005                                if (selectors.wraptime.length > 0) {
    19102006                                        var wrapselectors = {
     
    19172013
    19182014                                                wrapped: true,
     2015                                                // build_from_token_rule: selectors.build_from_token_rule,
     2016                                                // Not (yet) needed.
    19192017                                        };
    19202018
     
    19282026                                        }
    19292027
    1930                                         blocks.push(wrapselectors);
     2028                                        rules.push(wrapselectors);
    19312029                                }
    1932                         } while (continue_at)
     2030                        } while (continue_at);
     2031                }
     2032                // console.log(JSON.stringify(tokens, null, '    '));
     2033                // console.log(JSON.stringify(new_tokens, null, '    '));
     2034                // }}}
     2035
     2036                /* Format warning or error message for the user. {{{
     2037                 *
     2038                 * :param nrule: Rule number starting with zero.
     2039                 * :param at: Token position at which the issue occurred.
     2040                 * :param message: Human readable string with the message.
     2041                 * :returns: String with position of the warning or error marked for the user.
     2042                 */
     2043                function formatWarnErrorMessage(nrule, at, message) {
     2044                        // FIXME: Change to new_tokens.
     2045                        if (typeof nrule == 'number') {
     2046                                var pos = 0;
     2047                                if (nrule == -1) { // Usage of rule index not required because we do have access to value.length.
     2048                                        pos = value.length - at;
     2049                                } else { // Issue accrued at a later time, position in string needs to be reconstructed.
     2050                                        if (typeof tokens[nrule][0][at] == 'undefined') {
     2051                                                if (typeof tokens[nrule][0] && at == -1) {
     2052                                                        pos = value.length;
     2053                                                        if (typeof tokens[nrule+1] == 'object' && typeof tokens[nrule+1][2] == 'number') {
     2054                                                                pos -= tokens[nrule+1][2];
     2055                                                        } else if (typeof tokens[nrule][2] == 'number') {
     2056                                                                pos -= tokens[nrule][2];
     2057                                                        }
     2058                                                } else {
     2059                                                        // Given position is invalid.
     2060                                                        //
     2061                                                        formatLibraryBugMessage('Bug in warning generation code which could not determine the exact position of the warning or error in value.');
     2062                                                        pos = value.length;
     2063                                                        if (typeof tokens[nrule][2] != 'undefined') {
     2064                                                                // Fallback: Point to last token in the rule which caused the problem.
     2065                                                                // Run real_test regularly to fix the problem before a user is confronted with it.
     2066                                                                pos -= tokens[nrule][2];
     2067                                                                console.warn('Last token for rule: ' + tokens[nrule]);
     2068                                                                console.log(value.substring(0, pos) + ' <--- (' + message + ')');
     2069                                                                console.log('\n');
     2070                                                        } {
     2071                                                                console.warn('tokens[nrule][2] is undefined. This is ok if nrule is the last rule.');
     2072                                                        }
     2073                                                }
     2074                                        } else {
     2075                                                pos = value.length;
     2076                                                if (typeof tokens[nrule][0][at+1] != 'undefined') {
     2077                                                        pos -= tokens[nrule][0][at+1][2];
     2078                                                } else if (typeof tokens[nrule][2] != 'undefined') {
     2079                                                        pos -= tokens[nrule][2];
     2080                                                }
     2081                                        }
     2082                                }
     2083                                return value.substring(0, pos) + ' <--- (' + message + ')';
     2084                        } else if (typeof nrule == 'string') {
     2085                                return nrule.substring(0, at) + ' <--- (' + message + ')';
     2086                        }
    19332087                }
    19342088                // }}}
    19352089
    1936                 /* Tokenization function: Splits string into parts. {{{
     2090                /* Format internal library error message. {{{
     2091                 *
     2092                 * :param message: Human readable string with the error message.
     2093                 * :returns: Error message for the user.
     2094                 */
     2095                function formatLibraryBugMessage(message) {
     2096                        if (typeof message == 'undefined')
     2097                                message = '';
     2098                        else
     2099                                message = ' ' + message;
     2100
     2101                        message = 'An error occurred during evaluation of the value "' + value + '".'
     2102                                + ' Please file a bug report here: ' + issues_url + '.'
     2103                                + message;
     2104                        console.log(message);
     2105                        return message;
     2106                }
     2107                // }}}
     2108
     2109                /* Tokenize input stream. {{{
    19372110                 *
    19382111                 * :param value: Raw opening_hours value.
    1939                  * :returns: Tokenized list object. Complex structure. You can print the
    1940                  *              thing as JSON if you would like to know in details.
    1941                  *              The most inner list has the following items: [ internal_value, token_name, value_length ].
     2112                 * :returns: Tokenized list object. Complex structure. Check the
     2113                 *              internal documentation in the doc directory for details.
    19422114                 */
    19432115                function tokenize(value) {
    1944                         var all_tokens        = new Array();
    1945                         var curr_block_tokens = new Array();
    1946 
    1947                         var last_block_fallback_terminated = false;
    1948 
    1949                         while (value != '') {
     2116                        var all_tokens       = [];
     2117                        var curr_rule_tokens = [];
     2118
     2119                        var last_rule_fallback_terminated = false;
     2120
     2121                        while (value !== '') {
     2122                                // console.log("Parsing value: " + value);
    19502123                                var tmp;
    1951                                 if (tmp = value.match(/^(?:week\b|open\b|unknown\b)/i)) {
     2124                                if (tmp = value.match(/^week\b/i)) {
    19522125                                        // Reserved keywords.
    1953                                         curr_block_tokens.push([tmp[0].toLowerCase(), tmp[0].toLowerCase(), value.length ]);
     2126                                        curr_rule_tokens.push([tmp[0].toLowerCase(), tmp[0].toLowerCase(), value.length ]);
     2127                                        value = value.substr(tmp[0].length);
     2128                                } else if (tmp = value.match(/^(?:off\b|closed\b|open\b|unknown\b)/i)) {
     2129                                        // Reserved keywords.
     2130                                        curr_rule_tokens.push([tmp[0].toLowerCase(), 'state', value.length ]);
    19542131                                        value = value.substr(tmp[0].length);
    19552132                                } else if (tmp = value.match(/^24\/7/i)) {
    19562133                                        // Reserved keyword.
    1957                                         has_token[tmp[0]] = true;
    1958                                         curr_block_tokens.push([tmp[0], tmp[0], value.length ]);
    1959                                         value = value.substr(tmp[0].length);
    1960                                 } else if (tmp = value.match(/^(?:off|closed)/i)) {
    1961                                         // Reserved keywords.
    1962                                         curr_block_tokens.push([tmp[0].toLowerCase(), 'closed', value.length ]);
     2134                                        curr_rule_tokens.push([tmp[0], tmp[0], value.length ]);
    19632135                                        value = value.substr(tmp[0].length);
    19642136                                } else if (tmp = value.match(/^(?:PH|SH)/i)) {
    19652137                                        // special day name (holidays)
    1966                                         curr_block_tokens.push([tmp[0].toUpperCase(), 'holiday', value.length ]);
     2138                                        curr_rule_tokens.push([tmp[0].toUpperCase(), 'holiday', value.length ]);
    19672139                                        value = value.substr(2);
    1968                                 } else if (tmp = value.match(/^days?/i)) {
    1969                                         curr_block_tokens.push([tmp[0].toLowerCase(), 'calcday', value.length ]);
    1970                                         value = value.substr(tmp[0].length);
    1971                                 } else if (tmp = value.match(/^(&|_|→|–|−|=|opening_hours=|ー|\?|~|~|:|°°|25x7|7[ ]?days( a week|)|all days?|every day|-late|public holidays?|7j?\/7|every day|до|рм|ам|jours fériés|sonn-|[a-zäößàáéøčěíúýřПнВсо]+\b)\.?/i)) {
    1972                                         // Handle all remaining words with error tolerance.
     2140                                } 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)) {
     2141                                        /* Handle all remaining words and specific other characters with error tolerance.
     2142                                         *
     2143                                         * à|á: Word boundary does not work with unicode chars: 'test à test'.match(/\bà\b/i)
     2144                                         * https://stackoverflow.com/questions/10590098/javascript-regexp-word-boundaries-unicode-characters
     2145                                         * Order in the regular expression capturing group is important in some cases.
     2146                                         *
     2147                                         * mo|tu|we|th|fr|sa|su|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec: Prefer defended keywords
     2148                                         * if used in cases like 'mo12:00-14:00' (when keyword is followed by number).
     2149                                         */
    19732150                                        var correct_val = returnCorrectWordOrToken(tmp[1].toLowerCase(), value.length);
     2151                                        // console.log('Error tolerance for string "' + tmp[1] + '" returned "' + correct_val + '".');
    19742152                                        if (typeof correct_val == 'object') {
    1975                                                 curr_block_tokens.push([ correct_val[0], correct_val[1], value.length ]);
     2153                                                curr_rule_tokens.push([ correct_val[0], correct_val[1], value.length ]);
    19762154                                                value = value.substr(tmp[0].length);
    19772155                                        } else if (typeof correct_val == 'string') {
    19782156                                                if (tmp[1].toLowerCase() == 'pm') {
    1979                                                         var hours_token_at = curr_block_tokens.length - 3;
    1980                                                         if (hours_token_at > 0) {
    1981                                                                 if (matchTokens(curr_block_tokens, hours_token_at,
    1982                                                                                         'number', 'timesep', 'number')
    1983                                                                                 ) {
    1984                                                                         var hours_token = curr_block_tokens[hours_token_at];
    1985                                                                 } else if (matchTokens(curr_block_tokens, hours_token_at + 2,
    1986                                                                                         'number')
    1987                                                                                 ) {
    1988                                                                         hours_token_at += 2;
    1989                                                                         var hours_token = curr_block_tokens[hours_token_at];
     2157                                                        var hours_token_at = curr_rule_tokens.length - 1;
     2158                                                        var hours_token;
     2159                                                        if (hours_token_at >= 0) {
     2160                                                                if (hours_token_at -2 >= 0 &&
     2161                                                                                matchTokens(
     2162                                                                                        curr_rule_tokens, hours_token_at - 2,
     2163                                                                                        'number', 'timesep', 'number'
     2164                                                                                )
     2165                                                                        ) {
     2166                                                                        hours_token_at -= 2;
     2167                                                                        hours_token = curr_rule_tokens[hours_token_at];
     2168                                                                } else if (matchTokens(curr_rule_tokens, hours_token_at, 'number')) {
     2169                                                                        hours_token = curr_rule_tokens[hours_token_at];
    19902170                                                                }
    1991                                                                 if (hours_token[0] <= 12) {
     2171
     2172                                                                if (typeof hours_token == 'object' && hours_token[0] <= 12) {
    19922173                                                                        hours_token[0] += 12;
    1993                                                                         curr_block_tokens[hours_token_at] = hours_token;
     2174                                                                        curr_rule_tokens[hours_token_at] = hours_token;
    19942175                                                                }
    19952176                                                        }
    19962177                                                }
    1997                                                 value = correct_val + value.substr(tmp[0].length);
     2178                                                var correct_tokens = tokenize(correct_val)[0];
     2179                                                if (correct_tokens[1] === true) { // last_rule_fallback_terminated
     2180                                                        throw formatLibraryBugMessage();
     2181                                                }
     2182                                                for (var i = 0; i < correct_tokens[0].length; i++) {
     2183                                                        curr_rule_tokens.push([correct_tokens[0][i][0], correct_tokens[0][i][1], value.length]);
     2184                                                        // value.length - tmp[0].length does not have the desired effect for all test cases.
     2185                                                }
     2186
     2187                                                value = value.substr(tmp[0].length);
     2188                                                // value = correct_val + value.substr(tmp[0].length);
     2189                                                // Does not work because it would generate the wrong length for formatWarnErrorMessage.
    19982190                                        } else {
    19992191                                                // other single-character tokens
    2000                                                 curr_block_tokens.push([value[0].toLowerCase(), value[0].toLowerCase(), value.length - 1 ]);
     2192                                                curr_rule_tokens.push([value[0].toLowerCase(), value[0].toLowerCase(), value.length - 1 ]);
    20012193                                                value = value.substr(1);
    20022194                                        }
    20032195                                } else if (tmp = value.match(/^\d+/)) {
    20042196                                        // number
    2005                                         if (tmp[0] > 1900) // Assumed to be a year number.
    2006                                                 curr_block_tokens.push([tmp[0], 'year', value.length ]);
    2007                                         else
    2008                                                 curr_block_tokens.push([+tmp[0], 'number', value.length ]);
     2197                                        if (Number(tmp[0]) > 1900) { // Assumed to be a year number.
     2198                                                curr_rule_tokens.push([tmp[0], 'year', value.length ]);
     2199                                                if (Number(tmp[0]) >= 2100) // Probably an error
     2200                                                        parsing_warnings.push([ -1, value.length - 1,
     2201                                                                'The number ' + Number(tmp[0]) + ' will be interpreted as year.'
     2202                                                                + ' This is probably not intended. Times can be specified as "12:00".'
     2203                                                        ]);
     2204                                        } else {
     2205                                                curr_rule_tokens.push([Number(tmp[0]), 'number', value.length ]);
     2206                                        }
     2207
    20092208                                        value = value.substr(tmp[0].length);
    2010                                 } else if (tmp = value.match(/^"([^"]*)"/)) {
    2011                                         // comment
    2012                                         curr_block_tokens.push([tmp[1], 'comment', value.length ]);
     2209                                } else if (tmp = value.match(/^"([^"]+)"/)) {
     2210                                        // Comment following the specification.
     2211                                        // Any character is allowed inside the comment except " itself.
     2212                                        curr_rule_tokens.push([tmp[1], 'comment', value.length ]);
     2213                                        value = value.substr(tmp[0].length);
     2214                                } else if (tmp = value.match(/^(["'„“‚‘’«「『])([^"'“”‘’»」』;|]*)(["'”“‘’»」』])/)) {
     2215                                        // Comments with error tolerance.
     2216                                        // The comments still have to be somewhat correct meaning
     2217                                        // the start and end quote signs used have to be
     2218                                        // appropriate. So “testing„ will not match as it is not a
     2219                                        // quote but rather something unknown which the user should
     2220                                        // fix first.
     2221                                        // console.log('Matched: ' + JSON.stringify(tmp));
     2222                                        for (var pos = 1; pos <= 3; pos += 2) {
     2223                                                // console.log('Pos: ' + pos + ', substring: ' + tmp[pos]);
     2224                                                var correct_val = returnCorrectWordOrToken(tmp[pos],
     2225                                                        value.length - (pos == 3 ? tmp[1].length + tmp[2].length : 0)
     2226                                                );
     2227                                                if (typeof correct_val != 'string' && tmp[pos] != '"') {
     2228                                                        throw formatLibraryBugMessage(
     2229                                                                'A character for error tolerance was allowed in the regular expression'
     2230                                                                + ' but is not covered by word_error_correction'
     2231                                                                + ' which is needed to format a proper message for the user.'
     2232                                                        );
     2233                                                }
     2234                                        }
     2235                                        curr_rule_tokens.push([tmp[2], 'comment', value.length ]);
    20132236                                        value = value.substr(tmp[0].length);
    20142237                                } else if (value.match(/^;/)) {
    2015                                         // semicolon terminates block
    2016                                         // next tokens belong to a new block
    2017                                         all_tokens.push([ curr_block_tokens, last_block_fallback_terminated, value.length ]);
     2238                                        // semicolon terminates rule
     2239                                        // next tokens belong to a new rule
     2240                                        all_tokens.push([ curr_rule_tokens, last_rule_fallback_terminated, value.length ]);
    20182241                                        value = value.substr(1);
    20192242
    2020                                         curr_block_tokens = [];
    2021                                         last_block_fallback_terminated = false;
     2243                                        curr_rule_tokens = [];
     2244                                        last_rule_fallback_terminated = false;
    20222245                                } else if (value.match(/^\|\|/)) {
    2023                                         // || terminates block
    2024                                         // next tokens belong to a fallback block
    2025                                         if (curr_block_tokens.length == 0)
     2246                                        // || terminates rule
     2247                                        // Next tokens belong to a fallback rule.
     2248                                        if (curr_rule_tokens.length === 0)
    20262249                                                throw formatWarnErrorMessage(-1, value.length - 2, 'Rule before fallback rule does not contain anything useful');
    20272250
    2028                                         all_tokens.push([ curr_block_tokens, last_block_fallback_terminated, value.length ]);
     2251                                        all_tokens.push([ curr_rule_tokens, last_rule_fallback_terminated, value.length ]);
     2252                                        curr_rule_tokens = [];
     2253                                        // curr_rule_tokens = [ [ '||', 'rule separator', value.length  ] ];
     2254                                        // FIXME: Use this. Unknown bug needs to be solved in the process.
    20292255                                        value = value.substr(2);
    20302256
    2031                                         curr_block_tokens = [];
    2032                                         last_block_fallback_terminated = true;
     2257                                        last_rule_fallback_terminated = true;
    20332258                                } else if (value.match(/^(?:␣|\s)/)) {
    2034                                         // Using "␣" as space is not expected to be a normal mistake. Just ignore it to make using taginfo easier.
     2259                                        // Using "␣" as space is not expected to be a normal
     2260                                        // mistake. Just ignore it to make using taginfo easier.
    20352261                                        value = value.substr(1);
    20362262                                } else if (tmp = value.match(/^\s+/)) {
     
    20412267                                        if (value[0] == '.' && !done_with_warnings)
    20422268                                                parsing_warnings.push([ -1, value.length - 1, 'Please use ":" as hour/minute-separator' ]);
    2043                                         curr_block_tokens.push([ ':', 'timesep', value.length ]);
     2269                                        curr_rule_tokens.push([ ':', 'timesep', value.length ]);
    20442270                                        value = value.substr(1);
    20452271                                } else {
    20462272                                        // other single-character tokens
    2047                                         curr_block_tokens.push([value[0].toLowerCase(), value[0].toLowerCase(), value.length ]);
     2273                                        curr_rule_tokens.push([value[0].toLowerCase(), value[0].toLowerCase(), value.length ]);
    20482274                                        value = value.substr(1);
    20492275                                }
    20502276                        }
    20512277
    2052                         all_tokens.push([ curr_block_tokens, last_block_fallback_terminated ]);
     2278                        all_tokens.push([ curr_rule_tokens, last_rule_fallback_terminated ]);
    20532279
    20542280                        return all_tokens;
     
    20642290                 *              * (valid) opening_hours sub string.
    20652291                 *              * object with [ internal_value, token_name ] if value is correct.
    2066                  *              * undefined if word could not be found (and thus not be corrected).
     2292                 *              * undefined if word could not be found (and thus is not be corrected).
    20672293                 */
    20682294                function returnCorrectWordOrToken(word, value_length) {
     
    20892315                                                                }
    20902316                                                                if (typeof correct_abbr == 'undefined') {
    2091                                                                         throw 'Please file a bug for opening_hours.js.'
    2092                                                                                 + ' Including the stacktrace.'
     2317                                                                        throw formatLibraryBugMessage('Including the stacktrace.');
    20932318                                                                }
    20942319                                                                if (token_name != 'timevar') {
    20952320                                                                        // Everything else than timevar:
    2096                                                                         // E.g. 'Mo' are start with a upper case letter.
     2321                                                                        // E.g. 'Mo' start with a upper case letter.
    20972322                                                                        // It just looks better.
    20982323                                                                        correct_abbr = correct_abbr.charAt(0).toUpperCase()
     
    21182343                 */
    21192344                function getWarnings(it) {
    2120                         if (typeof it == 'object') { // getWarnings was called in a state without critical errors. We can do extended tests.
    2121 
    2122                                 // Check if 24/7 is used and it does not mean 24/7 because there are other blocks.
     2345                        if (!done_with_warnings && typeof it == 'object') {
     2346                                /* getWarnings was called in a state without critical errors.
     2347                                 * We can do extended tests.
     2348                                 */
     2349
     2350                                /* Place all tests in this function if an additional (high
     2351                                 * level) test is added and this does not require to rewrite
     2352                                 * big parts of (sub) selector parsers only to get the
     2353                                 * position. If that is the case, then rather place the test
     2354                                 * code in the (sub) selector parser function directly.
     2355                                 */
     2356
     2357                                var wide_range_selectors = [ 'year', 'month', 'week', 'holiday' ];
     2358                                var small_range_selectors = [ 'weekday', 'time', '24/7', 'state', 'comment'];
     2359
     2360                                // How many times was a selector_type used per rule? {{{
     2361                                var used_selectors = [];
     2362                                var used_selectors_types_array = [];
     2363                                var has_token = {};
     2364
     2365                                for (var nrule = 0; nrule < new_tokens.length; nrule++) {
     2366                                        if (new_tokens[nrule][0].length === 0) continue;
     2367                                        // Rule does contain nothing useful e.g. second rule of '10:00-12:00;' (empty) which needs to be handled.
     2368
     2369                                        var selector_start_end_type = [ 0, 0, undefined ],
     2370                                                prettified_group_value  = [];
     2371                                        // console.log(new_tokens[nrule][0]);
     2372
     2373                                        used_selectors[nrule] = {};
     2374                                        used_selectors_types_array[nrule] = [];
     2375
     2376                                        do {
     2377                                                selector_start_end_type = getSelectorRange(new_tokens[nrule][0], selector_start_end_type[1]);
     2378                                                // console.log(selector_start_end_type, new_tokens[nrule][0].length);
     2379
     2380                                                if (selector_start_end_type[0] == selector_start_end_type[1] &&
     2381                                                        new_tokens[nrule][0][selector_start_end_type[0]][0] == '24/7'
     2382                                                        ) {
     2383                                                                has_token['24/7'] = true;
     2384                                                }
     2385
     2386                                                if (typeof used_selectors[nrule][selector_start_end_type[2]] != 'object') {
     2387                                                        used_selectors[nrule][selector_start_end_type[2]] = [ selector_start_end_type[1] ];
     2388                                                } else {
     2389                                                        used_selectors[nrule][selector_start_end_type[2]].push(selector_start_end_type[1]);
     2390                                                }
     2391                                                used_selectors_types_array[nrule].push(selector_start_end_type[2]);
     2392
     2393                                                selector_start_end_type[1]++;
     2394                                        } while (selector_start_end_type[1] < new_tokens[nrule][0].length);
     2395                                }
     2396                                // console.log('used_selectors: ' + JSON.stringify(used_selectors, null, '    '));
     2397                                // }}}
     2398
     2399                                for (var nrule = 0; nrule < used_selectors.length; nrule++) {
     2400
     2401                                        /* Check if more than one not connected selector of the same type is used in one rule {{{ */
     2402                                        for (var selector_type in used_selectors[nrule]) {
     2403                                                // console.log(selector_type + ' use at: ' + used_selectors[nrule][selector_type].length);
     2404                                                if (used_selectors[nrule][selector_type].length > 1) {
     2405                                                        parsing_warnings.push([nrule, used_selectors[nrule][selector_type][used_selectors[nrule][selector_type].length - 1],
     2406                                                                'You have used ' + used_selectors[nrule][selector_type].length
     2407                                                                + (selector_type.match(/^(?:comment|state)/) ?
     2408                                                                        ' ' + selector_type
     2409                                                                        + (selector_type == 'state' ? ' keywords' : 's')
     2410                                                                        + ' in one rule.'
     2411                                                                        + ' You may only use one in one rule.'
     2412                                                                        :
     2413                                                                        ' not connected ' + selector_type
     2414                                                                        + (selector_type.match(/^(?:month|weekday)$/) ? 's' : ' ranges')
     2415                                                                        + ' in one rule.'
     2416                                                                        + ' This is probably an error.'
     2417                                                                        + ' Equal selector types can (and should) always be written in conjunction separated by comma or something.'
     2418                                                                        + ' Example for time ranges "12:00-13:00,15:00-18:00".'
     2419                                                                        + ' Example for weekdays "Mo-We,Fr".'
     2420                                                                  )
     2421                                                                + ' Rules can be separated by ";".' ]
     2422                                                        );
     2423                                                        done_with_selector_reordering = true; // Correcting the selector order makes no sense if this kind of issue exists.
     2424                                                }
     2425                                        }
     2426                                        /* }}} */
     2427
     2428                                        /* Check if change default state rule is not the first rule {{{ */
     2429                                        if (   typeof used_selectors[nrule].state === 'object'
     2430                                                && Object.keys(used_selectors[nrule]).length === 1
     2431                                        ) {
     2432
     2433                                                if (nrule !== 0) {
     2434                                                        parsing_warnings.push([nrule, new_tokens[nrule][0].length - 1,
     2435                                                                "This rule which changes the default state (which is closed) for all following rules is not the first rule."
     2436                                                                + " The rule will overwrite all previous rules."
     2437                                                                + " It can be legitimate to change the default state to open for example"
     2438                                                                + " and then only specify for which times the facility is closed."
     2439                                                        ]);
     2440                                                }
     2441                                        /* }}} */
     2442                                        /* Check if a rule (with state open) has no time selector {{{ */
     2443                                        } else if (typeof used_selectors[nrule].time === 'undefined') {
     2444                                                if (    (          typeof used_selectors[nrule].state === 'object'
     2445                                                                        && new_tokens[nrule][0][used_selectors[nrule].state[0]][0] === 'open'
     2446                                                                        && typeof used_selectors[nrule].comment === 'undefined'
     2447                                                                ) || ( typeof used_selectors[nrule].comment === 'undefined'
     2448                                                                        && typeof used_selectors[nrule].state === 'undefined'
     2449                                                                ) &&
     2450                                                                typeof used_selectors[nrule]['24/7'] === 'undefined'
     2451                                                ) {
     2452
     2453                                                        parsing_warnings.push([nrule, new_tokens[nrule][0].length - 1,
     2454                                                                "This rule is not very explicit because there is no time selector being used."
     2455                                                                + " Please add a time selector to this rule or use a comment to make it more explicit."
     2456                                                        ]);
     2457                                                }
     2458                                        }
     2459                                        /* }}} */
     2460
     2461                                        /* Check if empty comment was given {{{ */
     2462                                        if (typeof used_selectors[nrule].comment === 'object'
     2463                                                && new_tokens[nrule][0][used_selectors[nrule].comment[0]][0].length === 0
     2464                                        ) {
     2465
     2466                                                parsing_warnings.push([nrule, used_selectors[nrule].comment[0],
     2467                                                        "You have used an empty comment."
     2468                                                        + " Please either write something in the comment or use the keyword unknown instead."
     2469                                                ]);
     2470                                        }
     2471                                        /* }}} */
     2472
     2473                                        /* Check if rule with closed|off modifier is additional {{{ */
     2474                                        /* FIXME: Enable this test. */
     2475                                        if (typeof new_tokens[nrule][0][0] === 'object'
     2476                                                        && new_tokens[nrule][0][0][0] === ','
     2477                                                        && new_tokens[nrule][0][0][1] === 'rule separator'
     2478                                                        && typeof used_selectors[nrule].state === 'object'
     2479                                                        && (
     2480                                                                   new_tokens[nrule][0][used_selectors[nrule].state[0]][0] === 'closed'
     2481                                                                || new_tokens[nrule][0][used_selectors[nrule].state[0]][0] === 'off'
     2482                                                           )
     2483                                        ) {
     2484
     2485                                                // parsing_warnings.push([nrule, new_tokens[nrule][0].length - 1,
     2486                                                        // "This rule will be evaluated as closed but it was specified as additional rule."
     2487                                                        // + " It is enough to specify this rule as normal rule using the \";\" character."
     2488                                                        // + " See https://wiki.openstreetmap.org/wiki/Key:opening_hours:specification#explain:rule_modifier:closed."
     2489                                                // ]);
     2490                                        }
     2491                                        /* }}} */
     2492
     2493                                        /* Check for valid use of <separator_for_readability> {{{ */
     2494                                        for (var i = 0; i < used_selectors_types_array[nrule].length - 1; i++) {
     2495                                                var selector_type = used_selectors_types_array[nrule][i];
     2496                                                var next_selector_type = used_selectors_types_array[nrule][i+1];
     2497                                                if (   (   wide_range_selectors.indexOf(selector_type)       != -1
     2498                                                                && wide_range_selectors.indexOf(next_selector_type)  != -1
     2499                                                        ) || ( small_range_selectors.indexOf(selector_type)      != -1
     2500                                                                && small_range_selectors.indexOf(next_selector_type) != -1)
     2501                                                        ) {
     2502
     2503                                                        if (new_tokens[nrule][0][used_selectors[nrule][selector_type][0]][0] == ':') {
     2504                                                                parsing_warnings.push([nrule, used_selectors[nrule][selector_type][0],
     2505                                                                        "You have used the optional symbol <separator_for_readability> in the wrong place."
     2506                                                                        + " Please check the syntax specification to see where it could be used or remove it."
     2507                                                                ]);
     2508                                                        }
     2509                                                }
     2510                                        }
     2511                                        /* }}} */
     2512
     2513                                }
     2514
     2515                                /* Check if 24/7 is used and it does not mean 24/7 because there are other rules {{{ */
    21232516                                var has_advanced = it.advance();
    21242517
     
    21302523                                                        + ' e.g. "open; Mo 12:00-14:00 off".']);
    21312524                                }
     2525                                /* }}} */
     2526
     2527                                prettifyValue();
    21322528                        }
     2529                        done_with_warnings = true;
    21332530
    21342531                        var warnings = [];
     2532                        // FIXME: Sort based on parsing_warnings[1], tricky …
    21352533                        for (var i = 0; i < parsing_warnings.length; i++) {
    21362534                                warnings.push( formatWarnErrorMessage(parsing_warnings[i][0], parsing_warnings[i][1], parsing_warnings[i][2]) );
     
    21382536                        return warnings;
    21392537                }
     2538
     2539                /* Helpers for getWarnings {{{ */
     2540
     2541                /* Check if token is the begin of a selector and why. {{{
     2542                 *
     2543                 * :param tokens: List of token objects.
     2544                 * :param at: Position where to start.
     2545                 * :returns:
     2546                 *              * false the current token is not the begin of a selector.
     2547                 *              * Position in token array from where the decision was made that
     2548                 *                the token is the start of a selector.
     2549                 */
     2550                function tokenIsTheBeginOfSelector(tokens, at) {
     2551                        if (typeof tokens[at][3] == 'string') {
     2552                                return 3;
     2553                        } else if (tokens[at][1] == 'comment'
     2554                                        || tokens[at][1] == 'state'
     2555                                        || tokens[at][1] == '24/7'
     2556                                        || tokens[at][1] == 'rule separator'
     2557                                ){
     2558
     2559                                return 1;
     2560                        } else {
     2561                                return false;
     2562                        }
     2563                }
     2564                /* }}} */
     2565
     2566                /* Get start and end position of a selector. {{{
     2567                 * For example this value 'Mo-We,Fr' will return the position of the
     2568                 * token lexeme 'Mo' and 'Fr' e.g. there indexes [ 0, 4 ] in the
     2569                 * selector array of tokens.
     2570                 *
     2571                 * :param tokens: List of token objects.
     2572                 * :param at: Position where to start.
     2573                 * :returns: Array:
     2574                 *                      0. Index of first token in selector array of tokens.
     2575                 *                      1. Index of last token in selector array of tokens.
     2576                 *                      2. Selector type.
     2577                 */
     2578                function getSelectorRange(tokens, at) {
     2579                        var selector_start = at,
     2580                                selector_end,
     2581                                pos_in_token_array;
     2582
     2583                        for (; selector_start >= 0; selector_start--) {
     2584                                pos_in_token_array = tokenIsTheBeginOfSelector(tokens, selector_start);
     2585                                if (pos_in_token_array)
     2586                                        break;
     2587                        }
     2588                        selector_end = selector_start;
     2589
     2590                        if (pos_in_token_array === 1) {
     2591                                // Selector consists of a single token.
     2592
     2593                                // Include tailing colon.
     2594                                if (selector_end + 1 < tokens.length && tokens[selector_end + 1][0] == ':')
     2595                                        selector_end++;
     2596
     2597                                return [ selector_start, selector_end, tokens[selector_start][pos_in_token_array] ];
     2598                        }
     2599
     2600                        for (selector_end++; selector_end < tokens.length ; selector_end++) {
     2601                                if (tokenIsTheBeginOfSelector(tokens, selector_end))
     2602                                        return [ selector_start, selector_end - 1, tokens[selector_start][pos_in_token_array] ];
     2603                        }
     2604
     2605                        return [ selector_start, selector_end - 1, tokens[selector_start][pos_in_token_array] ];
     2606                }
     2607                /* }}} */
     2608                /* }}} */
     2609                /* }}} */
     2610
     2611                /* Prettify raw value from user. {{{
     2612                 * The value is generated by putting the tokens back together to a string.
     2613                 *
     2614                 * :param argument_hash: Hash which can contain:
     2615                 *              'conf': Configuration hash.
     2616                 *              'get_internals: If true export internal data structures.
     2617                 *              'rule_index: Only prettify the rule with this index.
     2618                 * :returns: Prettified value string or object if get_internals is true.
     2619                 */
     2620                function prettifyValue(argument_hash) {
     2621                        var user_conf = {},
     2622                                get_internals   = false,
     2623                                rule_index;
     2624                        if (typeof argument_hash != 'undefined') {
     2625
     2626                                if (typeof argument_hash.conf === 'object')
     2627                                        user_conf = argument_hash.conf;
     2628
     2629                                if (typeof argument_hash.rule_index === 'number')
     2630                                        rule_index = argument_hash.rule_index;
     2631
     2632                                if (argument_hash.get_internals === true)
     2633                                        get_internals = true;
     2634                        }
     2635
     2636                        for (var key in default_prettify_conf) {
     2637                                if (typeof user_conf[key] == 'undefined')
     2638                                        user_conf[key] = default_prettify_conf[key];
     2639                        }
     2640
     2641                        prettified_value = '';
     2642                        var prettified_value_array = [];
     2643
     2644                        for (var nrule = 0; nrule < new_tokens.length; nrule++) {
     2645                                if (new_tokens[nrule][0].length === 0) continue;
     2646                                // Rule does contain nothing useful e.g. second rule of '10:00-12:00;' (empty) which needs to be handled.
     2647
     2648                                if (typeof rule_index == 'number') {
     2649                                        if (rule_index != nrule) continue;
     2650                                } else {
     2651                                        if (nrule !== 0)
     2652                                                prettified_value += (
     2653                                                        new_tokens[nrule][1]
     2654                                                                ? user_conf.rule_sep_string + '|| '
     2655                                                                : (
     2656                                                                        new_tokens[nrule][0][0][1] == 'rule separator'
     2657                                                                        ? ','
     2658                                                                        : (
     2659                                                                                user_conf.print_semicolon
     2660                                                                                ? ';'
     2661                                                                                : ''
     2662                                                                        )
     2663                                                                ) +
     2664                                                        user_conf.rule_sep_string);
     2665                                }
     2666
     2667                                var selector_start_end_type = [ 0, 0, undefined ],
     2668                                        prettified_group_value = [];
     2669                                // console.log(new_tokens[nrule][0]);
     2670                                var count = 0;
     2671
     2672
     2673                                do {
     2674                                        selector_start_end_type = getSelectorRange(new_tokens[nrule][0], selector_start_end_type[1]);
     2675                                        // console.log(selector_start_end_type, new_tokens[nrule][0].length, count);
     2676
     2677                                        if (count > 50) {
     2678                                                throw formatLibraryBugMessage('infinite loop');
     2679                                        }
     2680
     2681                                        if (selector_start_end_type[2] != 'rule separator') {
     2682                                                prettified_group_value.push(
     2683                                                        [
     2684                                                                selector_start_end_type,
     2685                                                                prettifySelector(
     2686                                                                        new_tokens[nrule][0],
     2687                                                                        selector_start_end_type[0],
     2688                                                                        selector_start_end_type[1],
     2689                                                                        selector_start_end_type[2],
     2690                                                                        user_conf
     2691                                                                ),
     2692                                                        ]
     2693                                                );
     2694                                        }
     2695
     2696                                        selector_start_end_type[1]++;
     2697                                        count++;
     2698                                        // console.log(selector_start_end_type, new_tokens[nrule][0].length, count);
     2699                                } while (selector_start_end_type[1] < new_tokens[nrule][0].length);
     2700                                // console.log('Prettified value: ' + JSON.stringify(prettified_group_value, null, '    '));
     2701                                var not_sorted_prettified_group_value = prettified_group_value.slice();
     2702
     2703                                if (!done_with_selector_reordering) {
     2704                                        prettified_group_value.sort(
     2705                                                function (a, b) {
     2706                                                        var selector_order = [ 'year', 'month', 'week', 'holiday', 'weekday', 'time', '24/7', 'state', 'comment'];
     2707                                                        return selector_order.indexOf(a[0][2]) - selector_order.indexOf(b[0][2]);
     2708                                                }
     2709                                        );
     2710                                }
     2711                                var old_prettified_value_length = prettified_value.length;
     2712
     2713                                prettified_value += prettified_group_value.map(
     2714                                        function (array) {
     2715                                                return array[1];
     2716                                        }
     2717                                ).join(' ');
     2718
     2719                                prettified_value_array.push( prettified_group_value );
     2720
     2721                                if (!done_with_selector_reordering_warnings) {
     2722                                        for (var i = 0, l = not_sorted_prettified_group_value.length; i < l; i++) {
     2723                                                if (not_sorted_prettified_group_value[i] != prettified_group_value[i]) {
     2724                                                        // console.log(i + ': ' + prettified_group_value[i][0][2]);
     2725                                                        var length = i + old_prettified_value_length; // i: Number of spaces in string.
     2726                                                        for (var x = 0; x <= i; x++) {
     2727                                                                length += prettified_group_value[x][1].length;
     2728                                                                // console.log('Length: ' + length + ' ' + prettified_group_value[x][1]);
     2729                                                        }
     2730                                                        // console.log(length);
     2731                                                        parsing_warnings.push([ prettified_value, length,
     2732                                                                'The selector "' + prettified_group_value[i][0][2] + '" was switched with'
     2733                                                                + ' the selector "' + not_sorted_prettified_group_value[i][0][2] + '"'
     2734                                                                + ' for readablitity and compatibiltity reasons.'
     2735                                                        ]);
     2736                                                }
     2737                                        }
     2738                                }
     2739                        }
     2740
     2741                        done_with_selector_reordering_warnings = true;
     2742                        // console.log(JSON.stringify(prettified_value_array, null, '    '));
     2743
     2744                        if (get_internals) {
     2745                                return [ prettified_value_array, new_tokens ];
     2746                        } else {
     2747                                return prettified_value;
     2748                        }
     2749                }
    21402750                // }}}
    21412751
    2142                 /* Function to check token array for specific pattern {{{
     2752                /* Check selector array of tokens for specific token name pattern. {{{
    21432753                 *
    21442754                 * :param tokens: List of token objects.
     
    21732783                                        return res;
    21742784                                return [ res[0], new Date(res[1].getTime() - shift) ];
    2175                         }
     2785                        };
    21762786                }
    21772787                // }}}
     
    21802790                 *
    21812791                 * :param tokens: List of token objects.
    2182                  * :param at: Position at which the matching should begin.
     2792                 * :param at: Position where to start.
    21832793                 * :param selectors: Reference to selector object.
    2184                  * :param nblock: Block number starting with 0.
    2185                  * :param conf: Configuration for prettifyValue.
     2794                 * :param nrule: Rule number starting with 0.
    21862795                 * :returns: See selector code.
    21872796                 */
    2188                 function parseGroup(tokens, at, selectors, nblock, conf) {
    2189                         var prettified_group_value = '';
    2190                         used_subparsers = { 'time ranges': [ ] };
     2797                function parseGroup(tokens, at, selectors, nrule) {
     2798                        var rule_modifier_specified = false;
    21912799
    21922800                        // console.log(tokens); // useful for debugging of tokenize
    21932801                        while (at < tokens.length) {
    2194                                 var old_at = at;
    21952802                                // console.log('Parsing at position', at +':', tokens[at]);
    21962803                                if (matchTokens(tokens, at, 'weekday')) {
     
    22122819                                                || matchTokens(tokens, at, 'year', 'event')
    22132820                                                || matchTokens(tokens, at, 'event')) {
    2214                                         at = parseMonthdayRange(tokens, at, nblock);
     2821
     2822                                        at = parseMonthdayRange(tokens, at, nrule);
    22152823                                        week_stable = false;
    22162824                                } else if (matchTokens(tokens, at, 'year')) {
     
    22192827                                } else if (matchTokens(tokens, at, 'month')) {
    22202828                                        at = parseMonthRange(tokens, at);
    2221                                         // week_stable = false; // decided based on actual values
     2829                                        // week_stable = false; // Decided based on the actual value/tokens.
    22222830                                } else if (matchTokens(tokens, at, 'week')) {
    2223                                         at = parseWeekRange(tokens, at + 1);
    2224                                         week_stable = false;
    2225 
    2226                                         // if (prettified_group_value[-1] != ' ')
    2227                                         //      prettified_group_value = prettified_group_value.substring(0, prettified_group_value.length - 1);
    2228                                 } else if (at != 0 && at != tokens.length - 1 && tokens[at][0] == ':') {
    2229                                         // Ignore colon if they appear somewhere else than as time separator.
    2230                                         // Except the start or end of the value.
    2231                                         // This provides compatibility with the syntax proposed by Netzwolf:
    2232                                         // http://wiki.openstreetmap.org/wiki/Key:opening_hours:specification
    2233                                         if (!done_with_warnings && (matchTokens(tokens, at-1, 'weekday') || matchTokens(tokens, at-1, 'holiday')))
    2234                                                 parsing_warnings.push([nblock, at, 'Please don’t use ":" after ' + tokens[at-1][1] + '.']);
    2235 
    2236                                         if (prettified_group_value[-1] != ' ')
    2237                                                 prettified_group_value = prettified_group_value.substring(0, prettified_group_value.length - 1);
     2831                                        tokens[at][3] = 'week';
     2832                                        at = parseWeekRange(tokens, at);
     2833
     2834                                } else if (at !== 0 && at != tokens.length - 1 && tokens[at][0] == ':') {
     2835                                        /* Ignore colon if they appear somewhere else than as time separator.
     2836                                         * Except the start or end of the value.
     2837                                         * This provides compatibility with the syntax proposed by Netzwolf:
     2838                                         * http://wiki.openstreetmap.org/wiki/Key:opening_hours:specification#separator_for_readability
     2839                                         * Check for valid use of <separator_for_readability> is implemented in function getWarnings().
     2840                                         */
     2841
     2842                                        if (!done_with_warnings && matchTokens(tokens, at-1, 'holiday'))
     2843                                                parsing_warnings.push([nrule, at, 'Please don’t use ":" after ' + tokens[at-1][1] + '.']);
     2844
    22382845                                        at++;
    22392846                                } else if (matchTokens(tokens, at, 'number', 'timesep')
     
    22412848                                                || matchTokens(tokens, at, '(', 'timevar')
    22422849                                                || matchTokens(tokens, at, 'number', '-')) {
     2850
    22432851                                        at = parseTimeRange(tokens, at, selectors, false);
    22442852
    2245                                         used_subparsers['time ranges'].push(at);
    2246                                 } else if (matchTokens(tokens, at, 'closed')) {
    2247                                         selectors.meaning = false;
     2853                                } else if (matchTokens(tokens, at, 'state')) {
     2854
     2855                                        if (tokens[at][0] == 'open') {
     2856                                                selectors.meaning = true;
     2857                                        } else if (tokens[at][0] == 'closed' || tokens[at][0] == 'off') {
     2858                                                selectors.meaning = false;
     2859                                        } else {
     2860                                                selectors.meaning = false;
     2861                                                selectors.unknown = true;
     2862                                        }
     2863
     2864                                        rule_modifier_specified = true;
    22482865                                        at++;
    2249                                         if (matchTokens(tokens, at, ',')) // additional block
     2866                                        if (typeof tokens[at] == 'object' && tokens[at][0] == ',') // additional rule
    22502867                                                at = [ at + 1 ];
    22512868
    2252                                         if (typeof used_subparsers['state keywords'] != 'object')
    2253                                                 used_subparsers['state keywords'] = [ at ];
    2254                                         else
    2255                                                 used_subparsers['state keywords'].push(at);
    2256                                 } else if (matchTokens(tokens, at, 'open')) {
    2257                                         selectors.meaning = true;
    2258                                         at++;
    2259                                         if (matchTokens(tokens, at, ',')) // additional block
    2260                                                 at = [ at + 1 ];
    2261 
    2262                                         if (typeof used_subparsers['state keywords'] != 'object')
    2263                                                 used_subparsers['state keywords'] = [ at ];
    2264                                         else
    2265                                                 used_subparsers['state keywords'].push(at);
    2266                                 } else if (matchTokens(tokens, at, 'unknown')) {
    2267                                         selectors.meaning = false;
    2268                                         selectors.unknown = true;
    2269                                         at++;
    2270                                         if (matchTokens(tokens, at, ',')) // additional block
    2271                                                 at = [ at + 1 ];
    2272 
    2273                                         if (typeof used_subparsers['state keywords'] != 'object')
    2274                                                 used_subparsers['state keywords'] = [ at ];
    2275                                         else
    2276                                                 used_subparsers['state keywords'].push(at);
    22772869                                } else if (matchTokens(tokens, at, 'comment')) {
    22782870                                        selectors.comment = tokens[at][0];
    2279                                         if (at > 0) {
    2280                                                 if (!matchTokens(tokens, at - 1, 'open')
    2281                                                         && !matchTokens(tokens, at - 1, 'closed')) {
    2282                                                         // Then it is unknown. Either with unknown explicitly
    2283                                                         // specified or just a comment behind.
    2284                                                         selectors.meaning = false;
    2285                                                         selectors.unknown = true;
    2286                                                 }
    2287                                         } else { // block starts with comment
    2288                                                 selectors.time.push(function(date) { return [true]; });
    2289                                                 // Not needed. If there is no selector it automatically matches everything.
    2290                                                 // WRONG: This only works if there is no other selector in this selector group ...
     2871                                        if (!rule_modifier_specified) {
     2872                                                // Then it is unknown. Either with unknown explicitly
     2873                                                // specified or just a comment.
    22912874                                                selectors.meaning = false;
    22922875                                                selectors.unknown = true;
    22932876                                        }
     2877
     2878                                        rule_modifier_specified = true;
    22942879                                        at++;
    2295                                         if (matchTokens(tokens, at, ',')) // additional block
     2880                                        if (typeof tokens[at] == 'object' && tokens[at][0] == ',') // additional rule
    22962881                                                at = [ at + 1 ];
    2297 
    2298                                         if (typeof used_subparsers['comments'] != 'object')
    2299                                                 used_subparsers['comments'] = [ at ];
    2300                                         else
    2301                                                 used_subparsers['comments'].push(at);
     2882                                } else if ((at === 0 || at == tokens.length - 1) && matchTokens(tokens, at, 'rule separator')) {
     2883                                        at++;
     2884                                        console.log("value: " + nrule);
     2885                                        // throw formatLibraryBugMessage('Not implemented yet.');
    23022886                                } else {
    23032887                                        var warnings = getWarnings();
    2304                                         throw formatWarnErrorMessage(nblock, at, 'Unexpected token: "' + tokens[at][1]
     2888                                        throw formatWarnErrorMessage(nrule, at, 'Unexpected token: "' + tokens[at][1]
    23052889                                                + '" This means that the syntax is not valid at that point or it is currently not supported.')
    23062890                                                + (warnings ? ' ' + warnings.join('; ') : '');
    23072891                                }
    23082892
    2309                                 if (typeof conf != 'undefined') {
    2310                                         // 'Mo: 12:00-13:00' -> 'Mo 12:00-13:00'
    2311                                         if (used_subparsers['time ranges'] && old_at > 1 && tokens[old_at-1][0] == ':'
    2312                                                         && matchTokens(tokens, old_at - 2, 'weekday'))
    2313                                                 prettified_group_value = prettified_group_value.substring(0, prettified_group_value.length - 2) + ' ';
    2314 
    2315                                         // 'week 1, week 3' -> 'week 1,week 3'
    2316                                         if (prettified_group_value.substr(prettified_group_value.length -2, 2) == ', '
    2317                                                         && matchTokens(tokens, old_at, 'week'))
    2318                                                 prettified_group_value = prettified_group_value.substring(0, prettified_group_value.length - 1);
    2319 
    2320                                         prettified_group_value += prettifySelector(tokens, old_at, at, conf, used_subparsers['time ranges'].length);
    2321                                 }
    2322 
    2323                                 if (typeof at == 'object') // additional block
     2893                                if (typeof at == 'object') { // additional rule
     2894                                        tokens[at[0] - 1][1] = 'rule separator';
    23242895                                        break;
    2325                         }
    2326 
    2327                         prettified_value += prettified_group_value.replace(/\s+$/, '');
    2328 
    2329                         if (!done_with_warnings) {
    2330                                 for (var subparser_name in used_subparsers) {
    2331                                         if (used_subparsers[subparser_name].length > 1) {
    2332                                                 parsing_warnings.push([nblock, used_subparsers[subparser_name][used_subparsers[subparser_name].length - 1] - 1,
    2333                                                         'You have used ' + used_subparsers[subparser_name].length
    2334                                                         + (subparser_name.match(/^(?:comments|state keywords)/) ?
    2335                                                                         ' ' + subparser_name + ' in one rule.'
    2336                                                                         + ' You may only use one in one rule.'
    2337                                                                 :
    2338                                                                         ' not connected ' + subparser_name + ' in one rule.'
    2339                                                                         + ' This is probably an error.'
    2340                                                                         + ' Equal selector types can (and should) always be written in conjunction separated by comma or something.'
    2341                                                                         + ' Example for time ranges "12:00-13:00,15:00-18:00".'
    2342                                                                         + ' Example for weekdays "Mo-We,Fr".'
    2343                                                          )
    2344                                                         + ' Rules can be separated by ";".' ]
    2345                                                 );
    2346                                         }
    23472896                                }
    23482897                        }
    23492898
    23502899                        return at;
     2900                }
     2901
     2902                function get_last_token_pos_in_token_group(tokens, at, last_at) {
     2903                        for (at++; at < last_at; at++) {
     2904                                if (typeof tokens[at] != 'undefined') {
     2905                                        if (typeof tokens[at][3] == 'string'
     2906                                                        || tokens[at][1] == 'comment'
     2907                                                        || tokens[at][1] == 'state'){
     2908
     2909                                                        return at - 1;
     2910                                        }
     2911                                }
     2912                        }
     2913                        return last_at;
    23512914                }
    23522915                // }}}
     
    23702933                 *
    23712934                 * :param date: Date object.
    2372                  * :param day: Integer number for day of week. Starting with zero (Sunday).
     2935                 * :param weekday: Integer number for day of week. Starting with zero (Sunday).
    23732936                 * :returns: Moved date object.
    23742937                 */
    2375                 function dateAtNextWeekday(date, day) {
    2376                         var delta = day - date.getDay();
     2938                function dateAtNextWeekday(date, weekday) {
     2939                        var delta = weekday - date.getDay();
    23772940                        return new Date(date.getFullYear(), date.getMonth(), date.getDate() + delta + (delta < 0 ? 7 : 0));
    23782941                }
     
    24212984                                        // Negative number
    24222985                                        func(-tokens[at+1][0], -tokens[at+1][0], at);
    2423                                         at += 2
     2986                                        at += 2;
    24242987                                } else if (matchTokens(tokens, at, 'number')) {
    24252988                                        // Single number
     
    24272990                                        at++;
    24282991                                } else {
    2429                                         throw formatWarnErrorMessage(nblock, at + matchTokens(tokens, at, '-'),
     2992                                        throw formatWarnErrorMessage(nrule, at + matchTokens(tokens, at, '-'),
    24302993                                                'Unexpected token in number range: ' + tokens[at][1]);
    24312994                                }
     
    24533016
    24543017                                // bad number
    2455                                 if (from == 0 || from < -5 || from > 5)
    2456                                         throw formatWarnErrorMessage(nblock, at,
     3018                                if (from === 0 || from < -5 || from > 5)
     3019                                        throw formatWarnErrorMessage(nrule, at,
    24573020                                                'Number between -5 and 5 (except 0) expected');
    24583021
    24593022                                if (from == to) {
    2460                                         if (number != 0)
    2461                                                 throw formatWarnErrorMessage(nblock, at,
     3023                                        if (number !== 0)
     3024                                                throw formatWarnErrorMessage(nrule, at,
    24623025                                                        'You can not use more than one constrained weekday in a month range');
    24633026                                        number = from;
    24643027                                } else {
    2465                                         throw formatWarnErrorMessage(nblock, at+2,
     3028                                        throw formatWarnErrorMessage(nrule, at+2,
    24663029                                                'You can not use a range of constrained weekdays in a month range');
    24673030                                }
     
    24693032
    24703033                        if (!matchTokens(tokens, endat, ']'))
    2471                                 throw formatWarnErrorMessage(nblock, endat, '"]" expected.');
     3034                                throw formatWarnErrorMessage(nrule, endat, '"]" expected.');
    24723035
    24733036                        return [ number, endat + 1 ];
     
    24763039
    24773040                // Check if period is ok. Period 0 or 1 don’t make much sense.
    2478                 /* List parser for constrained weekdays in month range {{{
    2479                  * e.g. Su[-1] which selects the last Sunday of the month.
    2480                  *
    2481                  * :param tokens: List of token objects.
    2482                  * :param at: Position where to start.
    2483                  * :returns: Array:
    2484                  *                      0. Constrained weekday number.
    2485                  *                      1. Position at which the token does not belong to the list any more (after ']' token).
    2486                  */
    24873041                function checkPeriod(at, period, period_type, parm_string) {
    24883042                        if (done_with_warnings)
     
    24903044
    24913045                        if (period === 0) {
    2492                                 throw formatWarnErrorMessage(nblock, at,
     3046                                throw formatWarnErrorMessage(nrule, at,
    24933047                                        'You can not use '+ period_type +' ranges with period equals zero.');
    24943048                        } else if (period === 1) {
    24953049                                if (typeof parm_string == 'string' && parm_string == 'no_end_year')
    2496                                         parsing_warnings.push([nblock, at,
     3050                                        parsing_warnings.push([nrule, at,
    24973051                                                'Please don’t use '+ period_type +' ranges with period equals one.'
    24983052                                                + ' If you want to express that a facility is open starting from a year without limit use "<year>+".']);
    24993053                                else
    2500                                         parsing_warnings.push([nblock, at,
     3054                                        parsing_warnings.push([nrule, at,
    25013055                                                'Please don’t use '+ period_type +' ranges with period equals one.']);
    25023056                        }
    25033057                }
    25043058
     3059                /* Get date moved to constrained weekday (and moved for add_days. {{{
     3060                 * E.g. used for 'Aug Su[-1] -1 day'.
     3061                 *
     3062                 * :param year: Year as integer.
     3063                 * :param month: Month as integer starting with zero.
     3064                 * :param weekday: Integer number for day of week. Starting with zero (Sunday).
     3065                 * :param constrained_weekday: Position where to start.
     3066                 * :returns: Date object.
     3067                 */
    25053068                function getDateForConstrainedWeekday(year, month, weekday, constrained_weekday, add_days) {
    25063069                        var tmp_date = dateAtNextWeekday(
     
    25143077                        return tmp_date;
    25153078                }
    2516 
    2517                 function formatWarnErrorMessage(nblock, at, message) {
    2518                         var pos = 0;
    2519                         if (nblock == -1) { // Usage of block index not required because we do have access to value.length.
    2520                                 pos = value.length - at;
    2521                         } else { // Issue accrued at a later time, position in string needs to be reconstructed.
    2522                                 if (typeof tokens[nblock][0][at] == 'undefined') {
    2523                                         pos = value.length;
    2524                                         if (typeof tokens[nblock][0][tokens[nblock][0].length-1] != 'undefined') {
    2525                                                 // pos -= tokens[nblock][0][tokens[nblock][0].length-1][2];
    2526                                                 console.warn("FIXME");
    2527                                         }
    2528                                 } else {
    2529                                         pos = value.length;
    2530                                         if (typeof tokens[nblock][0][at+1] != 'undefined') {
    2531                                                 pos -= tokens[nblock][0][at+1][2];
    2532                                         } else if (typeof tokens[nblock][2] != 'undefined') {
    2533                                                 pos -= tokens[nblock][2];
    2534                                         } else {
    2535                                         }
    2536                                 }
    2537                         }
    2538                         return value.substring(0, pos) + ' <--- (' + message + ')';
    2539                 }
    2540 
    2541                 // check if date is valid
    2542                 function isValidDate(month, day, nblock, at) {
    2543                         // month == 0 is Jan
    2544 
    2545                         // May use this instead. Does not say, what is wrong as good was implementation below.
     3079                // }}}
     3080
     3081                /* Check if date is valid. {{{
     3082                 *
     3083                 * :param month: Month as integer starting with zero.
     3084                 * :param date: Day of month as integer.
     3085                 * :returns: undefined. There is no real return value. This function just throws an exception if something is wrong.
     3086                 */
     3087                function checkIfDateIsValid(month, day, nrule, at) {
     3088                        // May use this instead. The problem is that this does not give feedback as precise as the code which is used in this function.
    25463089                        // var testDate = new Date(year, month, day);
    25473090                        // if (testDate.getDate() != day || testDate.getMonth() != month || testDate.getFullYear() != year) {
     
    25513094                        // https://en.wikipedia.org/wiki/Month#Julian_and_Gregorian_calendars
    25523095                        if (day < 1 || day > 31)
    2553                                 throw formatWarnErrorMessage(nblock, at, 'Day must be between 1 and 31.');
     3096                                throw formatWarnErrorMessage(nrule, at, 'Day must be between 1 and 31.');
    25543097                        if ((month==3 || month==5 || month==8 || month==10) && day==31)
    2555                                 throw formatWarnErrorMessage(nblock, at, 'Month ' + months[month] + " doesn't have 31 days.!");
     3098                                throw formatWarnErrorMessage(nrule, at, 'Month ' + months[month] + " doesn't have 31 days.!");
    25563099                        if (month == 1 && day == 30)
    2557                                 throw formatWarnErrorMessage(nblock, at, 'Month ' + months[1]+ " either has 28 or 29 days (leap years).");
     3100                                throw formatWarnErrorMessage(nrule, at, 'Month ' + months[1]+ " either has 28 or 29 days (leap years).");
    25583101                }
    25593102                // }}}
    2560 
    2561                 // Time range parser (10:00-12:00,14:00-16:00) {{{
    2562                 //
    2563                 // extended_open_end: <time> - <time> +
    2564                 //                 at is here A (if extended_open_end is true)
     3103                // }}}
     3104
     3105                /* Time range parser (10:00-12:00,14:00-16:00) {{{
     3106                 *
     3107                 * :param tokens: List of token objects.
     3108                 * :param at: Position where to start.
     3109                 * :param selectors: Reference to selector object.
     3110                 * :param extended_open_end: Used for combined time range with open end.
     3111                 * extended_open_end: <time> - <time> +
     3112                 *            param at is here A (if extended_open_end is true)
     3113                 * :returns: Position at which the token does not belong to the selector anymore.
     3114                 */
    25653115                function parseTimeRange(tokens, at, selectors, extended_open_end) {
     3116                        if (!extended_open_end)
     3117                                tokens[at][3] = 'time';
     3118
    25663119                        for (; at < tokens.length; at++) {
    25673120                                var has_time_var_calc = [], has_normal_time = []; // element 0: start time, 1: end time
    2568                                 has_normal_time[0] = matchTokens(tokens, at, 'number', 'timesep', 'number');
    2569                                 has_time_var_calc[0] = matchTokens(tokens, at, '(', 'timevar');
     3121                                        has_normal_time[0] = matchTokens(tokens, at, 'number', 'timesep', 'number');
     3122                                        has_time_var_calc[0] = matchTokens(tokens, at, '(', 'timevar');
     3123                                var minutes_from,
     3124                                        minutes_to;
    25703125                                if (has_normal_time[0] || matchTokens(tokens, at, 'timevar') || has_time_var_calc[0]) {
    25713126                                        // relying on the fact that always *one* of them is true
     
    25753130                                        var timevar_add      = [ 0, 0 ];
    25763131                                        var timevar_string   = [];    // capture timevar string like 'sunrise' to calculate it for the current date.
     3132                                        var point_in_time_period;
    25773133
    25783134                                        // minutes_from
    25793135                                        if (has_normal_time[0]) {
    2580                                                 var minutes_from = getMinutesByHoursMinutes(tokens, nblock, at+has_time_var_calc[0]);
     3136                                                minutes_from = getMinutesByHoursMinutes(tokens, nrule, at+has_time_var_calc[0]);
    25813137                                        } else {
    25823138                                                timevar_string[0] = tokens[at+has_time_var_calc[0]][0];
    2583                                                 var minutes_from = word_value_replacement[timevar_string[0]];
     3139                                                minutes_from = word_value_replacement[timevar_string[0]];
    25843140
    25853141                                                if (has_time_var_calc[0]) {
     
    25943150                                                        has_open_end = true;
    25953151                                                } else {
    2596                                                         if (oh_mode == 0) {
    2597                                                                 throw formatWarnErrorMessage(nblock, at+(
     3152                                                        if (oh_mode === 0) {
     3153                                                                throw formatWarnErrorMessage(nrule,
     3154                                                                        at+(
    25983155                                                                                has_normal_time[0] ? (
    2599                                                                                                 typeof tokens[at+3] == 'object' ? 3 : 2
    2600                                                                                         ) : (
    2601                                                                                                 has_time_var_calc[0] ? 2 : 1
    2602                                                                                         )
    2603                                                                                 ),
     3156                                                                                        typeof tokens[at+3] == 'object' ? 3 : 2
     3157                                                                                ) : (
     3158                                                                                        has_time_var_calc[0] ? 2 : (
     3159                                                                                                        typeof tokens[at+1] != 'undefined' ? 1 : 0
     3160                                                                                                )
     3161                                                                                )
     3162                                                                        ),
    26043163                                                                        'hyphen (-) or open end (+) in time range '
    26053164                                                                        + (has_time_var_calc[0] ? 'calculation ' : '') + 'expected.'
    2606                                                                         + ' For working with points in time, the mode for opening_hours.js has to be altered.'
     3165                                                                        + ' For working with points in time, the mode for ' + library_name + ' has to be altered.'
    26073166                                                                        + ' Maybe wrong tag?');
    26083167                                                        } else {
    2609                                                                 var minutes_to = minutes_from + 1;
     3168                                                                minutes_to = minutes_from + 1;
    26103169                                                                is_point_in_time = true;
    26113170                                                        }
     
    26153174                                        // minutes_to
    26163175                                        if (has_open_end) {
     3176                                                if (extended_open_end === 1)
     3177                                                        minutes_from += minutes_in_day;
    26173178                                                if (minutes_from >= 22 * 60)
    2618                                                         var minutes_to = minutes_from +  8 * 60;
     3179                                                        minutes_to = minutes_from +  8 * 60;
    26193180                                                else if (minutes_from >= 17 * 60)
    2620                                                         var minutes_to = minutes_from + 10 * 60;
     3181                                                        minutes_to = minutes_from + 10 * 60;
    26213182                                                else
    2622                                                         var minutes_to = minutes_in_day;
     3183                                                        minutes_to = minutes_in_day;
    26233184                                        } else if (!is_point_in_time) {
    26243185                                                has_normal_time[1] = matchTokens(tokens, at_end_time, 'number', 'timesep', 'number');
    26253186                                                has_time_var_calc[1]      = matchTokens(tokens, at_end_time, '(', 'timevar');
    26263187                                                if (!has_normal_time[1] && !matchTokens(tokens, at_end_time, 'timevar') && !has_time_var_calc[1]) {
    2627                                                         throw formatWarnErrorMessage(nblock, at_end_time, 'time range does not continue as expected');
     3188                                                        throw formatWarnErrorMessage(nrule, at_end_time - (typeof tokens[at_end_time] != 'undefined' ? 0 : 1),
     3189                                                                        'Time range does not continue as expected');
    26283190                                                } else {
    26293191                                                        if (has_normal_time[1]) {
    2630                                                                 var minutes_to = getMinutesByHoursMinutes(tokens, nblock, at_end_time);
     3192                                                                minutes_to = getMinutesByHoursMinutes(tokens, nrule, at_end_time);
    26313193                                                        } else {
    2632                                                                 timevar_string[1] = tokens[at_end_time+has_time_var_calc[1]][0]
    2633                                                                 var minutes_to = word_value_replacement[timevar_string[1]];
     3194                                                                timevar_string[1] = tokens[at_end_time+has_time_var_calc[1]][0];
     3195                                                                minutes_to = word_value_replacement[timevar_string[1]];
    26343196                                                        }
    26353197
     
    26473209                                        if (matchTokens(tokens, at, '/', 'number')) {
    26483210                                                if (matchTokens(tokens, at + 2, 'timesep', 'number')) { // /hours:minutes
    2649                                                         var point_in_time_period = getMinutesByHoursMinutes(tokens, nblock, at + 1);
     3211                                                        point_in_time_period = getMinutesByHoursMinutes(tokens, nrule, at + 1);
    26503212                                                        at += 4;
    26513213                                                } else { // /minutes
    2652                                                         var point_in_time_period = tokens[at + 1][0];
     3214                                                        point_in_time_period = tokens[at + 1][0];
    26533215                                                        at += 2;
    26543216                                                        if (matchTokens(tokens, at, 'timesep'))
    2655                                                                 throw formatWarnErrorMessage(nblock, at,
     3217                                                                throw formatWarnErrorMessage(nrule, at,
    26563218                                                                        'Time period does not continue as expected. Exampe "/01:30".');
    26573219                                                }
    26583220
    2659                                                 if (oh_mode == 0)
    2660                                                         throw formatWarnErrorMessage(nblock, at - 1,
     3221                                                // Check at this later state in the if condition to get the correct position.
     3222                                                if (oh_mode === 0)
     3223                                                        throw formatWarnErrorMessage(nrule, at - 1,
    26613224                                                                'opening_hours is running in "time range mode". Found point in time.');
    26623225
    26633226                                                is_point_in_time = true;
    26643227                                        } else if (matchTokens(tokens, at, '+')) {
    2665                                                 parseTimeRange(tokens, at_end_time, selectors, true);
     3228                                                parseTimeRange(tokens, at_end_time, selectors, minutes_to < minutes_from ? 1 : true);
    26663229                                                at++;
    26673230                                        } else if (oh_mode == 1 && !is_point_in_time) {
    2668                                                 throw formatWarnErrorMessage(nblock, at_end_time,
     3231                                                throw formatWarnErrorMessage(nrule, at_end_time,
    26693232                                                        'opening_hours is running in "points in time mode". Found time range.');
    26703233                                        }
     
    26773240                                        }
    26783241
    2679                                         // normalize minutes into range
     3242                                        // Normalize minutes into range.
    26803243                                        if (!extended_open_end && minutes_from >= minutes_in_day)
    2681                                                 throw formatWarnErrorMessage(nblock, at_end_time - 1,
     3244                                                throw formatWarnErrorMessage(nrule, at_end_time - 2,
    26823245                                                        'Time range starts outside of the current day');
    26833246                                        if (minutes_to < minutes_from || ((has_normal_time[0] && has_normal_time[1]) && minutes_from == minutes_to))
    26843247                                                minutes_to += minutes_in_day;
    26853248                                        if (minutes_to > minutes_in_day * 2)
    2686                                                 throw formatWarnErrorMessage(nblock, at_end_time + (has_normal_time[1] ? 4 : (has_time_var_calc[1] ? 7 : 1)) - 2,
     3249                                                throw formatWarnErrorMessage(nrule, at_end_time + (has_normal_time[1] ? 4 : (has_time_var_calc[1] ? 7 : 1)) - 2,
    26873250                                                        'Time spanning more than two midnights not supported');
    26883251
    2689                                         // this shortcut makes always-open range check faster
    2690                                         if (!(minutes_from == 0 && minutes_to == minutes_in_day)) {
     3252                                        // This shortcut makes always-open range check faster.
     3253                                        if (minutes_from === 0 && minutes_to == minutes_in_day) {
     3254                                                selectors.time.push(function(date) { return [true]; });
     3255                                        } else {
    26913256                                                if (minutes_to > minutes_in_day) { // has_normal_time[1] must be true
    2692                                                         selectors.time.push(function(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period) { return function(date) {
     3257                                                        selectors.time.push(function(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period, extended_open_end) { return function(date) {
    26933258                                                                var ourminutes = date.getHours() * 60 + date.getMinutes();
    26943259
    26953260                                                                if (timevar_string[0]) {
    2696                                                                         var date_from = eval('SunCalc.getTimes(date, lat, lon).' + timevar_string[0]);
     3261                                                                        var date_from = SunCalc.getTimes(date, lat, lon)[timevar_string[0]];
    26973262                                                                        minutes_from  = date_from.getHours() * 60 + date_from.getMinutes() + timevar_add[0];
    26983263                                                                }
    26993264                                                                if (timevar_string[1]) {
    2700                                                                         var date_to = eval('SunCalc.getTimes(date, lat, lon).' + timevar_string[1]);
     3265                                                                        var date_to = SunCalc.getTimes(date, lat, lon)[timevar_string[1]];
    27013266                                                                        minutes_to  = date_to.getHours() * 60 + date_to.getMinutes() + timevar_add[1];
    27023267                                                                        minutes_to += minutes_in_day;
     
    27253290                                                                                return [false, dateAtDayMinutes(date, minutes_from)];
    27263291                                                                        else
    2727                                                                                 return [true, dateAtDayMinutes(date, minutes_to), has_open_end];
     3292                                                                                return [true, dateAtDayMinutes(date, minutes_to), has_open_end, extended_open_end];
    27283293                                                                }
    2729                                                         }}(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period));
    2730 
    2731                                                         selectors.wraptime.push(function(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period) { return function(date) {
     3294                                                        }}(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period, extended_open_end));
     3295
     3296                                                        selectors.wraptime.push(function(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period, extended_open_end) { return function(date) {
    27323297                                                                var ourminutes = date.getHours() * 60 + date.getMinutes();
    27333298
    27343299                                                                if (timevar_string[0]) {
    2735                                                                         var date_from = eval('SunCalc.getTimes(date, lat, lon).' + timevar_string[0]);
     3300                                                                        var date_from = SunCalc.getTimes(date, lat, lon)[timevar_string[0]];
    27363301                                                                        minutes_from  = date_from.getHours() * 60 + date_from.getMinutes() + timevar_add[0];
    27373302                                                                }
    27383303                                                                if (timevar_string[1]) {
    2739                                                                         var date_to = eval('SunCalc.getTimes(date, lat, lon).' + timevar_string[1]);
     3304                                                                        var date_to = SunCalc.getTimes(date, lat, lon)[timevar_string[1]];
    27403305                                                                        minutes_to  = date_to.getHours() * 60 + date_to.getMinutes() + timevar_add[1];
    27413306                                                                        // minutes_in_day does not need to be added.
     
    27583323                                                                } else {
    27593324                                                                        if (ourminutes < minutes_to)
    2760                                                                                 return [true, dateAtDayMinutes(date, minutes_to), has_open_end];
     3325                                                                                return [true, dateAtDayMinutes(date, minutes_to), has_open_end, extended_open_end];
    27613326                                                                }
    27623327                                                                return [false, undefined];
    2763                                                         }}(minutes_from, minutes_to - minutes_in_day, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period));
     3328                                                        }}(minutes_from, minutes_to - minutes_in_day, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period, extended_open_end));
    27643329                                                } else {
    2765                                                         selectors.time.push(function(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period) { return function(date) {
     3330                                                        selectors.time.push(function(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period, extended_open_end) { return function(date) {
    27663331                                                                var ourminutes = date.getHours() * 60 + date.getMinutes();
    27673332
    27683333                                                                if (timevar_string[0]) {
    2769                                                                         var date_from = eval('SunCalc.getTimes(date, lat, lon).' + timevar_string[0]);
     3334                                                                        var date_from = SunCalc.getTimes(date, lat, lon)[timevar_string[0]];
    27703335                                                                        minutes_from  = date_from.getHours() * 60 + date_from.getMinutes() + timevar_add[0];
    27713336                                                                }
    27723337                                                                if (timevar_string[1]) {
    2773                                                                         var date_to = eval('SunCalc.getTimes(date, lat, lon).' + timevar_string[1]);
     3338                                                                        var date_to = SunCalc.getTimes(date, lat, lon)[timevar_string[1]];
    27743339                                                                        minutes_to  = date_to.getHours() * 60 + date_to.getMinutes() + timevar_add[1];
    27753340                                                                } else if (is_point_in_time && typeof point_in_time_period != 'number') {
     
    27983363                                                                                return [false, dateAtDayMinutes(date, minutes_from + minutes_in_day)];
    27993364                                                                }
    2800                                                         }}(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period));
     3365                                                        }}(minutes_from, minutes_to, timevar_string, timevar_add, has_open_end, is_point_in_time, point_in_time_period, extended_open_end));
    28013366                                                }
    2802                                         } else {
    2803                                                 selectors.time.push(function(date) { return [true]; });
    28043367                                        }
    28053368
    28063369                                } else if (matchTokens(tokens, at, 'number', '-', 'number')) { // "Mo 09-18" (Please don’t use this) -> "Mo 09:00-18:00".
    2807                                         var minutes_from = tokens[at][0]   * 60;
    2808                                         var minutes_to   = tokens[at+2][0] * 60;
     3370                                        minutes_from = tokens[at][0]   * 60;
     3371                                        minutes_to   = tokens[at+2][0] * 60;
    28093372                                        if (!done_with_warnings)
    2810                                                 parsing_warnings.push([nblock, at + 2,
    2811                                                         'Time range without minutes specified. Not very explicit! Please use this syntax instead e.g. "12:00-14:00".']);
     3373                                                parsing_warnings.push([nrule, at + 2,
     3374                                                        'Time range without minutes specified. Not very explicit!'
     3375                                                        + ' Please use this syntax instead "'
     3376                                                        + (tokens[at][0]   < 10 ? '0' : '') + tokens[at][0]   + ':00-'
     3377                                                        + (tokens[at+2][0] < 10 ? '0' : '') + tokens[at+2][0] + ':00".']);
    28123378
    28133379                                        if (minutes_from >= minutes_in_day)
    2814                                                 throw formatWarnErrorMessage(nblock, at,
     3380                                                throw formatWarnErrorMessage(nrule, at,
    28153381                                                        'Time range starts outside of the current day');
    28163382                                        if (minutes_to < minutes_from)
    28173383                                                minutes_to += minutes_in_day;
    28183384                                        if (minutes_to > minutes_in_day * 2)
    2819                                                 throw formatWarnErrorMessage(nblock, at + 2,
     3385                                                throw formatWarnErrorMessage(nrule, at + 2,
    28203386                                                        'Time spanning more than two midnights not supported');
    28213387
     
    28523418
    28533419                                        at += 3;
    2854                                 } else { // additional block
     3420                                } else { // additional rule
    28553421                                        if (matchTokens(tokens, at, '('))
    2856                                                 throw formatWarnErrorMessage(nblock, at, 'Missing variable time (e.g. sunrise) after: "' + tokens[at][1] + '"');
     3422                                                throw formatWarnErrorMessage(nrule, at, 'Missing variable time (e.g. sunrise) after: "' + tokens[at][1] + '"');
    28573423                                        if (matchTokens(tokens, at, 'number', 'timesep'))
    2858                                                 throw formatWarnErrorMessage(nblock, at+2, 'Missing minutes in time range after: "' + tokens[at+1][1] + '"');
     3424                                                throw formatWarnErrorMessage(nrule, at+1, 'Missing minutes in time range after: "' + tokens[at+1][1] + '"');
    28593425                                        if (matchTokens(tokens, at, 'number'))
    2860                                                 throw formatWarnErrorMessage(nblock, at+2, 'Missing time seperator in time range after: "' + tokens[at][1] + '"');
     3426                                                throw formatWarnErrorMessage(nrule, at + (typeof tokens[at+1] != 'undefined' ? 1 : 0),
     3427                                                                'Missing time separator in time range after: "' + tokens[at][1] + '"');
    28613428                                        return [ at ];
    28623429                                }
     
    28683435                        return at;
    28693436                }
    2870 
    2871                 // get time in minutes from <hour>:<minute> {{{
    2872                 // Only used if throwing an error is wanted.
    2873                 function getMinutesByHoursMinutes(tokens, nblock, at) {
     3437                // }}}
     3438
     3439                /* Helpers for time range parser {{{ */
     3440
     3441                /* Get time in minutes from <hour>:<minute> (tokens). {{{
     3442                 * Only used if throwing an error is wanted.
     3443                 *
     3444                 * :param tokens: List of token objects.
     3445                 * :param nrule: Rule number starting with 0.
     3446                 * :param at: Position at which the time begins.
     3447                 * :returns: Time in minutes.
     3448                 */
     3449                function getMinutesByHoursMinutes(tokens, nrule, at) {
    28743450                        if (tokens[at+2][0] > 59)
    2875                                 throw formatWarnErrorMessage(nblock, at+2,
     3451                                throw formatWarnErrorMessage(nrule, at+2,
    28763452                                                'Minutes are greater than 59.');
    28773453                        return tokens[at][0] * 60 + tokens[at+2][0];
     
    28793455                // }}}
    28803456
    2881                 // get time in minutes from "(sunrise-01:30)" {{{
    2882                 // Extract the added or subtracted time from "(sunrise-01:30)"
    2883                 // returns time in minutes e.g. -90
     3457                /* Get time in minutes from "(sunrise-01:30)" {{{
     3458                 * Extract the added or subtracted time from "(sunrise-01:30)"
     3459                 * returns time in minutes e.g. -90.
     3460                 *
     3461                 * :param tokens: List of token objects.
     3462                 * :param at: Position where the specification for the point in time could be.
     3463                 * :returns: Time in minutes on suggest, throws an exception otherwise.
     3464                */
    28843465                function parseTimevarCalc(tokens, at) {
     3466                        var error;
    28853467                        if (matchTokens(tokens, at+2, '+') || matchTokens(tokens, at+2, '-')) {
    28863468                                if (matchTokens(tokens, at+3, 'number', 'timesep', 'number')) {
    28873469                                        if (matchTokens(tokens, at+6, ')')) {
    28883470                                                var add_or_subtract = tokens[at+2][0] == '+' ? '1' : '-1';
    2889                                                 var minutes = getMinutesByHoursMinutes(tokens, nblock, at+3) * add_or_subtract;
    2890                                                 if (minutes == 0)
    2891                                                         parsing_warnings.push([ nblock, at+5, 'Adding zero in a variable time calculation does not change the variable time.'
    2892                                                                         + ' Please omit the calculation (example: "12:00-sunset").' ]
     3471                                                var minutes = getMinutesByHoursMinutes(tokens, nrule, at+3) * add_or_subtract;
     3472                                                if (minutes === 0)
     3473                                                        parsing_warnings.push([ nrule, at+5, 'Adding zero in a variable time calculation does not change the variable time.'
     3474                                                                        + ' Please omit the calculation (example: "sunrise-(sunset-00:00)").' ]
    28933475                                                                );
    28943476                                                return minutes;
     
    29043486
    29053487                        if (error)
    2906                                 throw formatWarnErrorMessage(nblock, error[0],
     3488                                throw formatWarnErrorMessage(nrule, error[0],
    29073489                                        'Calculcation with variable time is not in the right syntax' + error[1]);
    29083490                }
    2909                 // }}}
    2910                 // }}}
    2911 
    2912                 // Weekday range parser (Mo,We-Fr,Sa[1-2,-1],PH) {{{
    2913                 function parseWeekdayRange(tokens, at, selectors) {
     3491                /* }}} */
     3492                /* }}} */
     3493
     3494                /* Weekday range parser (Mo,We-Fr,Sa[1-2,-1],PH). {{{
     3495                 *
     3496                 * :param tokens: List of token objects.
     3497                 * :param at: Position where the weekday tokens could be.
     3498                 * :param selectors: Reference to selector object.
     3499                 * :returns: Position at which the token does not belong to the selector anymore.
     3500                 */
     3501                function parseWeekdayRange(tokens, at, selectors, in_holiday_selector) {
     3502                        if (!in_holiday_selector) {
     3503                                in_holiday_selector = true;
     3504                                tokens[at][3] = 'weekday';
     3505                        }
     3506
    29143507                        for (; at < tokens.length; at++) {
    29153508                                if (matchTokens(tokens, at, 'weekday', '[')) {
     
    29213514
    29223515                                                // bad number
    2923                                                 if (from == 0 || from < -5 || from > 5)
    2924                                                         throw formatWarnErrorMessage(nblock, at,
     3516                                                if (from === 0 || from < -5 || from > 5)
     3517                                                        throw formatWarnErrorMessage(nrule, at,
    29253518                                                                'Number between -5 and 5 (except 0) expected');
    29263519
     
    29303523                                                        for (var i = from; i <= to; i++) {
    29313524                                                                // bad number
    2932                                                                 if (i == 0 || i < -5 || i > 5)
    2933                                                                         throw formatWarnErrorMessage(nblock, at+2,
     3525                                                                if (i === 0 || i < -5 || i > 5)
     3526                                                                        throw formatWarnErrorMessage(nrule, at+2,
    29343527                                                                                'Number between -5 and 5 (except 0) expected.');
    29353528
     
    29373530                                                        }
    29383531                                                } else {
    2939                                                         throw formatWarnErrorMessage(nblock, at+2,
     3532                                                        throw formatWarnErrorMessage(nrule, at+2,
    29403533                                                                'Bad range: ' + from + '-' + to);
    29413534                                                }
     
    29433536
    29443537                                        if (!matchTokens(tokens, endat, ']'))
    2945                                                 throw formatWarnErrorMessage(nblock, endat, '"]" or more numbers expected.');
     3538                                                throw formatWarnErrorMessage(nrule, endat, '"]" or more numbers expected.');
    29463539
    29473540                                        var add_days = getMoveDays(tokens, endat+1, 6, 'constrained weekdays');
    29483541                                        week_stable = false;
    29493542
    2950                                         // Create selector for each list element
     3543                                        // Create selector for each list element.
    29513544                                        for (var nnumber = 0; nnumber < numbers.length; nnumber++) {
    29523545
     
    29873580                                                        }
    29883581
     3582                                                        var target_day_with_added_moved_days_this_month;
    29893583                                                        if (add_days > 0) {
    2990                                                                 var target_day_with_added_moved_days_this_month = dateAtNextWeekday(
     3584                                                                target_day_with_added_moved_days_this_month = dateAtNextWeekday(
    29913585                                                                        new Date(date.getFullYear(), date.getMonth() + (number > 0 ? 0 : 1) -1, 1), weekday);
    29923586                                                                target_day_with_added_moved_days_this_month.setDate(target_day_with_added_moved_days_this_month.getDate()
     
    29963590                                                                        return [true, dateAtDayMinutes(date, minutes_in_day)];
    29973591                                                        } else if (add_days < 0) {
    2998                                                                 var target_day_with_added_moved_days_this_month = dateAtNextWeekday(
     3592                                                                target_day_with_added_moved_days_this_month = dateAtNextWeekday(
    29993593                                                                        new Date(date.getFullYear(), date.getMonth() + (number > 0 ? 0 : 1) + 1, 1), weekday);
    30003594                                                                target_day_with_added_moved_days_this_month.setDate(target_day_with_added_moved_days_this_month.getDate()
     
    30653659                                } else if (matchTokens(tokens, at, 'holiday')) {
    30663660                                        week_stable = false;
    3067                                         return parseHoliday(tokens, at, selectors, true);
     3661                                        return parseHoliday(tokens, at, selectors, true, in_holiday_selector);
    30683662                                } else {
    3069                                         throw formatWarnErrorMessage(nblock, at, 'Unexpected token in weekday range: ' + tokens[at][1]);
     3663                                        throw formatWarnErrorMessage(nrule, at, 'Unexpected token in weekday range: ' + tokens[at][1]);
    30703664                                }
    30713665
     
    30743668                        }
    30753669
    3076                         if (typeof used_subparsers['weekdays'] != 'object')
    3077                                 used_subparsers['weekdays'] = [ at ];
    3078                         else
    3079                                 used_subparsers['weekdays'].push(at);
    3080 
    30813670                        return at;
    30823671                }
    3083 
     3672                // }}}
     3673
     3674                /* Get the number of days a date should be moved (if any). {{{
     3675                 *
     3676                 * :param tokens: List of token objects.
     3677                 * :param at: Position where the date moving tokens could be.
     3678                 * :param max_differ: Maximal number of days to move (could also be zero if there are no day move tokens).
     3679                 * :returns: Array:
     3680                 *                      0. Days to add.
     3681                 *                      1. How many tokens.
     3682                 */
    30843683                function getMoveDays(tokens, at, max_differ, name) {
    3085                         var add_days = [ 0, 0 ]; // [ 'add days', 'how many tokens' ]
     3684                        var add_days = [ 0, 0 ]; // [ 'days to add', 'how many tokens' ]
    30863685                        add_days[0] = matchTokens(tokens, at, '+') || (matchTokens(tokens, at, '-') ? -1 : 0);
    3087                         if (add_days[0] != 0 && matchTokens(tokens, at+1, 'number', 'calcday')) {
     3686                        if (add_days[0] !== 0 && matchTokens(tokens, at+1, 'number', 'calcday')) {
    30883687                                // continues with '+ 5 days' or something like that
    30893688                                if (tokens[at+1][0] > max_differ)
    3090                                         throw formatWarnErrorMessage(nblock, at+2,
     3689                                        throw formatWarnErrorMessage(nrule, at+2,
    30913690                                                'There should be no reason to differ more than ' + max_differ + ' days from a ' + name + '. If so tell us …');
    30923691                                add_days[0] *= tokens[at+1][0];
    3093                                 if (add_days[0] == 0 && !done_with_warnings)
    3094                                         parsing_warnings.push([ nblock, at+2, 'Adding 0 does not change the date. Please omit this.' ]);
     3692                                if (add_days[0] === 0 && !done_with_warnings)
     3693                                        parsing_warnings.push([ nrule, at+2, 'Adding 0 does not change the date. Please omit this.' ]);
    30953694                                add_days[1] = 3;
    30963695                        } else {
     
    31013700                // }}}
    31023701
    3103                 // Holiday parser for public and school holidays (PH,SH) {{{
    3104                 // push_to_weekday will push the selector into the weekday selector array which has the desired side effect of working in conjunction with the weekday selectors (either the holiday match or the weekday), which is the normal and expected behavior.
    3105                 function parseHoliday(tokens, at, selectors, push_to_weekday) {
     3702                /* Holiday parser for public and school holidays (PH,SH) {{{
     3703                 *
     3704                 * :param tokens: List of token objects.
     3705                 * :param at: Position where to start.
     3706                 * :param selectors: Reference to selector object.
     3707                 * :param push_to_weekday: Will push the selector into the weekday selector array which has the desired side effect of working in conjunction with the weekday selectors (either the holiday match or the weekday), which is the normal and expected behavior.
     3708                 * :returns: Position at which the token does not belong to the selector anymore.
     3709                 */
     3710                function parseHoliday(tokens, at, selectors, push_to_weekday, in_holiday_selector) {
     3711                        if (!in_holiday_selector) {
     3712
     3713                                if (push_to_weekday)
     3714                                        tokens[at][3] = 'weekday';
     3715                                else
     3716                                        tokens[at][3] = 'holiday'; // Could also be holiday but this is not important here.
     3717                        }
     3718
    31063719                        for (; at < tokens.length; at++) {
    31073720                                if (matchTokens(tokens, at, 'holiday')) {
     
    32403853                                        }
    32413854                                } else if (matchTokens(tokens, at, 'weekday')) {
    3242                                         return parseWeekdayRange(tokens, at, selectors);
     3855                                        return parseWeekdayRange(tokens, at, selectors, true);
    32433856                                } else {
    3244                                         throw formatWarnErrorMessage(nblock, at, 'Unexpected token (school holiday parser): ' + tokens[at][1]);
     3857                                        throw formatWarnErrorMessage(nrule, at, 'Unexpected token (school holiday parser): ' + tokens[at][1]);
    32453858                                }
    32463859
     
    32533866
    32543867                // Helpers for holiday parsers {{{
    3255                 // Returns a number for a date which can then be used to compare just the dates (without the time).
    3256                 // This is necessary because a selector could be called for the middle of the day and we need to tell if it matches that day.
    3257                 // Example: Returns 20150015 for Jan 01 2015
     3868
     3869                /* Returns a number for a date which can then be used to compare just the dates (without the time). {{{
     3870                 * This is necessary because a selector could be called for the middle of the day and we need to tell if it matches that day.
     3871                 * Example: Returns 20150015 for Jan 01 2015
     3872                 *
     3873                 * :param date: Date object.
     3874                 * :param include_year: Boolean. If true include the year.
     3875                 * :returns: Number for the date.
     3876                 */
    32583877                function getValueForDate(date, include_year) {
    3259                         // Implicit because undefined evaluates to false
     3878                        // Implicit because undefined evaluates to false.
    32603879                        // include_year = typeof include_year != 'undefined' ? include_year : false;
    32613880
    32623881                        return (include_year ? date.getFullYear() * 10000 : 0) + date.getMonth() * 100 + date.getDate();
    32633882                }
     3883                // }}}
    32643884
    32653885                // return the school holiday definition e.g. [ 5, 25, /* to */ 6, 5 ],
     
    32743894                                if (typeof holiday == 'undefined') {
    32753895                                        if (fatal) {
    3276                                                 throw 'School holiday ' + SH_hash.name + ' has no definition for the year ' + year + '.';
     3896                                                throw formatLibraryBugMessage('School holiday ' + SH_hash.name + ' has no definition for the year ' + year + '.'
     3897                                                                + ' You can also add them: ' + repository_url);
    32773898                                        } else {
    32783899                                                return undefined;
     
    32973918                                                } else if (holidays[location_cc][type_of_holidays]) {
    32983919                                                        // holidays are only defined country wide
    3299                                                         matching_holiday = {}; // holidays in the country wide scope can be limited to certain states
     3920                                                        var matching_holiday = {}; // holidays in the country wide scope can be limited to certain states
    33003921                                                        for (var holiday_name in holidays[location_cc][type_of_holidays]) {
    33013922                                                                if (typeof holidays[location_cc][type_of_holidays][holiday_name][2] === 'object') {
    3302                                                                         if (-1 != indexOf.call(holidays[location_cc][type_of_holidays][holiday_name][2], location_state))
     3923                                                                        if (-1 != holidays[location_cc][type_of_holidays][holiday_name][2].indexOf(location_state))
    33033924                                                                                matching_holiday[holiday_name] = holidays[location_cc][type_of_holidays][holiday_name];
    33043925                                                                } else {
     
    33063927                                                                }
    33073928                                                        }
    3308                                                         if (Object.keys(matching_holiday).length == 0)
    3309                                                                 throw 'There are no holidays ' + type_of_holidays + ' defined for country ' + location_cc + '.'
    3310                                                                         + ' Please add them: https://github.com/ypid/opening_hours.js ';
     3929                                                        if (Object.keys(matching_holiday).length === 0)
     3930                                                        throw formatLibraryBugMessage('There are no holidays ' + type_of_holidays + ' defined for country ' + location_cc + '.'
     3931                                                                        + ' You can also add them: ' + repository_url);
    33113932                                                        return matching_holiday;
    33123933                                                } else {
    3313                                                         throw 'Holidays ' + type_of_holidays + ' are not defined for country ' + location_cc
    3314                                                                 + ' and state ' + location_state + '.'
    3315                                                                 + ' Please add them.';
     3934                                                        throw formatLibraryBugMessage('Holidays ' + type_of_holidays + ' are not defined for country ' + location_cc
     3935                                                                        + ' and state ' + location_state + '.'
     3936                                                                        + ' You can also add them: ' + repository_url);
    33163937                                                }
    33173938                                        }
    33183939                                } else {
    3319                                         throw 'No holidays are defined for country ' + location_cc + '. Please add them: https://github.com/ypid/opening_hours.js ';
     3940                                        throw formatLibraryBugMessage('No holidays are defined for country ' + location_cc + '.'
     3941                                                        + ' You can also add them: ' + repository_url);
    33203942                                }
    33213943                        } else { // we have no idea which holidays do apply because the country code was not provided
    3322                                 throw 'Country code missing which is needed to select the correct holidays (see README how to provide it)'
     3944                                throw 'Country code missing which is needed to select the correct holidays (see README how to provide it)';
    33233945                        }
    33243946                }
     
    33443966                        var oD = (19*oC + 15) % 30;
    33453967                        var oE = (2*oA+4*oB - oD + 34) % 7;
    3346                         var oF = oD+oE
    3347 
    3348                         if (oF < 9) {oDate = new Date(Y, 4-1, oF+4);}
    3349                         else {if ((oF+4)<31) {oDate = new Date(Y, 4-1, oF+4);}
    3350                               else {oDate = new Date(Y, 5-1, oF-26);}}
     3968                        var oF = oD+oE;
     3969
     3970                        var oDate;
     3971                        if (oF < 9) {
     3972                                oDate = new Date(Y, 4-1, oF+4);
     3973                        } else {
     3974                                if ((oF+4)<31) {
     3975                                        oDate = new Date(Y, 4-1, oF+4);
     3976                                } else {
     3977                                        oDate = new Date(Y, 5-1, oF-26);
     3978                                }
     3979                        }
    33513980
    33523981                        // calculate last Sunday in February
     
    33683997                                var firstMonday = 1 + ((8 - first.getDay()) % 7);
    33693998                                firstMondays[i] = firstMonday;
    3370                         };
     3999                        }
    33714000
    33724001                        return {
     
    33874016
    33884017                        var sorted_holidays = [];
     4018                        var next_holiday;
    33894019
    33904020                        for (var holiday_name in applying_holidays) {
     
    33944024                                                throw 'Movable day ' + applying_holidays[holiday_name][0] + ' can not not be calculated.'
    33954025                                                        + ' Please add the formula how to calculate it.';
    3396                                         var next_holiday = new Date(selected_movableDay.getFullYear(),
     4026                                        next_holiday = new Date(selected_movableDay.getFullYear(),
    33974027                                                        selected_movableDay.getMonth(),
    33984028                                                        selected_movableDay.getDate()
     
    34044034                                                        + ' days is not in the year of the movable day anymore. Currently not supported.';
    34054035                                } else {
    3406                                         var next_holiday = new Date(year,
     4036                                        next_holiday = new Date(year,
    34074037                                                        applying_holidays[holiday_name][0] - 1,
    34084038                                                        applying_holidays[holiday_name][1]
     
    34264056                // }}}
    34274057
    3428                 // Year range parser (2013,2016-2018,2020/2) {{{
     4058                /* Year range parser (2013,2016-2018,2020/2). {{{
     4059                 *
     4060                 * :param tokens: List of token objects.
     4061                 * :param at: Position where to start.
     4062                 * :returns: Position at which the token does not belong to the selector anymore.
     4063                 */
    34294064                function parseYearRange(tokens, at) {
     4065                        tokens[at][3] = 'year';
    34304066                        for (; at < tokens.length; at++) {
    34314067                                if (matchTokens(tokens, at, 'year')) {
    3432                                         var is_range = false, has_period = false;
     4068                                        var is_range   = false,
     4069                                                has_period,
     4070                                                period;
    34334071                                        if (matchTokens(tokens, at+1, '-', 'year', '/', 'number')) {
    3434                                                 var is_range   = true;
    3435                                                 var has_period = true;
    3436                                                 var period = parseInt(tokens[at+4][0]);
     4072                                                is_range   = true;
     4073                                                has_period = true;
     4074                                                period = parseInt(tokens[at+4][0]);
    34374075                                                checkPeriod(at+4, period, 'year');
    34384076                                        } else {
    3439                                                 var is_range   = matchTokens(tokens, at+1, '-', 'year');
    3440                                                 var has_period = matchTokens(tokens, at+1, '/', 'number');
     4077                                                is_range   = matchTokens(tokens, at+1, '-', 'year');
     4078                                                has_period = matchTokens(tokens, at+1, '/', 'number');
    34414079                                                if (has_period) {
    3442                                                         var period = parseInt(tokens[at+2][0]);
     4080                                                        period = parseInt(tokens[at+2][0]);
    34434081                                                        checkPeriod(at+2, period, 'year', 'no_end_year');
    34444082                                                } else if (matchTokens(tokens, at+1, '+')) {
    3445                                                         var period = 1;
     4083                                                        period = 1;
    34464084                                                        has_period = 2;
    34474085                                                }
     
    34534091                                                // handle reversed range
    34544092                                                if (tokens[at+2][0] == year_from)
    3455                                                         throw formatWarnErrorMessage(nblock, at,
     4093                                                        throw formatWarnErrorMessage(nrule, at,
    34564094                                                                'A year range in which the start year is equal to the end year does not make sense.'
    34574095                                                                + ' Please remove the end year. E.g. "' + year_from + ' May 23"');
    34584096                                                else
    3459                                                         throw formatWarnErrorMessage(nblock, at,
     4097                                                        throw formatWarnErrorMessage(nrule, at,
    34604098                                                                'A year range in which the start year is greater than the end year does not make sense.'
    34614099                                                                + ' Please turn it over.');
     
    34714109                                                } else if (has_period) {
    34724110                                                        if (year_from <= ouryear) {
    3473                                                                 if (is_range && year_to < ouryear)
     4111                                                                if (is_range && ouryear > year_to)
    34744112                                                                        return [false];
    34754113                                                                if (period > 0) {
    3476                                                                         if ((ouryear - year_from) % period == 0) {
     4114                                                                        if ((ouryear - year_from) % period === 0) {
    34774115                                                                                return [true, new Date(ouryear + 1, 0, 1)];
    34784116                                                                        } else {
     
    34944132                                        at += 1 + (is_range ? 2 : 0) + (has_period ? (has_period == 2 ? 1 : 2) : 0);
    34954133                                } else {
    3496                                         throw formatWarnErrorMessage(nblock, at, 'Unexpected token in year range: ' + tokens[at][1]);
     4134                                        throw formatWarnErrorMessage(nrule, at, 'Unexpected token in year range: ' + tokens[at][1]);
    34974135                                }
    34984136
     
    35014139                        }
    35024140
    3503                         if (typeof used_subparsers['year ranges'] != 'object')
    3504                                 used_subparsers['year ranges'] = [ at ];
    3505                         else
    3506                                 used_subparsers['year ranges'].push(at);
    3507 
    35084141                        return at;
    35094142                }
    35104143                // }}}
    35114144
    3512                 // Week range parser (week 11-20, week 1-53/2) {{{
     4145                /* Week range parser (week 11-20, week 1-53/2). {{{
     4146                 *
     4147                 * :param tokens: List of token objects.
     4148                 * :param at: Position where to start.
     4149                 * :returns: Position at which the token does not belong to the selector anymore.
     4150                 */
    35134151                function parseWeekRange(tokens, at) {
    35144152                        for (; at < tokens.length; at++) {
     4153                                if (matchTokens(tokens, at, 'week')) {
     4154                                        at++;
     4155                                }
    35154156                                if (matchTokens(tokens, at, 'number')) {
    3516                                         var is_range = matchTokens(tokens, at+1, '-', 'number'), has_period = false;
     4157                                        var is_range = matchTokens(tokens, at+1, '-', 'number'), period = 0;
     4158                                        var week_from = tokens[at][0];
     4159                                        var week_to   = is_range ? tokens[at+2][0] : week_from;
     4160                                        if (week_from > week_to) {
     4161                                                throw formatWarnErrorMessage(nrule, at+2,
     4162                                                        'You have specified a week range in reverse order or leaping over a year. This is (currently) not supported.');
     4163                                        }
     4164                                        if (week_from < 1) {
     4165                                                throw formatWarnErrorMessage(nrule, at,
     4166                                                        'You have specified a week date less then one. A valid week date range is 1-53.');
     4167                                        }
     4168                                        if (week_to > 53) {
     4169                                                throw formatWarnErrorMessage(nrule, is_range ? at+2 : at,
     4170                                                        'You have specified a week date greater then 53. A valid week date range is 1-53.');
     4171                                        }
    35174172                                        if (is_range) {
    3518                                                 has_period = matchTokens(tokens, at+3, '/', 'number');
    3519                                                 // if (week_stable) {
    3520                                                 //      if (tokens[at][0] == 1 && tokens[at+2][0] >) // Maximum?
    3521                                                 //              week_stable = true;
    3522                                                 //      else
    3523                                                 //              week_stable = false;
    3524                                                 // } else {
    3525                                                 //      week_stable = false;
    3526                                                 // }
    3527                                         }
    3528 
    3529                                         selectors.week.push(function(tokens, at, is_range, has_period) { return function(date) {
    3530                                                 var ourweek = Math.floor((date - dateAtWeek(date, 0)) / msec_in_week);
    3531 
    3532                                                 var week_from = tokens[at][0] - 1;
    3533                                                 var week_to = is_range ? tokens[at+2][0] - 1 : week_from;
    3534 
    3535                                                 var start_of_next_year = new Date(date.getFullYear() + 1, 0, 1);
    3536 
    3537                                                 // before range
    3538                                                 if (ourweek < week_from)
    3539                                                         return [false, getMinDate(dateAtWeek(date, week_from), start_of_next_year)];
    3540 
    3541                                                 // we're after range, set check date to next year
    3542                                                 if (ourweek > week_to)
    3543                                                         return [false, start_of_next_year];
    3544 
    3545                                                 // we're in range
    3546                                                 var period;
    3547                                                 if (has_period) {
    3548                                                         var period = tokens[at+4][0];
    3549                                                         if (period > 1) {
    3550                                                                 var in_period = (ourweek - week_from) % period == 0;
    3551                                                                 if (in_period)
    3552                                                                         return [true, getMinDate(dateAtWeek(date, ourweek + 1), start_of_next_year)];
    3553                                                                 else
    3554                                                                         return [false, getMinDate(dateAtWeek(date, ourweek + period - 1), start_of_next_year)];
     4173                                                period = matchTokens(tokens, at+3, '/', 'number');
     4174                                                if (period) {
     4175                                                        period = tokens[at+4][0];
     4176                                                        if (period < 2) {
     4177                                                                throw formatWarnErrorMessage(nrule, at+4,
     4178                                                                        'You have specified a week period which is less than two.'
     4179                                                                        + ' If you want to select the whole range from week ' + week_from + ' to week ' + week_to + ' then just omit the "/' + period + '".');
     4180                                                        } else if (period > 26) {
     4181                                                                throw formatWarnErrorMessage(nrule, at+4,
     4182                                                                        'You have specified a week period which is greater than 26.'
     4183                                                                        + ' 26.5 is the half of the maximum 53 week dates per year so a week date period greater than 26 would only apply once per year.'
     4184                                                                        + ' Please specify the week selector as "week ' + week_from + '" if that is what you want to express.');
    35554185                                                        }
    35564186                                                }
    3557 
    3558                                                 return [true, getMinDate(dateAtWeek(date, week_to + 1), start_of_next_year)];
    3559                                         }}(tokens, at, is_range, has_period));
    3560 
    3561                                         at += 1 + (is_range ? 2 : 0) + (has_period ? 2 : 0);
     4187                                        }
     4188
     4189                                        if (week_stable && (!(week_from <= 1 && week_to >= 53) || period)) {
     4190                                                week_stable = false;
     4191                                        }
     4192
     4193                                        if (!period && week_from == 1 && week_to == 53) {
     4194                                                /* Shortcut and work around bug. */
     4195                                                selectors.week.push(function(date) { return [true]; });
     4196                                        } else {
     4197
     4198                                                selectors.week.push(function(week_from, week_to, is_range, period) { return function(date) {
     4199                                                        var ourweek = date.getWeekNumber();
     4200
     4201                                                        // console.log("week_from: %s, week_to: %s", week_from, week_to);
     4202                                                        // console.log("ourweek: %s, date: %s", ourweek, date);
     4203
     4204                                                        // before range
     4205                                                        if (ourweek < week_from) {
     4206                                                                // console.log("Before: " + getNextDateOfISOWeek(week_from, date));
     4207                                                                return [false, getNextDateOfISOWeek(week_from, date)];
     4208                                                        }
     4209
     4210                                                        // we're after range, set check date to next year
     4211                                                        if (ourweek > week_to) {
     4212                                                                // console.log("After");
     4213                                                                return [false, getNextDateOfISOWeek(week_from, date)];
     4214                                                        }
     4215
     4216                                                        // we're in range
     4217                                                        if (period) {
     4218                                                                var in_period = (ourweek - week_from) % period === 0;
     4219                                                                if (in_period) {
     4220                                                                        return [true, getNextDateOfISOWeek(ourweek + 1, date)];
     4221                                                                } else {
     4222                                                                        return [false, getNextDateOfISOWeek(ourweek + period - 1, date)];
     4223                                                                }
     4224                                                        }
     4225
     4226                                                        // console.log("Match");
     4227                                                        return [true, getNextDateOfISOWeek(week_to == 53 ? 1 : week_to + 1, date)];
     4228                                                }}(week_from, week_to, is_range, period));
     4229                                        }
     4230
     4231                                        at += 1 + (is_range ? 2 : 0) + (period ? 2 : 0);
    35624232                                } else {
    3563                                         throw formatWarnErrorMessage(nblock, at, 'Unexpected token in week range: ' + tokens[at][1]);
     4233                                        throw formatWarnErrorMessage(nrule, at, 'Unexpected token in week range: ' + tokens[at][1]);
    35644234                                }
    35654235
    35664236                                if (!matchTokens(tokens, at, ','))
    35674237                                        break;
    3568 
    3569                                 if (!matchTokens(tokens, at+1, 'number')) {
    3570                                         at++; // we don‘t need the comma in parseGroup
    3571                                         break;
     4238                        }
     4239
     4240                        return at;
     4241                }
     4242
     4243                // http://stackoverflow.com/a/6117889
     4244                Date.prototype.getWeekNumber = function(){
     4245                        var d = new Date(+this);
     4246                        d.setHours(0,0,0);
     4247                        d.setDate(d.getDate()+4-(d.getDay()||7));
     4248                        return Math.ceil((((d-new Date(d.getFullYear(),0,1))/8.64e7)+1)/7);
     4249                };
     4250                // http://stackoverflow.com/a/16591175
     4251                function getDateOfISOWeek(w, y) {
     4252                        var simple = new Date(y, 0, 1 + (w - 1) * 7);
     4253                        var dow = simple.getDay();
     4254                        var ISOweekStart = simple;
     4255                        if (dow <= 4)
     4256                                ISOweekStart.setDate(simple.getDate() - simple.getDay() + 1);
     4257                        else
     4258                                ISOweekStart.setDate(simple.getDate() + 8 - simple.getDay());
     4259                        return ISOweekStart;
     4260                }
     4261                function getNextDateOfISOWeek(week, date) {
     4262                        var next_date;
     4263                        for (var i = -1; i <= 1; i++) {
     4264                                next_date = getDateOfISOWeek(week, date.getFullYear() + i);
     4265                                if (next_date.getTime() > date.getTime()) {
     4266                                        return next_date;
    35724267                                }
    35734268                        }
    3574 
    3575                         if (typeof used_subparsers['week ranges'] != 'object')
    3576                                 used_subparsers['week ranges'] = [ at ];
    3577                         else
    3578                                 used_subparsers['week ranges'].push;
    3579 
    3580                         return at;
    3581                 }
    3582 
    3583                 function dateAtWeek(date, week) {
    3584                         var tmpdate = new Date(date.getFullYear(), 0, 1);
    3585                         tmpdate.setDate(1 - (tmpdate.getDay() + 6) % 7 + week * 7); // start of week n where week starts on Monday
    3586                         return tmpdate;
    3587                 }
    3588 
    3589                 function getMinDate(date /*, ...*/) {
    3590                         for (var i = 1; i < arguments.length; i++)
    3591                                 if (arguments[i].getTime() < date.getTime())
    3592                                         date = arguments[i];
    3593                         return date;
     4269                        throw formatLibraryBugMessage();
    35944270                }
    35954271                // }}}
    35964272
    3597                 // Month range parser (Jan,Feb-Mar) {{{
    3598                 // push_to_monthday will push the selector into the monthday selector array which has the desired side effect of working in conjunction with the monthday selectors (either the month match or the monthday).
    3599                 function parseMonthRange(tokens, at, push_to_monthday) {
     4273                /* Month range parser (Jan,Feb-Mar). {{{
     4274                 *
     4275                 * :param tokens: List of token objects.
     4276                 * :param at: Position where to start.
     4277                 * :param push_to_monthday: Will push the selector into the monthday selector array which has the desired side effect of working in conjunction with the monthday selectors (either the month match or the monthday).
     4278                 * :returns: Position at which the token does not belong to the selector anymore.
     4279                 */
     4280                function parseMonthRange(tokens, at, push_to_monthday, in_selector) {
     4281                        if (!in_selector)
     4282                                tokens[at][3] = 'month';
     4283
    36004284                        for (; at < tokens.length; at++) {
    36014285                                // Use parseMonthdayRange if '<month> <daynum>' and not '<month> <hour>:<minute>'
    36024286                                if (matchTokens(tokens, at, 'month', 'number') && !matchTokens(tokens, at+2, 'timesep', 'number')) {
    3603                                         return parseMonthdayRange(tokens, at, nblock, true);
     4287                                        return parseMonthdayRange(tokens, at, nrule, true);
    36044288                                } else if (matchTokens(tokens, at, 'month')) {
    36054289                                        // Single month (Jan) or month range (Feb-Mar)
    36064290                                        var is_range = matchTokens(tokens, at+1, '-', 'month');
    36074291
     4292                                        var month_from = tokens[at][0];
     4293                                        var month_to = is_range ? tokens[at+2][0] : month_from;
     4294
    36084295                                        if (is_range && week_stable) {
    3609                                                 var month_from = tokens[at][0];
    3610                                                 var month_to   = tokens[at+2][0];
    3611                                                 if (month_from == (month_to + 1) % 12)
    3612                                                         week_stable = true;
    3613                                                 else
     4296                                                if (month_from !== (month_to + 1) % 12)
    36144297                                                        week_stable = false;
    36154298                                        } else {
     
    36174300                                        }
    36184301
    3619                                         var selector = function(tokens, at, is_range) { return function(date) {
     4302                                        var inside = true;
     4303
     4304                                        // handle reversed range
     4305                                        if (month_to < month_from) {
     4306                                                var tmp = month_to;
     4307                                                month_to = month_from - 1;
     4308                                                month_from = tmp + 1;
     4309                                                inside = false;
     4310                                        }
     4311
     4312                                        var selector = function(tokens, at, month_from, month_to, is_range, inside) { return function(date) {
    36204313                                                var ourmonth = date.getMonth();
    3621                                                 var month_from = tokens[at][0];
    3622                                                 var month_to = is_range ? tokens[at+2][0] : month_from;
    3623 
    3624                                                 var inside = true;
    3625 
    3626                                                 // handle reversed range
    3627                                                 if (month_to < month_from) {
    3628                                                         var tmp = month_to;
    3629                                                         month_to = month_from - 1;
    3630                                                         month_from = tmp + 1;
    3631                                                         inside = false;
    3632                                                 }
    36334314
    36344315                                                // handle full range
     
    36414322                                                        return [inside, dateAtNextMonth(date, month_to + 1)];
    36424323                                                }
    3643                                         }}(tokens, at, is_range);
     4324                                        }}(tokens, at, month_from, month_to, is_range, inside);
    36444325
    36454326                                        if (push_to_monthday === true)
     
    36504331                                        at += is_range ? 3 : 1;
    36514332                                } else {
    3652                                         throw formatWarnErrorMessage(nblock, at, 'Unexpected token in month range: ' + tokens[at][1]);
     4333                                        throw formatWarnErrorMessage(nrule, at, 'Unexpected token in month range: ' + tokens[at][1]);
    36534334                                }
    36544335
     
    36564337                                        break;
    36574338                        }
    3658 
    3659                         if (typeof used_subparsers['months'] != 'object')
    3660                                 used_subparsers['months'] = [ at ];
    3661                         else
    3662                                 used_subparsers['months'].push(at);
    36634339
    36644340                        return at;
     
    36704346                // }}}
    36714347
    3672                 // Month day range parser (Jan 26-31; Jan 26-Feb 26) {{{
    3673                 // push_to_month will push the selector into the month selector array which has the desired side effect of working in conjunction with the month selectors (either the month match or the monthday).
    3674                 function parseMonthdayRange(tokens, at, nblock, push_to_month) {
     4348                /* Month day range parser (Jan 26-31; Jan 26-Feb 26). {{{
     4349                 *
     4350                 * :param tokens: List of token objects.
     4351                 * :param at: Position where to start.
     4352                 * :param nrule: Rule number starting with 0.
     4353                 * :param push_to_month: Will push the selector into the month selector array which has the desired side effect of working in conjunction with the month selectors (either the month match or the monthday).
     4354                 * :returns: Position at which the token does not belong to the selector anymore.
     4355                 */
     4356                function parseMonthdayRange(tokens, at, nrule, push_to_month) {
     4357                        if (!push_to_month)
     4358                                tokens[at][3] = 'month';
     4359
    36754360                        for (; at < tokens.length; at++) {
    3676                                 var has_year = [], has_month = [], has_event = [], has_calc = [], has_constrained_weekday = [], has_calc = [];
     4361                                var has_year = [], has_month = [], has_event = [], has_calc = [], has_constrained_weekday = [];
    36774362                                has_year[0]  = matchTokens(tokens, at, 'year');
    36784363                                has_month[0] = matchTokens(tokens, at+has_year[0], 'month', 'number');
    36794364                                has_event[0] = matchTokens(tokens, at+has_year[0], 'event');
     4365
    36804366                                if (has_event[0])
    36814367                                        has_calc[0] = getMoveDays(tokens, at+has_year[0]+1, 200, 'event like easter');
    36824368
     4369                                var at_range_sep;
    36834370                                if (matchTokens(tokens, at+has_year[0], 'month', 'weekday', '[')) {
    36844371                                        has_constrained_weekday[0] = getConstrainedWeekday(tokens, at+has_year[0]+3);
    36854372                                        has_calc[0] = getMoveDays(tokens, has_constrained_weekday[0][1], 6, 'constrained weekdays');
    3686                                         var at_range_sep = has_constrained_weekday[0][1] + (typeof has_calc[0] != 'undefined' && has_calc[0][1] ? 3 : 0);
     4373                                        at_range_sep = has_constrained_weekday[0][1] + (typeof has_calc[0] != 'undefined' && has_calc[0][1] ? 3 : 0);
    36874374                                } else {
    3688                                         var at_range_sep = at+has_year[0]
     4375                                        at_range_sep = at+has_year[0]
    36894376                                                + (has_event[0]
    36904377                                                        ? (typeof has_calc[0] != 'undefined' && has_calc[0][1] ? 4 : 1)
     
    36924379                                }
    36934380
     4381                                var at_sec_event_or_month;
    36944382                                if ((has_month[0] || has_event[0] || has_constrained_weekday[0]) && matchTokens(tokens, at_range_sep, '-')) {
    3695                                         has_year[1]  = matchTokens(tokens, at_range_sep+1, 'year');
    3696                                         var at_sec_event_or_month = at_range_sep+1+has_year[1];
     4383                                        has_year[1] = matchTokens(tokens, at_range_sep+1, 'year');
     4384                                        at_sec_event_or_month = at_range_sep+1+has_year[1];
    36974385                                        has_month[1] = matchTokens(tokens, at_sec_event_or_month, 'month', 'number');
    36984386                                        if (!has_month[1]) {
     
    37114399
    37124400                                        if (has_month[0])
    3713                                                 isValidDate(tokens[at+has_year[0]][0], tokens[at+has_year[0]+1][0], nblock, at+has_year[0]+1);
     4401                                                checkIfDateIsValid(tokens[at+has_year[0]][0], tokens[at+has_year[0]+1][0], nrule, at+has_year[0]+1);
    37144402                                        if (has_month[1])
    3715                                                 isValidDate(tokens[at_sec_event_or_month][0], tokens[at_sec_event_or_month+1][0], nblock, at+has_year[0]+1);
    3716 
    3717                                         var selector = function(tokens, at, nblock, has_year, has_event, has_calc, at_sec_event_or_month, has_constrained_weekday) { return function(date) {
     4403                                                checkIfDateIsValid(tokens[at_sec_event_or_month][0], tokens[at_sec_event_or_month+1][0], nrule, at_sec_event_or_month+1);
     4404
     4405                                        var selector = function(tokens, at, nrule, has_year, has_event, has_calc, at_sec_event_or_month, has_constrained_weekday) { return function(date) {
    37184406                                                var start_of_next_year = new Date(date.getFullYear() + 1, 0, 1);
    37194407
     4408                                                var movableDays,
     4409                                                        from_date;
    37204410                                                if (has_event[0]) {
    3721                                                         var movableDays = getMovableEventsForYear(has_year[0] ? parseInt(tokens[at][0]) : date.getFullYear());
    3722                                                         var from_date  = movableDays[tokens[at+has_year[0]][0]];
     4411                                                        movableDays = getMovableEventsForYear(has_year[0] ? parseInt(tokens[at][0]) : date.getFullYear());
     4412                                                        from_date = movableDays[tokens[at+has_year[0]][0]];
    37234413
    37244414                                                        if (typeof has_calc[0] != 'undefined' && has_calc[0][1]) {
     
    37264416                                                                from_date.setDate(from_date.getDate() + has_calc[0][0]);
    37274417                                                                if (from_year_before_calc != from_date.getFullYear())
    3728                                                                         throw formatWarnErrorMessage(nblock, at+has_year[0]+has_calc[0][1]*3,
     4418                                                                        throw formatWarnErrorMessage(nrule, at+has_year[0]+has_calc[0][1]*3,
    37294419                                                                                'The movable day ' + tokens[at+has_year[0]][0] + ' plus ' + has_calc[0][0]
    37304420                                                                                + ' days is not in the year of the movable day anymore. Currently not supported.');
    37314421                                                        }
    37324422                                                } else if (has_constrained_weekday[0]) {
    3733                                                         var from_date = getDateForConstrainedWeekday((has_year[0] ? tokens[at][0] : date.getFullYear()), // year
     4423                                                        from_date = getDateForConstrainedWeekday((has_year[0] ? tokens[at][0] : date.getFullYear()), // year
    37344424                                                                tokens[at+has_year[0]][0], // month
    37354425                                                                tokens[at+has_year[0]+1][0], // weekday
    37364426                                                                has_constrained_weekday[0],
    37374427                                                                has_calc[0]);
    3738                                                         // var from_date_without_calc = getDateForConstrainedWeekday((has_year[0] ? tokens[at][0] : date.getFullYear()), // year
    3739                                                         //      tokens[at+has_year[0]][0], // month
    3740                                                         //      tokens[at+has_year[0]+1][0], // weekday
    3741                                                         //      has_constrained_weekday[0],
    3742                                                         //      [ 0, 0 ]);
    3743                                                         //      if (from_date_without_calc.getFullYear() != from_date.getFullYear())
    3744                                                         //              throw formatWarnErrorMessage(nblock, at+has_year[0]+has_calc[0][1],
    3745                                                         //                      'The constrained ' + weekdays[tokens[at+has_year[0]+1][0]] + ' plus ' + has_calc[0][0]
    3746                                                         //                      + ' days is not in the year of the movable day anymore. Currently not supported.');
    37474428                                                } else {
    3748                                                         var from_date = new Date((has_year[0] ? tokens[at][0] : date.getFullYear()),
     4429                                                        from_date = new Date((has_year[0] ? tokens[at][0] : date.getFullYear()),
    37494430                                                                tokens[at+has_year[0]][0], tokens[at+has_year[0]+1][0]);
    37504431                                                }
    37514432
     4433                                                var to_date;
    37524434                                                if (has_event[1]) {
    3753                                                         var movableDays = getMovableEventsForYear(has_year[1]
     4435                                                        movableDays = getMovableEventsForYear(has_year[1]
    37544436                                                                                ? parseInt(tokens[at_sec_event_or_month-1][0])
    37554437                                                                                : date.getFullYear());
    3756                                                         var to_date    = movableDays[tokens[at_sec_event_or_month][0]];
     4438                                                        to_date = movableDays[tokens[at_sec_event_or_month][0]];
    37574439
    37584440                                                        if (typeof has_calc[1] != 'undefined' && has_calc[1][1]) {
     
    37604442                                                                to_date.setDate(to_date.getDate() + has_calc[1][0]);
    37614443                                                                if (to_year_before_calc != to_date.getFullYear())
    3762                                                                         throw formatWarnErrorMessage(nblock, at_sec_event_or_month+has_calc[1][1],
     4444                                                                        throw formatWarnErrorMessage(nrule, at_sec_event_or_month+has_calc[1][1],
    37634445                                                                                'The movable day ' + tokens[at_sec_event_or_month][0] + ' plus ' + has_calc[1][0]
    37644446                                                                                + ' days is not in the year of the movable day anymore. Currently not supported.');
    37654447                                                        }
    37664448                                                } else if (has_constrained_weekday[1]) {
    3767                                                         var to_date = getDateForConstrainedWeekday((has_year[1] ? tokens[at_sec_event_or_month-1][0] : date.getFullYear()), // year
     4449                                                        to_date = getDateForConstrainedWeekday((has_year[1] ? tokens[at_sec_event_or_month-1][0] : date.getFullYear()), // year
    37684450                                                                tokens[at_sec_event_or_month][0],   // month
    37694451                                                                tokens[at_sec_event_or_month+1][0], // weekday
     
    37714453                                                                has_calc[1]);
    37724454                                                } else {
    3773                                                         var to_date = new Date((has_year[1] ? tokens[at_sec_event_or_month-1][0] : date.getFullYear()),
     4455                                                        to_date = new Date((has_year[1] ? tokens[at_sec_event_or_month-1][0] : date.getFullYear()),
    37744456                                                                tokens[at_sec_event_or_month][0], tokens[at_sec_event_or_month+1][0] + 1);
    37754457                                                }
     
    37924474                                                                return [!inside];
    37934475                                                        } else {
    3794                                                                 // // back matching, if from_date is moved to last year
    3795                                                                 // var from_date_next_year = getDateForConstrainedWeekday(date.getFullYear() + 1, // year
    3796                                                                 //      tokens[at+has_year[0]][0], // month
    3797                                                                 //      tokens[at+has_year[0]+1][0], // weekday
    3798                                                                 //      has_constrained_weekday[0],
    3799                                                                 //      has_calc[0]);
    3800                                                                 // if (date.getFullYear() == from_date_next_year.getFullYear()) {
    3801                                                                 //      if (date.getTime() < from_date_next_year.getTime()) {
    3802                                                                 //              return [!inside, from_date_next_year];
    3803                                                                 //      }
    3804                                                                 // }
    3805 
    38064476                                                                return [!inside, start_of_next_year];
    38074477                                                        }
    38084478                                                }
    3809                                         }}(tokens, at, nblock, has_year, has_event, has_calc, at_sec_event_or_month, has_constrained_weekday);
     4479                                        }}(tokens, at, nrule, has_year, has_event, has_calc, at_sec_event_or_month, has_constrained_weekday);
    38104480
    38114481                                        if (push_to_month === true)
     
    38484518                                                                        && (matchTokens(tokens, at_timesep_if_monthRange+2, '+')
    38494519                                                                                || matchTokens(tokens, at_timesep_if_monthRange+2, '-')
    3850                                                                                 || oh_mode != 0))
    3851                                                                 return parseMonthRange(tokens, at);
     4520                                                                                || oh_mode !== 0)) {
     4521                                                                                        return parseMonthRange(tokens, at, true, true);
     4522                                                        }
    38524523                                                }
    38534524
    38544525                                                // error checking {{{
    38554526                                                if (range_to < range_from)
    3856                                                         throw formatWarnErrorMessage(nblock, at+has_year+3,
     4527                                                        throw formatWarnErrorMessage(nrule, at+has_year+3,
    38574528                                                                        'Range in wrong order. From day is greater than to day.');
    3858                                                 isValidDate(month, range_from, nblock, at+1 + has_year);
    3859                                                 isValidDate(month, range_to - 1 /* added previously */,
    3860                                                                 nblock, at+has_year+(is_range ? 3 : 1));
     4529
     4530                                                checkIfDateIsValid(month, range_from, nrule, at+1 + has_year);
     4531                                                checkIfDateIsValid(month, range_to - 1 /* added previously */,
     4532                                                        nrule, at+has_year+(is_range ? 3 : 1));
    38614533                                                // }}}
    38624534
     
    38854557                                                        var in_period = nday % period;
    38864558
    3887                                                         if (in_period == 0)
     4559                                                        if (in_period === 0)
    38884560                                                                return [true, new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1)];
    38894561                                                        else
     
    39084580                                } else if (has_event[0]) {
    39094581
    3910                                         var selector = function(tokens, at, nblock, has_year, add_days) { return function(date) {
     4582                                        var selector = function(tokens, at, nrule, has_year, add_days) { return function(date) {
    39114583
    39124584                                                // console.log('enter selector with date: ' + date);
     
    39204592                                                        event_date.setDate(event_date.getDate() + add_days[0]);
    39214593                                                        if (date.getFullYear() != event_date.getFullYear())
    3922                                                                 throw formatWarnErrorMessage(nblock, at+has_year+add_days[1], 'The movable day ' + tokens[at+has_year][0] + ' plus '
     4594                                                                throw formatWarnErrorMessage(nrule, at+has_year+add_days[1], 'The movable day ' + tokens[at+has_year][0] + ' plus '
    39234595                                                                        + add_days[0]
    39244596                                                                        + ' days is not in the year of the movable day anymore. Currently not supported.');
     
    39334605                                                        return [false, new Date(date.getFullYear() + 1, 0, 1)];
    39344606
    3935                                         }}(tokens, at, nblock, has_year[0], has_calc[0]);
     4607                                        }}(tokens, at, nrule, has_year[0], has_calc[0]);
    39364608
    39374609                                        if (push_to_month === true)
     
    39454617                                        at = parseMonthRange(tokens, at);
    39464618                                } else if (matchTokens(tokens, at, 'month')) {
    3947                                         return parseMonthRange(tokens, at, true);
     4619                                        return parseMonthRange(tokens, at, true, true);
    39484620                                } else {
    39494621                                        // throw 'Unexpected token in monthday range: "' + tokens[at] + '"';
     
    39554627                        }
    39564628
    3957                         if (typeof used_subparsers['monthday ranges'] != 'object')
    3958                                 used_subparsers['monhday ranges'] = [ at ];
    3959                         else
    3960                                 used_subparsers['monhday ranges'].push(at);
    3961 
    39624629                        return at;
    39634630                }
    39644631                // }}}
    39654632
    3966                 // Main selector traversal function (return state array for date) {{{
     4633                /* Main selector traversal function (return state array for date). {{{
     4634                 * Checks for given date which rule and those which state and comment applies.
     4635                 *
     4636                 * :param date: Date object.
     4637                 * :returns: Array:
     4638                 *                      0. resultstate: State: true for 'open', false for 'closed'.
     4639                 *                      1. changedate: Next change as date object.
     4640                 *                      2. unknown: true if state open is not sure.
     4641                 *                      3. comment: Comment which applies for this time range (from date to changedate).
     4642                 *                      4. match_rule: Rule number starting with 0 (nrule).
     4643                 */
    39674644                this.getStatePair = function(date) {
    39684645                        var resultstate = false;
     
    39704647                        var unknown = false;
    39714648                        var comment;
    3972                         var match_block;
    3973 
    3974                         var date_matching_blocks = [];
    3975 
    3976                         for (var nblock = 0; nblock < blocks.length; nblock++) {
    3977                                 var matching_date_block = true;
    3978                                 // console.log(nblock, 'length',  blocks[nblock].date.length);
     4649                        var match_rule;
     4650
     4651                        var date_matching_rules = [];
     4652
     4653                        for (var nrule = 0; nrule < rules.length; nrule++) {
     4654                                var matching_date_rule = true;
     4655                                // console.log(nrule, 'length',  rules[nrule].date.length);
    39794656
    39804657                                // Try each date selector type
    3981                                 for (var ndateselector = 0; ndateselector < blocks[nblock].date.length; ndateselector++) {
    3982                                         var dateselectors = blocks[nblock].date[ndateselector];
    3983                                         // console.log(nblock, ndateselector);
     4658                                for (var ndateselector = 0; ndateselector < rules[nrule].date.length; ndateselector++) {
     4659                                        var dateselectors = rules[nrule].date[ndateselector];
     4660                                        // console.log(nrule, ndateselector);
    39844661
    39854662                                        var has_matching_selector = false;
     
    39994676
    40004677                                        if (!has_matching_selector) {
    4001                                                 matching_date_block = false;
     4678                                                matching_date_rule = false;
    40024679                                                // We can ignore other date selectors, as the state won't change
    40034680                                                // anyway until THIS selector matches (due to conjunction of date
     
    40104687                                }
    40114688
    4012                                 if (matching_date_block) {
     4689                                if (matching_date_rule) {
    40134690                                        // The following lines implement date overwriting logic (e.g. for
    4014                                         // "Mo-Fr 10:00-20:00; We 10:00-16:00", We block overrides Mo-Fr block.
     4691                                        // "Mo-Fr 10:00-20:00; We 10:00-16:00", We rule overrides Mo-Fr rule partly (We).
    40154692                                        //
    40164693                                        // This is the only way to be consistent. I thought about ("22:00-02:00; Tu 12:00-14:00") letting Th override 22:00-02:00 partly:
    40174694                                        // Like: Th 00:00-02:00,12:00-14:00 but this would result in including 22:00-00:00 for Th which is probably not what you want.
    4018                                         if (blocks[nblock].date.length > 0 && (blocks[nblock].meaning || blocks[nblock].unknown)
    4019                                                         && !blocks[nblock].wrapped && !blocks[nblock].additional && !blocks[nblock].fallback) {
    4020                                                 // var old_date_matching_blocks = date_matching_blocks;
    4021                                                 date_matching_blocks = [];
    4022                                                 // for (var nblock = 0; nblock < old_date_matching_blocks.length; nblock++) {
    4023                                                 //      if (!blocks[old_date_matching_blocks[nblock]].wrapped)
    4024                                                 //              date_matching_blocks.push(nblock);
     4695                                        if ((rules[nrule].date.length > 0 || nrule > 0 && rules[nrule].meaning && rules[nrule-1].date.length === 0)
     4696                                                        && (rules[nrule].meaning || rules[nrule].unknown)
     4697                                                        && !rules[nrule].wrapped && !rules[nrule].additional && !rules[nrule].fallback
     4698                                                ) {
     4699
     4700                                                // var old_date_matching_rules = date_matching_rules;
     4701                                                date_matching_rules = [];
     4702                                                // for (var nrule = 0; nrule < old_date_matching_rules.length; nrule++) {
     4703                                                //      if (!rules[old_date_matching_rules[nrule]].wrapped)
     4704                                                //              date_matching_rules.push(nrule);
    40254705                                                // }
    40264706                                        }
    4027                                         date_matching_blocks.push(nblock);
     4707                                        date_matching_rules.push(nrule);
    40284708                                }
    40294709                        }
    40304710
    4031                         block:
    4032                         for (var nblock = 0; nblock < date_matching_blocks.length; nblock++) {
    4033                                 var block = date_matching_blocks[nblock];
    4034 
    4035                                 // console.log('Processing block ' + block + ':\t' + blocks[block].comment + '    with date', date,
    4036                                 //      'and', blocks[block].time.length, 'time selectors');
     4711                        rule:
     4712                        for (var nrule = 0; nrule < date_matching_rules.length; nrule++) {
     4713                                var rule = date_matching_rules[nrule];
     4714
     4715                                // console.log('Processing rule ' + rule + ': with date ' + date
     4716                                        // + ' and ' + rules[rule].time.length + ' time selectors (comment: "' + rules[rule].comment + '").');
    40374717
    40384718                                // there is no time specified, state applies to the whole day
    4039                                 if (blocks[block].time.length == 0) {
     4719                                if (rules[rule].time.length === 0) {
    40404720                                        // console.log('there is no time', date);
    4041                                         if (!blocks[block].fallback || (blocks[block].fallback && !(resultstate || unknown))) {
    4042                                                 resultstate = blocks[block].meaning;
    4043                                                 unknown     = blocks[block].unknown;
    4044                                                 match_block = block;
    4045 
    4046                                                 if (typeof blocks[block].comment != 'undefined')
    4047                                                         comment     = blocks[block].comment;
     4721                                        if (!rules[rule].fallback || (rules[rule].fallback && !(resultstate || unknown))) {
     4722                                                resultstate = rules[rule].meaning;
     4723                                                unknown     = rules[rule].unknown;
     4724                                                match_rule = rule;
     4725
     4726                                                if (typeof rules[rule].comment != 'undefined')
     4727                                                        comment     = rules[rule].comment;
    40484728                                                else if (typeof comment == 'object') // holiday name
    40494729                                                        comment = comment[0];
    40504730
    4051                                                 if (blocks[block].fallback)
    4052                                                         break block; // fallback block matched, no need for checking the rest
     4731                                                // if (rules[rule].fallback)
     4732                                                        // break rule; // fallback rule matched, no need for checking the rest
     4733                                                // WRONG: What if closing rules follow?
    40534734                                        }
    40544735                                }
    40554736
    4056                                 for (var timesel = 0; timesel < blocks[block].time.length; timesel++) {
    4057                                         var res = blocks[block].time[timesel](date);
     4737                                for (var timesel = 0; timesel < rules[rule].time.length; timesel++) {
     4738                                        var res = rules[rule].time[timesel](date);
    40584739
    40594740                                        // console.log('res:', res);
    40604741                                        if (res[0]) {
    4061                                                 if (!blocks[block].fallback || (blocks[block].fallback && !(resultstate || unknown))) {
    4062                                                         resultstate = blocks[block].meaning;
    4063                                                         unknown     = blocks[block].unknown;
    4064                                                         match_block = block;
    4065 
    4066                                                         if (typeof blocks[block].comment == 'string') // only use comment if one is specified
    4067                                                                 comment     = blocks[block].comment;
     4742                                                if (!rules[rule].fallback || (rules[rule].fallback && !(resultstate || unknown))) {
     4743                                                        resultstate = rules[rule].meaning;
     4744                                                        unknown     = rules[rule].unknown;
     4745                                                        match_rule  = rule;
     4746
     4747                                                        if (typeof rules[rule].comment == 'string') // only use comment if one is specified
     4748                                                                comment     = rules[rule].comment;
    40684749                                                        else if (typeof comment == 'object') // holiday name
    40694750                                                                comment = comment[0];
    40704751                                                        else if (comment === 'Specified as open end. Closing time was guessed.')
    4071                                                                 comment = blocks[block].comment;
     4752                                                                comment = rules[rule].comment;
    40724753
    40734754                                                        // open end
    4074                                                         if (typeof res[2] == 'boolean' && res[2] && (resultstate || unknown)) {
     4755                                                        if (res[2] === true && (resultstate || unknown)) {
    40754756                                                                if (typeof comment == 'undefined')
    40764757                                                                        comment = 'Specified as open end. Closing time was guessed.';
     4758
    40774759                                                                resultstate = false;
    40784760                                                                unknown     = true;
     4761
     4762                                                                /* Hack to make second rule in '07:00+,12:00-16:00; 16:00-24:00 closed "needed because of open end"' obsolete {{{ */
     4763                                                                if (typeof rules[rule].time[timesel+1] == 'function') {
     4764
     4765                                                                        var next_res = rules[rule].time[timesel+1](date);
     4766                                                                        if (  !next_res[0]
     4767                                                                                // && next_res[2]
     4768                                                                                && typeof next_res[1] == 'object'
     4769                                                                                // && getValueForDate(next_res[1], true) != getValueForDate(date, true) // Just to be sure.
     4770                                                                                && rules[rule].time[timesel](new Date(date.getTime() - 1))[0]
     4771                                                                                /* To keep the following two apart:
     4772                                                                                 *       'sunrise-14:00,14:00+',
     4773                                                                                 *   '07:00+,12:00-16:00',
     4774                                                                                 */
     4775                                                                                ) {
     4776
     4777                                                                                // console.log("07:00+,12:00-16:00 matched.");
     4778
     4779                                                                                resultstate = false;
     4780                                                                                unknown     = false;
     4781                                                                        }
     4782                                                                }
     4783
     4784                                                                /* Hack to handle '17:00+,13:00-02:00' {{{ */
     4785                                                                /* Not enabled. To complicated, just don‘t use them …
     4786                                                                 * It gets even crazier …
     4787                                                                 * Time wrapping over midnight is
     4788                                                                 * stored in the next internal rule:
     4789                                                                 * '17:00-00:00 unknown "Specified as open end. Closing time was guessed.", 13:00-00:00 open' // First internal rule.
     4790                                                                 * + ', ' overwritten part: 00:00-03:00 open + '00:00-02:00 open', // Second internal rule.
     4791                                                                 */
     4792                                                                if (    false
     4793                                                                                && typeof rules[rule-1] == 'object'
     4794                                                                                && rules[rule].build_from_token_rule.toString() == rules[rule-1].build_from_token_rule.toString()
     4795                                                                                && typeof rules[rule] == 'object'
     4796                                                                                && rules[rule].build_from_token_rule.toString() == rules[rule].build_from_token_rule.toString()
     4797                                                                                ) {
     4798
     4799                                                                        var last_wrapping_time_selector = rules[rule].time[rules[rule].time.length - 1];
     4800                                                                        var last_w_res = last_wrapping_time_selector(new Date(date.getTime() - 1));
     4801                                                                        // console.log(last_w_res);
     4802
     4803                                                                        if (    last_w_res[0]
     4804                                                                                        && typeof last_w_res[2] == 'undefined'
     4805                                                                                        && (typeof last_w_res[2] == 'undefined' || last_w_res[2] === false) // Not match for 'Tu 23:59-40:00+'
     4806                                                                                        &&  typeof last_w_res[1] == 'object'
     4807                                                                                        && date.getTime() == last_w_res[1].getTime()
     4808                                                                                ) {
     4809
     4810                                                                                // '05:00-06:00,17:00+,13:00-02:00',
     4811
     4812                                                                                // console.log("17:00+,13:00-02:00 matched.");
     4813                                                                                // console.log(JSON.stringify(rules, null, '    '));
     4814
     4815                                                                                resultstate = false;
     4816                                                                                unknown     = false;
     4817                                                                        }
     4818                                                                /* }}} */
     4819                                                                }
     4820                                                                /* }}} */
    40794821                                                        }
    40804822
    4081                                                         if (blocks[block].fallback) {
     4823                                                        if (rules[rule].fallback) {
    40824824                                                                if (typeof changedate === 'undefined' || (typeof res[1] !== 'undefined' && res[1] < changedate))
    40834825                                                                        changedate = res[1];
    40844826
    4085                                                                 // break block; // Fallback block matched, no need for checking the rest.
    4086                                                                 // WRONG: What if 'off' is used after fallback block.
     4827                                                                // break rule; // Fallback rule matched, no need for checking the rest.
     4828                                                                // WRONG: What if 'off' is used after fallback rule.
    40874829                                                        }
    40884830                                                }
     
    40934835                        }
    40944836
    4095                         // console.log('changedate', changedate, resultstate, comment, match_block);
    4096                         return [ resultstate, changedate, unknown, comment, match_block ];
    4097                 }
     4837                        // console.log('changedate', changedate, resultstate, comment, match_rule);
     4838                        return [ resultstate, changedate, unknown, comment, match_rule ];
     4839                };
    40984840                // }}}
    40994841
    4100                 // Generate prettified value based on tokens {{{
    4101                 function prettifySelector(tokens, at, last_at, conf, used_parseTimeRange) {
     4842                /* Generate prettified value for selector based on tokens. {{{
     4843                 *
     4844                 * :param tokens: List of token objects.
     4845                 * :param at: Position where to start.
     4846                 * :param last_at: Position where to stop.
     4847                 * :param conf: Configuration options.
     4848                 * :returns: Prettified value.
     4849                 */
     4850                function prettifySelector(tokens, selector_start, selector_end, selector_type, conf) {
     4851
    41024852                        var value = '';
    4103                         var start_at = at;
    4104                         while (at < last_at) {
    4105                                 if (matchTokens(tokens, at, 'weekday')) { // FIXME
     4853                        var at = selector_start;
     4854                        while (at <= selector_end) {
     4855                                // console.log('At: ' + at + ', token: ' + tokens[at]);
     4856                                if (matchTokens(tokens, at, 'weekday')) {
    41064857                                        if (!conf.leave_weekday_sep_one_day_betw
    4107                                                 && at - start_at > 1 && (matchTokens(tokens, at-1, ',') || matchTokens(tokens, at-1, '-'))
     4858                                                && at - selector_start > 1 && (matchTokens(tokens, at-1, ',') || matchTokens(tokens, at-1, '-'))
    41084859                                                && matchTokens(tokens, at-2, 'weekday')
    4109                                                 && tokens[at][0] == (tokens[at-2][0] + 1) % 7)  {
     4860                                                && tokens[at][0] == (tokens[at-2][0] + 1) % 7) {
    41104861                                                        value = value.substring(0, value.length - 1) + conf.sep_one_day_between;
    41114862                                        }
    41124863                                        value += weekdays[tokens[at][0]];
    4113                                 } else if (at - start_at > 0 // e.g. '09:0' -> '09:00'
    4114                                                 && used_parseTimeRange > 0
     4864                                } else if (at - selector_start > 0 // e.g. '09:0' -> '09:00'
     4865                                                && selector_type == 'time'
    41154866                                                && matchTokens(tokens, at-1, 'timesep')
    41164867                                                && matchTokens(tokens, at, 'number')) {
    41174868                                        value += (tokens[at][0] < 10 ? '0' : '') + tokens[at][0].toString();
    4118                                 } else if (used_parseTimeRange > 0 // e.g. '9:00' -> ' 09:00'
    4119                                                 && conf.leading_zero_hour
     4869                                } else if (selector_type == 'time' // e.g. '9:00' -> ' 09:00'
     4870                                                && conf.zero_pad_hour
    41204871                                                && at != tokens.length
    41214872                                                && matchTokens(tokens, at, 'number')
     
    41234874                                        value += (
    41244875                                                        tokens[at][0] < 10 ?
    4125                                                                 (tokens[at][0] == 0 && conf.one_zero_if_hour_zero ?
     4876                                                                (tokens[at][0] === 0 && conf.one_zero_if_hour_zero ?
    41264877                                                                 '' : '0') :
    41274878                                                                '') + tokens[at][0].toString();
    4128                                 } else if (used_parseTimeRange > 0 // e.g. '9-18' -> '09:00-18:00'
    4129                                                 && at + 2 < last_at
     4879                                } else if (selector_type == 'time' // e.g. '9-18' -> '09:00-18:00'
     4880                                                && at + 2 <= selector_end
    41304881                                                && matchTokens(tokens, at, 'number')
    41314882                                                && matchTokens(tokens, at+1, '-')
    41324883                                                && matchTokens(tokens, at+2, 'number')) {
    41334884                                        value += (tokens[at][0] < 10 ?
    4134                                                         (tokens[at][0] == 0 && conf.one_zero_if_hour_zero ? '' : '0')
     4885                                                        (tokens[at][0] === 0 && conf.one_zero_if_hour_zero ? '' : '0')
    41354886                                                        : '') + tokens[at][0].toString();
    41364887                                        value += ':00-'
     
    41424893                                } else if (matchTokens(tokens, at, 'closed')) {
    41434894                                        value += (conf.leave_off_closed ? tokens[at][0] : conf.keyword_for_off_closed);
    4144                                 } else if (at - start_at > 0 && matchTokens(tokens, at, 'number')
    4145                                                 && (matchTokens(tokens, at-1, 'month')
    4146                                                 ||  matchTokens(tokens, at-1, 'week')
     4895                                } else if (at - selector_start > 0 && matchTokens(tokens, at, 'number')
     4896                                                && (matchTokens(tokens, at-1, 'month') && selector_type == 'month'
     4897                                                ||  matchTokens(tokens, at-1, 'week')  && selector_type == 'week'
    41474898                                                )) {
    4148                                         value += ' ' + tokens[at][0];
    4149                                 } else if (at - start_at > 0 && matchTokens(tokens, at, 'month')
     4899                                        value += ' '
     4900                                                + (conf.zero_pad_month_and_week_numbers && tokens[at][0] < 10 ? '0' : '')
     4901                                                + tokens[at][0];
     4902                                } else if (at - selector_start > 0 && matchTokens(tokens, at, 'month')
    41504903                                                && matchTokens(tokens, at-1, 'year')) {
    41514904                                        value += ' ' + months[[tokens[at][0]]];
    4152                                 } else if (at - start_at > 0 && matchTokens(tokens, at, 'event')
     4905                                } else if (at - selector_start > 0 && matchTokens(tokens, at, 'event')
    41534906                                                && matchTokens(tokens, at-1, 'year')) {
    41544907                                        value += ' ' + tokens[at][0];
    41554908                                } else if (matchTokens(tokens, at, 'month')) {
    41564909                                        value += months[[tokens[at][0]]];
    4157                                         if (at + 1 < last_at && matchTokens(tokens, at+1, 'weekday'))
     4910                                        if (at + 1 <= selector_end && matchTokens(tokens, at+1, 'weekday'))
    41584911                                                value += ' ';
    4159                                 } else if (at + 2 < last_at
     4912                                } else if (at + 2 <= selector_end
    41604913                                                && (matchTokens(tokens, at, '-') || matchTokens(tokens, at, '+'))
    41614914                                                && matchTokens(tokens, at+1, 'number', 'calcday')) {
    41624915                                        value += ' ' + tokens[at][0] + tokens[at+1][0] + ' day' + (Math.abs(tokens[at+1][0]) == 1 ? '' : 's');
    41634916                                        at += 2;
     4917                                } else if (at == selector_end
     4918                                                && selector_type == 'weekday'
     4919                                                && tokens[at][0] == ':') {
     4920                                        // Do nothing.
    41644921                                } else {
    4165                                         // if (matchTokens(tokens, at, 'open') || matchTokens(tokens, at, 'unknown'))
    4166                                         //      value += ' ';
    4167 
    41684922                                        value += tokens[at][0].toString();
    41694923                                }
    41704924                                at++;
    41714925                        }
    4172                         return value + ' ';
     4926                        return value;
    41734927                }
    41744928                // }}}
     
    41794933                //======================================================================
    41804934
    4181                 //======================================================================
    41824935                // Iterator interface {{{
    4183                 //======================================================================
    41844936                this.getIterator = function(date) {
    41854937                        return new function(oh) {
     
    41904942                                var state = oh.getStatePair(date);
    41914943
     4944                                /* getDate {{{ */
     4945                                this.getDate = function() {
     4946                                        return prevstate[1];
     4947                                };
     4948                                /* }}} */
     4949
     4950                                /* setDate {{{ */
    41924951                                this.setDate = function(date) {
    41934952                                        if (typeof date != 'object')
    4194                                                 throw 'Date as parameter needed.';
     4953                                                throw 'Date parameter needed.';
    41954954
    41964955                                        prevstate = [ undefined, date, undefined, undefined, undefined ];
    41974956                                        state     = oh.getStatePair(date);
    4198                                 }
    4199 
    4200                                 this.getDate = function() {
    4201                                         return prevstate[1];
    4202                                 }
    4203 
     4957                                };
     4958                                /* }}} */
     4959
     4960                                /* getState: Check whether facility is `open' {{{ */
    42044961                                this.getState = function() {
    42054962                                        return state[0];
    4206                                 }
    4207 
     4963                                };
     4964                                /* }}} */
     4965
     4966                                /* getUnknown: Checks whether the opening state is conditional or unknown {{{ */
    42084967                                this.getUnknown = function() {
    42094968                                        return state[2];
    4210                                 }
    4211 
     4969                                };
     4970                                /* }}} */
     4971
     4972                                /* getStateString: Get state string. Either 'open', 'unknown' or 'closed' {{{ */
    42124973                                this.getStateString = function(past) {
    42134974                                        return (state[0] ? 'open' : (state[2] ? 'unknown' : (past ? 'closed' : 'close')));
    4214                                 }
    4215 
     4975                                };
     4976                                /* }}} */
     4977
     4978                                /* getComment: Get the comment, undefined in none {{{ */
    42164979                                this.getComment = function() {
    42174980                                        return state[3];
    4218                                 }
    4219 
    4220                                 this.getMatchingRule = function(user_conf) {
     4981                                };
     4982                                /* }}} */
     4983
     4984                                /* getMatchingRule: Get the rule which matched thus deterrents the current state {{{ */
     4985                                this.getMatchingRule = function() {
    42214986                                        if (typeof state[4] == 'undefined')
    42224987                                                return undefined;
    42234988
    4224                                         if (typeof user_conf != 'object')
    4225                                                 var user_conf = {};
    4226                                         for (key in default_prettify_conf) {
    4227                                                 if (typeof user_conf[key] == 'undefined')
    4228                                                         user_conf[key] = default_prettify_conf[key];
    4229                                         }
    4230 
    4231                                         var really_done_with_warnings = done_with_warnings; // getWarnings can be called later.
    4232                                         done_with_warnings = true;
    4233                                         prettified_value = '';
    4234                                         var selectors = { // Not really needed. This whole thing is only necessary because of the token used for additional blocks.
    4235                                                 time: [], weekday: [], holiday: [], week: [], month: [], monthday: [], year: [], wraptime: [],
    4236 
    4237                                                 fallback: false, // does not matter
    4238                                                 additional: false,
    4239                                                 meaning: true,
    4240                                                 unknown: false,
    4241                                                 comment: undefined,
    4242                                         };
    4243 
    4244                                         // token block index used to build the selectors for this block.
    4245                                         var token_block = blocks[state[4]].build_from_token_block;
    4246                                         parseGroup(tokens[token_block[0]][0], token_block[1], selectors, state[4], user_conf);
    4247 
    4248                                         if (prettified_value[prettified_value.length - 1] == ',')
    4249                                                 prettified_value = prettified_value.substr(0, prettified_value.length - 1);
    4250 
    4251                                         done_with_warnings = really_done_with_warnings;
    4252 
    4253                                         return prettified_value;
    4254                                 }
    4255 
     4989                                        return rules[state[4]].build_from_token_rule[2];
     4990                                };
     4991                                /* }}} */
     4992
     4993                                /* advance: Advances to the next position {{{ */
    42564994                                this.advance = function(datelimit) {
    42574995                                        if (typeof datelimit === 'undefined')
     
    42665004
    42675005                                                // console.log('\n' + 'previous check time:', prevstate[1]
    4268                                                 //      + ', current check time:',
    4269                                                 //      // (state[1].getHours() < 10 ? '0' : '') + state[1].getHours() +
    4270                                                 //      // ':'+(state[1].getMinutes() < 10 ? '0' : '')+ state[1].getMinutes(), state[1].getDate(),
    4271                                                 //      state[1],
    4272                                                 //      (state[0] ? 'open' : (state[2] ? 'unknown' : 'closed')) + ', comment:', state[3]);
     5006                                                        // + ', current check time:',
     5007                                                        // // (state[1].getHours() < 10 ? '0' : '') + state[1].getHours() +
     5008                                                        // // ':'+(state[1].getMinutes() < 10 ? '0' : '')+ state[1].getMinutes(), state[1].getDate(),
     5009                                                        // state[1],
     5010                                                        // (state[0] ? 'open' : (state[2] ? 'unknown' : 'closed')) + ', comment:', state[3]);
    42735011
    42745012                                                // We're going backwards or staying at place.
     
    42865024                                        } while (state[0] === prevstate[0] && state[2] === prevstate[2] && state[3] === prevstate[3]);
    42875025                                        return true;
    4288                                 }
     5026                                };
     5027                                /* }}} */
    42895028                        }(this);
    4290                 }
     5029                };
    42915030                // }}}
    42925031
    42935032                // Simple API {{{
    42945033
    4295                 // Get parse warnings.
    4296                 // Returns an empty string if there are no warnings.
     5034                this.getState = function(date) {
     5035                        var it = this.getIterator(date);
     5036                        return it.getState();
     5037                };
     5038
     5039                this.getUnknown = function(date) {
     5040                        var it = this.getIterator(date);
     5041                        return it.getUnknown();
     5042                };
     5043
     5044                this.getStateString = function(date, past) {
     5045                        var it = this.getIterator(date);
     5046                        return it.getStateString(past);
     5047                };
     5048
     5049                this.getComment = function(date) {
     5050                        var it = this.getIterator(date);
     5051                        return it.getComment();
     5052                };
     5053
     5054                this.getMatchingRule = function(date) {
     5055                        var it = this.getIterator(date);
     5056                        return it.getMatchingRule();
     5057                };
     5058
     5059                /* Not available for iterator API {{{ */
     5060                /* getWarnings: Get warnings, empty list if none {{{ */
    42975061                this.getWarnings = function() {
    42985062                        var it = this.getIterator();
    42995063                        return getWarnings(it);
    4300                 }
    4301 
    4302                 // Get a nicely formated value.
    4303                 this.prettifyValue = function(user_conf) {
    4304                         if (typeof user_conf != 'object')
    4305                                 var user_conf = {};
    4306 
    4307                         for (key in default_prettify_conf) {
    4308                                 if (typeof user_conf[key] == 'undefined')
    4309                                         user_conf[key] = default_prettify_conf[key];
    4310                         }
    4311 
    4312                         var really_done_with_warnings = done_with_warnings; // getWarnings can be called later.
    4313                         done_with_warnings = true;
    4314 
    4315                         prettified_value = '';
    4316                         for (var nblock = 0; nblock < tokens.length; nblock++) {
    4317                                 if (tokens[nblock][0].length == 0) continue;
    4318                                 // Block does contain nothing useful e.g. second block of '10:00-12:00;' (empty) which needs to be handled.
    4319 
    4320                                 if (nblock != 0)
    4321                                         prettified_value += (tokens[nblock][1]
    4322                                                 ?  user_conf.block_sep_string + '|| '
    4323                                                 : (user_conf.print_semicolon ? ';' : '') + user_conf.block_sep_string);
    4324 
    4325                                 var continue_at = 0;
    4326                                 do {
    4327                                         if (continue_at == tokens[nblock][0].length) break;
    4328                                         // Block does contain nothing useful e.g. second block of '10:00-12:00,' (empty) which needs to be handled.
    4329 
    4330                                         var selectors = { // Not really needed. This whole thing is only necessary because of the token used for additional blocks.
    4331                                                 time: [], weekday: [], holiday: [], week: [], month: [], monthday: [], year: [], wraptime: [],
    4332 
    4333                                                 fallback: tokens[nblock][1],
    4334                                                 additional: continue_at ? true : false,
    4335                                                 meaning: true,
    4336                                                 unknown: false,
    4337                                                 comment: undefined,
    4338                                         };
    4339 
    4340                                         continue_at = parseGroup(tokens[nblock][0], continue_at, selectors, nblock, user_conf);
    4341 
    4342                                         if (typeof continue_at == 'object') {
    4343                                                 continue_at = continue_at[0];
    4344                                                 prettified_value += user_conf.block_sep_string;
    4345                                         } else {
    4346                                                 continue_at = 0;
    4347                                         }
    4348 
    4349                                 } while (continue_at)
    4350                         }
    4351 
    4352                         done_with_warnings = really_done_with_warnings;
    4353 
    4354                         return prettified_value;
    4355                 }
    4356 
    4357                 // Check whether facility is `open' on the given date (or now).
    4358                 this.getState = function(date) {
    4359                         var it = this.getIterator(date);
    4360                         return it.getState();
    4361                 }
    4362 
    4363                 // If the state of a amenity is conditional. Conditions can be expressed in comments.
    4364                 // True will only be returned if the state is false as the getState only
    4365                 // returns true if the amenity is really open. So you may want to check
    4366                 // the result of getUnknown if getState returned false.
    4367                 this.getUnknown = function(date) {
    4368                         var it = this.getIterator(date);
    4369                         return it.getUnknown();
    4370                 }
    4371 
    4372                 // Return state string. Either 'open', 'unknown' or 'closed'.
    4373                 this.getStateString = function(date, past) {
    4374                         var it = this.getIterator(date);
    4375                         return it.getStateString(past);
    4376                 }
    4377 
    4378                 // Returns the comment.
    4379                 // If no comment is specified this function will return undefined.
    4380                 this.getComment = function(date) {
    4381                         var it = this.getIterator(date);
    4382                         return it.getComment();
    4383                 }
    4384 
    4385                 // Return the block which matched thus deterrents the current state.
    4386                 this.getMatchingRule = function(date) {
    4387                         var it = this.getIterator(date);
    4388                         return it.getMatchingRule();
    4389                 }
    4390 
    4391                 // Returns time of next status change.
     5064                };
     5065                /* }}} */
     5066
     5067                /* prettifyValue: Get a nicely formated value {{{ */
     5068                this.prettifyValue = function(argument_hash) {
     5069                        this.getWarnings();
     5070                        /* getWarnings has to be run before prettifyValue because some
     5071                         * decisions if a certain aspect makes sense to prettify or not
     5072                         * are based on the warnings.
     5073                         * Basically, both functions depend on each other in some way :(
     5074                         * See done_with_selector_reordering.
     5075                         */
     5076                        return prettifyValue(argument_hash);
     5077                };
     5078                /* }}} */
     5079
     5080                /* getNextChange: Get time of next status change {{{ */
    43925081                this.getNextChange = function(date, maxdate) {
    43935082                        var it = this.getIterator(date);
     
    43955084                                return undefined;
    43965085                        return it.getDate();
    4397                 }
    4398 
    4399                 // Checks whether open intervals are same for every week.
     5086                };
     5087                /* }}} */
     5088
     5089                /* isWeekStable: Checks whether open intervals are same for every week. {{{ */
    44005090                this.isWeekStable = function() {
    44015091                        return week_stable;
    4402                 }
    4403                 // }}}
     5092                };
     5093                /* }}} */
     5094                /* }}} */
     5095                /* }}} */
    44045096
    44055097                // High-level API {{{
    44065098
    4407                 // return array of open intervals between two dates
     5099                /* getOpenIntervals: Get array of open intervals between two dates {{{ */
    44085100                this.getOpenIntervals = function(from, to) {
    44095101                        var res = [];
     
    44165108                        while (it.advance(to)) {
    44175109                                if (it.getState() || it.getUnknown()) {
    4418                                         if (res.length != 0 && typeof res[res.length - 1][1] == 'undefined') {
     5110                                        if (res.length !== 0 && typeof res[res.length - 1][1] == 'undefined') {
    44195111                                                // last state was also open or unknown
    44205112                                                res[res.length - 1][1] = it.getDate();
     
    44225114                                        res.push([it.getDate(), undefined, it.getUnknown(), it.getComment()]);
    44235115                                } else {
    4424                                         if (res.length != 0 && typeof res[res.length - 1][1] == 'undefined') {
     5116                                        if (res.length !== 0 && typeof res[res.length - 1][1] == 'undefined') {
    44255117                                                // only use the first time as closing/change time and ignore closing times which might follow
    44265118                                                res[res.length - 1][1] = it.getDate();
     
    44335125
    44345126                        return res;
    4435                 }
    4436 
    4437                 // return total number of milliseconds a facility is open within a given date range
     5127                };
     5128                /* }}} */
     5129
     5130                /* getOpenDuration: Get total number of milliseconds a facility is open,unknown within a given date range {{{ */
    44385131                this.getOpenDuration = function(from, to) {
    44395132                // console.log('-----------');
     
    44825175
    44835176                        return [ open, unknown ];
    4484                 }
    4485                 // }}}
    4486                 // }}}
    4487         }
     5177                };
     5178                /* }}} */
     5179                /* }}} */
     5180                /* }}} */
     5181        };
    44885182}));
    44895183// vim: set ts=4 sw=4 tw=0 noet foldmarker={{{,}}} foldlevel=0 foldmethod=marker :
Note: See TracChangeset for help on using the changeset viewer.