Library to parse and process the opening_hours tag from OpenStreetMap data
Clone or download
Pull request Compare This branch is 1252 commits ahead, 2 commits behind AMDmi3:master.
Latest commit 7d3995a Nov 12, 2018
Failed to load latest commit information.
css Fix #257 in HTML5 `input[size]` has no effect for `input[type="number"]` Apr 15, 2018
docs Move holidays README Feb 21, 2017
examples Fix warning: "type" attribute is unnecessary for JavaScript resources Nov 5, 2018
holidays Add new states for PH Reformationstag Oct 29, 2018
hooks No need to test the uglified version before every commit. Aug 19, 2015
img Added favicon with transparent background used for the GitHub org. Nov 29, 2015
js Include for now in this repo Nov 5, 2018
licenses Rename COPYING.LGPL-3.0 file to the more common LICENSE Mar 2, 2017
locales small german fixes Apr 20, 2018
submodules Include for now in this repo Nov 5, 2018
.editorconfig WIP: Wrote gen_word_error_correction. Related to #107. Nov 15, 2015
.gitignore Add `opening_hours.js` to .gitignore which is not tracked anymore Feb 26, 2017
.gitmodules Include for now in this repo Nov 5, 2018
.travis.yml Use `node` instead of the legacy `nodejs` executable name Nov 5, 2018
.yamllint Enhance YAML files and lint them using `yamllint` Feb 27, 2017
CHANGELOG.rst Merge branch '#271' Nov 5, 2018 Updated URL to this repo after I moved it to the opening-hours org. Nov 29, 2015
Gen_taginfo_json.hx Optimized Gen_taginfo_json.hx. Dec 22, 2015
LICENSE Rename COPYING.LGPL-3.0 file to the more common LICENSE Mar 2, 2017
Makefile Use `node` instead of the legacy `nodejs` executable name Nov 5, 2018 Improve spelling Mar 5, 2017
PH_SH_exporter.js Use `node` instead of the legacy `nodejs` executable name Nov 5, 2018 Remove discontinued Issuestats from README Nov 9, 2018
banner.js Fix source file reference after switch to rollup. Fixes #198. Mar 5, 2017
benchmark.js Use `node` instead of the legacy `nodejs` executable name Nov 5, 2018
bower.json Bump version in bower.json and semi automate for next release Mar 19, 2017
check_for_new_taginfo_data.js Use `node` instead of the legacy `nodejs` executable name Nov 5, 2018
gen_taginfo_json.js Use `node` instead of the legacy `nodejs` executable name Nov 5, 2018
gen_weekly_task_report Wrote gen_weekly_task_report. I should not have written it in Bash … May 8, 2015
gen_word_error_correction.js Use `node` instead of the legacy `nodejs` executable name Nov 5, 2018
index.html Fix warning: "type" attribute is unnecessary for JavaScript resources Nov 5, 2018
index.js Integrate and fix Ivory Coast (CI) PH definition into the library Jun 22, 2018
interactive_testing.js Use `node` instead of the legacy `nodejs` executable name Nov 5, 2018 Added debug warning for missing data… May 8, 2015
package.json Drop support for NodeJS 4.0 and 5.0. We now require 6.0. Nov 5, 2018
real_test.js Use `node` instead of the legacy `nodejs` executable name Nov 5, 2018
regex_search.js Use `node` instead of the legacy `nodejs` executable name Nov 5, 2018 Switch to JOSM https calls Sep 21, 2016
related_tags.txt fee tag does not use the opening_hours syntax directly. Nov 20, 2015
rollup.config.js rollup: update config file Jan 2, 2018
stats_for_boundaries.txt Wrote merge_all_stats_into_country script. Apr 23, 2015
taginfo.json Rework the way Nominatim responses are handled. Closes #167. Mar 5, 2017
taginfo_template.json Rework the way Nominatim responses are handled. Closes #167. Mar 5, 2017 Add test for Austria SH. Updates #219. Jun 9, 2018
test.en.log Add test for Austria SH. Updates #219. Jun 9, 2018
test.js Use `node` instead of the legacy `nodejs` executable name Nov 5, 2018
uglifyjs.log Fix accidental global variable `lang` Feb 26, 2017


Build Status license NPM required Node.js version version NPM monthly downloads via NPM total downloads via NPM


The opening_hours tag is used in OpenStreetMap to describe time ranges when a facility (for example, a café) is open. As it has pretty complex syntax which requires special parsing and additional processing to extract some useful information (e.g. whether a facility is open at a specific time, next time it's going to open/close, or a readable set of working hours), this library is being developed.

Examples of some complex opening_hours values:

Mo,Tu,Th,Fr 12:00-18:00; Sa,PH 12:00-17:00; Th[3],Th[-1] off
Mo-Fr 12:00-18:00; We off; Sa,PH 12:00-17:00; Th[3],Th[-1] off

a library which is open from 12:00 to 18:00 on workdays (Mo-Fr) except Wednesday, and from 12:00 to 17:00 on Saturday and public holidays. It also has breaks on the third and last Thursday of each month.

open; Tu-Su 08:30-09:00 off; Tu-Su,PH 14:00-14:30 off; Mo 08:00-13:00 off

around-the-clock shop with some breaks.

Table of Contents

evaluation tool

Please have a look at the evaluation tool which can give you an impression how this library can be used and what it is capable of.

A mirror is setup up under:


For Developer

Just clone the repository:

git clone --recursive

and install it’s dependencies (execute inside the repository):

npm install

Web developer

If you are a web developer and want to use this library you can do so by including the current version from here:

To get started checkout the simple_index.html file.

NodeJS developer

This library is packaged with npm and is available under the name opening_hours so you should have no problems using it.


The version number consists of a major release, minor release and patch level (separated by a dot).

For version 2.2.0 and all later, Semantic Versioning is used:

  • The major release is only increased if the release breaks backward compatibility.
  • The minor release is increased if new features are added.
  • The patch level is increased to bundle a bunch of commits (minor changes like bug fixes and improvements) into a new tested version.

Check releases on GitHub for a list of the releases and their Changelog.


var oh = new opening_hours('We 12:00-14:00');

var from = new Date("01 Jan 2012");
var to   = new Date("01 Feb 2012");

// high-level API
    var intervals = oh.getOpenIntervals(from, to);
    for (var i in intervals)
        console.log('We are ' + (intervals[i][2] ? 'maybe ' : '')
            + 'open from ' + (intervals[i][3] ? '("' + intervals[i][3] + '") ' : '')
            + intervals[i][0] + ' till ' + intervals[i][1] + '.');

    var duration_hours = oh.getOpenDuration(from, to).map(function(x) { return x / 1000 / 60 / 60 });
    if (duration_hours[0])
        console.log('For the given range, we are open for ' + duration_hours[0] + ' hours');
    if (duration_hours[1])
        console.log('For the given range, we are maybe open for ' + duration_hours[1] + ' hours');

// helper function
function getReadableState(startString, endString, oh, past) {
    if (past === true) past = 'd';
    else past = '';

    var output = '';
    if (oh.getUnknown()) {
        output += ' maybe open'
            + (oh.getComment() ? ' but that depends on: "' + oh.getComment() + '"' : '');
    } else {
        output += ' ' + (oh.getState() ? 'open' : 'close' + past)
            + (oh.getComment() ? ', comment "' + oh.getComment() + '"' : '');
    return startString + output + endString + '.';

// simple API
    var state      = oh.getState(); // we use current date
    var unknown    = oh.getUnknown();
    var comment    = oh.getComment();
    var nextchange = oh.getNextChange();

    console.log(getReadableState('We\'re', '', oh, true));

    if (typeof nextchange === 'undefined')
        console.log('And we will never ' + (state ? 'close' : 'open'));
        console.log('And we will '
            + (oh.getUnknown(nextchange) ? 'maybe ' : '')
            + (state ? 'close' : 'open') + ' on ' + nextchange);

// iterator API
    var iterator = oh.getIterator(from);

    console.log(getReadableState('Initially, we\'re', '', iterator, true));

    while (iterator.advance(to))
        console.log(getReadableState('Then we', ' at ' + iterator.getDate(), iterator));

    console.log(getReadableState('And till the end we\'re', '', iterator, true));

Library API

  • var oh = new opening_hours('We 12:00-14:00', nominatim_object, mode);

    • value (mandatory, type: string): Constructs opening_hours object, given the opening_hours tag value.

      Throws an error string if the expression is malformed or unsupported.

    • nominatim_object (optional, type: object or null): In order to calculate the correct times for variable times (e.g. sunrise, dusk, see under Time ranges) the coordinates are needed. To apply the correct holidays (PH) and school holidays (SH) the country code and the state is needed. The only thing you as programmer need to know are the coordinates or preferably the OSM id (for the node, way or relation) of the facility (where the opening hours do apply) anything else can be queried for using reverse geocoding with Nominatim. So just use as second parameter the returned JSON from Nominatim (example URL: and you are good to go. Note that this second parameter is optional. The data returned by Nominatim should be in the local language (the language of the country for which the opening hours apply). If not, accept-language can be used as parameter in the request URL.

      The nominatim_object can also be null in which case a default location will be used. This can be used if you don’t care about correct opening hours for more complex opening_hours values.

    • optional_conf_parm (optional, either of type number of object):

      If this parameter is of the type number then it is interpreted as 'mode' (see below). For the type object, the following keys are defined.

      • 'mode' (type: (integer) number, default: 0): In OSM, the syntax originally designed to describe opening hours, is now used to describe a few other things as well. Some of those other tags work with points in time instead of time ranges. To support this the mode can be specified. Note that it is recommended to not use the mode parameter directly but instead use the tag_key parameter. If there is no mode specified, opening_hours.js will only operate with time ranges and will throw an error when points in times are used in the value.

        • 0: time ranges (opening_hours, lit, …) default
        • 1: points in time
        • 2: both (time ranges and points in time, used by collection_times, service_times, …)
      • 'tag_key' (type: string, default: undefined): The name of the key (Tag key). For example 'opening_hours' or 'lit'. Please always specify this parameter. If you do, the mode will be derived from the 'tag_key' parameter. Default is undefined e.g. no default value.

      • 'map_value' (type: boolean, default: false): Map certain values to different (valid) oh values. For example for the lit tag the value 'yes' is valid but not for opening_hours.js. If this parameter 'yes' is mapped to sunset-sunrise open "specified as yes: At night (unknown time schedule or daylight detection)".

      • 'warnings_severity' (type: number, default: 4): Can be one of the following numbers. The severity levels (including the codes) match the syslog specification. The default is level 4 to not break backwards compatibility. Lower levels e.g. 5 include higher levels e.g. 4.

        4: warning
        5: notice
        6: info
        7: debug
      • 'locale' (type: string, default: i18n.lng() || 'en'): Defines the locale for errors and warnings.

  • var warnings = oh.getWarnings();

    Get warnings which appeared during parsing as human readable string array. Every violation is described in one element of the array. Almost all warnings can be auto corrected and are probably interpreted as indented by the mapper. However, this is not a granite of course.

    This function does also some additional testing and because of that it theoretically possible that this function throws an error like all other functions which go through time.

  • var prettified = oh.prettifyValue(argument_hash);

    Return a prettified version of the opening_hours value. The value is generated by putting the tokens back together to a string.

    The function excepts one optional hash.

    The key 'conf' can hold another hash with configuration options. One example:

        rule_sep_string: '\n',
        print_semicolon: false

    Look in the source code if you need more.

    If the key 'rule_index' is a number then only the corresponding rule will be prettified.

    If the key 'get_internals' is true then an object containing internal stuff will be returned instead. The format of this internal object may change in minor release.

  • var every_week_is_same = oh.isWeekStable();

    Checks whether open intervals are same for every week. Useful for giving a user hint whether time table may change for another week.

  • var is_equal_to = oh.isEqualTo(new opening_hours('We 12:00-16:00'), start_date);

    Check if this opening_hours object has the same meaning as the given opening_hours object (evaluates to the same state for every given time).

    The optional parameter start_date (Date object) specifies the start date at which the comparison will begin.

    is_equal_to is a list:

    1. Boolean which is true if both opening_hours objects have the same meaning, otherwise false.

    2. Object hash containing more information when the given objects differ in meaning. Example:

        "matching_rule": 1,
        "matching_rule_other": 0,
        "deviation_for_time": {
          "1445637600000": [

Simple API

This API is useful for one-shot checks, but for iteration over intervals you should use the more efficient iterator API.

  • var is_open = oh.getState(date);

    Checks whether the facility is open at the given date. You may omit date to use current date.

  • var unknown = oh.getUnknown(date);

    Checks whether the opening state is conditional or unknown at the given date. You may omit date to use current date. Conditions can be expressed in comments. If unknown is true then is_open will be false since it is not sure if it is open.

  • var state_string = oh.getStateString(date, past);

    Return state string at given date. Either 'open', 'unknown' or 'closed?'. You may omit date to use current date.

    If the boolean parameter past is true you will get 'closed' else you will get 'close'.

  • var comment = oh.getComment(date);

    Returns the comment (if one is specified) for the facility at the given date. You may omit date to use current date. Comments can be specified for any state.

    If no comment is specified this function will return undefined.

  • var next_change = oh.getNextChange(date, limit);

    Returns date of next state change. You may omit date to use current date.

    Returns undefined if the next change cannot be found. This may happen if the state won't ever change (e.g. 24/7) or if search goes beyond limit (which is date + ~5 years if omitted).

  • var rule_index = oh.getMatchingRule(date);

    Returns the internal rule number of the matching rule. You may omit date to use current date. A opening_hours string can consist of multiple rules from which one of them is used to evaluate the state for a given point in time. If no rule applies, the state will be closed and this function returns undefined.

    To prettify this rule, you can specify rule_index as parameter for oh.prettifyValue like this:

    var matching_rule = oh.prettifyValue({ 'rule_index': rule_index });

High-level API

Here and below, unless noted otherwise, all arguments are expected to be and all output will be in the form of Date objects.

  • var intervals = oh.getOpenIntervals(from, to);

    Returns array of open intervals in a given range, in a form of

    [ [ from1, to1, unknown1, comment1 ], [ from2, to2, unknown2, comment2 ] ]

    Intervals are cropped with the input range.

  • var duration = oh.getOpenDuration(from, to);

    Returns an array with two durations for a given date range, in milliseconds. The first element is the duration for which the facility is open and the second is the duration for which the facility is maybe open (unknown is used).

Iterator API

  • var iterator = oh.getIterator(date);

    Constructs an iterator which can go through open/close points, starting at date. You may omit date to use current date.

  • var current_date = iterator.getDate();

    Returns current iterator position.

  • iterator.setDate(date);

    Set iterator position to date.

  • var is_open = iterator.getState();

    Returns whether the facility is open at the current iterator position.

  • var unknown = iterator.getUnknown();

    Checks whether the opening state is conditional or unknown at the current iterator position.

  • var state_string = iterator.getStateString(past);

    Return state string. Either 'open', 'unknown' or 'closed?'.

    If the boolean parameter past is true you will get 'closed' else you will get 'close'.

  • var comment = iterator.getComment();

    Returns the comment (if one is specified) for the facility at the current iterator position in time.

    If no comment is specified this function will return undefined.

  • var matching_rule = iterator.getMatchingRule();

    Returns the index of the matching rule starting with zero.

  • var had_advanced = iterator.advance(limit);

    Advances an iterator to the next position, but not further than limit (which is current position + ~5 years if omitted and is used to prevent infinite loop on non-periodic opening_hours, e.g. 24/7), returns whether the iterator was moved.

    For instance, returns false if the iterator would go beyond limit or if there's no next position (24/7 case).


Almost everything from opening_hours definition is supported, as well as some extensions (indicated as EXT below).

WARN indicates that the syntax element is evaluated correctly, but there is a better way to express this. A warning will be shown.

  • See the formal specification as of version 0.6.0.

  • Opening hours consist of multiple rules separated by semicolon (Mo 10:00-20:00; Tu 12:00-18:00) or by other separators as follows.

  • Supports fallback rules (We-Fr 10:00-24:00 open "it is open" || "please call").

    Note that only the rule which starts with || is a fallback rule. Other rules which might follow are considered as normal rules.

  • Supports additional rules or cooperative values (Mo-Fr 08:00-12:00, We 14:00-18:00). A additional rule is treated exactly the same as a normal rule, except that a additional rule does not overwrite the day for which it applies. Note that a additional rule does not use any data from previous or from following rules.

    A rule does only count as additional rule if the previous rule ends with a time range (12:00-14:00, We 16:00-18:00, but does not continue with a time range of course), a comment (12:00-14:00 "call us", We 16:00-18:00) or the keywords 'open', 'unknown' or 'closed' (12:00-14:00 unknown, We 16:00-18:00)

  • Rule may use off keyword to indicate that the facility is closed at that time (Mo-Fr 10:00-20:00; 12:00-14:00 off). closed can be used instead if you like. They mean exactly the same.

  • Rule consists of multiple date (Mo-Fr, Jan-Feb, week 2-10, Jan 10-Feb 10) and time (12:00-16:00, 12:00-14:00,16:00-18:00) conditions

  • If a rule's date condition overlap with previous rule, it overrides (as opposed to extends) the previous rule. E.g. Mo-Fr 10:00-16:00; We 12:00-18:00 means that on Wednesday the facility is open from 12:00 till 18:00, not from 10:00 to 18:00.

    This also applies for time ranges spanning midnight. This is the only way to be consistent. Example: 22:00-02:00; Th 12:00-14:00. By not overriding specifically for midnight ranges, we could get either 22:00-02:00; Th 00:00-02:00,12:00-14:00,22:00-02:00 or 22:00-02:00; Th 00:00-02:00,12:00-14:00 and deciding which interpretation was really intended cannot always be guessed.

  • Date ranges (calendar ranges) can be separated from the time range by a colon (Jan 10-Feb 10: 07:30-12:00) but this is not required. This was implemented to also parse the syntax proposed by Netzwolf.

Time ranges

  • Supports sets of time ranges (10:00-12:00,14:00-16:00)

    • WARN: Accept 10-12,14-16 as abbreviation for the previous example. Please don’t use this as this is not very explicit.
    • Correctly supports ranges wrapping over midnight (10:00-26:00, 10:00-02:00)
  • Supports 24/7 keyword (24/7, which means always open. Use state keywords to express always closed.)

    • WARN: 24/7 is handled as a synonym for 00:00-24:00, so Mo-Fr 24/7 (though not really correct, because of that you should avoid it or replace it with "open". A warning will be given if you use it anyway for that purpose) will be handled correctly

      The use of 24/7 as synonym is never needed and should be avoided in cases where it does not mean 24/7. In cases where a facility is really open 24 hours 7 days a week thats where this value is for.

  • WARN: Supports omitting time range (Mo-Fr; Tu off)

    A warning will be given as this is not very explicit. See issue 49.

  • WARN: Supports space as time interval separator, i.e. Mo 12:00-14:00,16:00-20:00 and Mo 12:00-14:00 16:00-20:00 are the same thing

  • WARN: Supports dot as time separator (12.00-16.00)

  • Complete support for dawn/sunrise/sunset/dusk (variable times) keywords (10:00-sunset, dawn-dusk). To calculate the correct values, the latitude and longitude are required which are included in the JSON returned by Nominatim (see in the Library API how to provide it). The calculation is done by suncalc.

    If the coordinates are missing, constant times will be used (dawn: '05:30', sunrise: '06:00', sunset: '18:00', dusk: '18:30').

    If the end time (second time in time range) is near the sunrise (for instance sunrise-08:00) than it can happen that the sunrise would actually be after 08:00 which would normally be interpreted as as time spanning midnight. But with variable times, this only partly applies. The rule here is that if the end time is lesser than the constant time (or the actual time) for the variable time in the start time (in that example sunrise: '06:00') then it is interpreted as the end time spanning over midnight. So this would be a valid time range spanning midnight: sunrise-05:59.

    A second thing to notice is that if the variable time becomes greater than the end time and the end time is greater than the constant time than this time range will be ignored (e.g sunrise-08:00 becomes 08:03-08:00 for one day, it is ignored for this day).

  • Support calculation with variable times (e.g. sunrise-(sunset-00:30): meaning that the time range ends 30 minutes before sunset; (sunrise+01:02)-(sunset-00:30)).

  • Supports open end (10:00+). It is interpreted as state unknown and the comment "Specified as open end. Closing time was guessed." if there is no comment specified.

    If a facility is open for a fix time followed by open end the shortcut 14:00-17:00+ can be used (see also proposal page).

    Open end applies until the end of the day if the opening time is before 17:00. If the opening time is between 17:00 and 21:59 the open end time ends 10 hours after the opening. And if the opening time is after 22:00 (including 22:00) the closing time will be interpreted as 8 hours after the opening time.

  • 07:00+,12:00-16:00: If an open end time is used in a way that the first time range includes the second one (07:00+ is interpreted as 07:00-24:00 and thus includes the complete 12:00-16:00 time selector), the second time selector cuts of the part which would follow after 16:00.

Points in time

  • In mode 1 or 2, points in time are evaluated. Example: Mo-Fr 12:00,15:00,18:00; Su (sunrise+01:00). Currently a point in time is interpreted as an interval of one minute. It was the easiest thing to implement and has some advantages. See here for discussion.
  • To express regular points in time, like each hour, a abbreviation can be used to express the previous example Mo-Fr 12:00-18:00/03:00 which means from 12:00 to 18:00 every three hours.

Weekday ranges

  • Supports set of weekdays and weekday ranges (Mo-We,Fr)
  • Supports weekdays which wrap to the next week (Fr-Mo)
  • Supports constrained weekdays (Th[1,2-3], Fr[-1])
  • Supports calculations based on constrained weekdays (Sa[-1],Sa[-1] +1 day e.g. last weekend in the month, this also works if Sunday is in the next month)


  • Supports public holidays (open; PH off, PH 12:00-13:00).

  • Support for school holidays (SH 10:00-14:00).

    • Countries with SH definition:

      • Germany, see hc
      • Austria
      • Romania
      • Hungary
  • There can be two cases which need to be separated (this applies for PH and SH):

    1. Mo-Fr,PH: The facility is open Mo-Fr and PH. If PH is a Sunday for example the facility is also open. This is the default case.
    2. EXT: PH Mo-Fr: The facility is only open if a PH falls on Mo-Fr. For example if a PH is on the weekday Wednesday then the facility will be open, if PH is Saturday it will be closed.
  • If there is no comment specified by the rule, the name of the holiday is used as comment.

  • To evaluate the correct holidays, the country code and the state (could be omitted but this will probably result in less correctness) are required which are included in the JSON returned by Nominatim (see in the Library API how to provide it).

  • If your country or state is missing or wrong you can add it or open an issue (and point to a definition of the holidays).

Month ranges

  • Supports set of months and month ranges (Jan,Mar-Apr)
  • Supports months which wrap to the next year (Dec-Jan)

Monthday ranges

  • Supports monthday ranges across multiple months (Jan 01-Feb 03 10:00-20:00)
  • Supports monthday ranges within single month (Jan 01-26 10:00-20:00), with periods as well Jan 01-29/7 10:00-20:00, period equals 1 should be avoided)
  • Supports monthday ranges with years (2013 Dec 31-2014 Jan 02 10:00-20:00, 2012 Jan 23-31 10:00-24:00)
  • Supports monthday ranges based on constrained weekdays (Jan Su[1]-Feb 03 10:00-20:00)
  • Supports calculation based on constrained weekdays in monthday range (Jan Su[1] +1 day-Feb 03 10:00-20:00)
  • Supports movable events like easter (easter - Apr 20: open "Around easter") Note that if easter would be after the 20th of April for one year, this will be interpreted as spanning into the next year currently.
  • Supports calculations based on movable events (2012 easter - 2 days - 2012 easter + 2 days: open "Around easter")
  • Supports multiple monthday ranges separated by a comma (Jan 23-31/3,Feb 1-12,Mar 1)

Week ranges

Year ranges

  • EXT: Supports year ranges (2013,2015,2050-2053,2055/2,2020-2029/3 10:00-20:00)

  • EXT: Supports periodic year (either limited by range or unlimited starting with given year) (2020-2029/3,2055/2 10:00-20:00)

    There is one exception. It is not necessary to use a year range with a period of one (2055-2066/1 10:00-20:00) because this means the same as just the year range without the period (2055-2066 10:00-20:00) and should be expressed like this …

    The oh.getWarnings() function will give you a warning if you use this anyway.

  • EXT: Supports way to say that a facility is open (or closed) from a specified year without limit in the future (2055+ 10:00-20:00)


  • A facility can be in two main states for a given point in time: open (true) or closed (false).

    • But since the state can also depend on other information (e.g. weather depending, call us) than just the time, a third state (called unknown) can be expressed (Mo unknown; Th-Fr 09:00-18:00 open)

      In that case the main state is false and unknown is true for Monday.

  • instead of closed off will also work


  • Supports (additional) comments (Mo unknown "on appointment"; Th-Fr 09:00-18:00 open "female only"; Su closed "really")

    • The string which is delimited by double-quotes can contain any character (except a double-quote sign)
    • unknown can be omitted (just a comment (without state) will also result in unknown)
    • value can also be just a double-quoted string ("on appointment") which will result in unknown for any given time.


This project has become so complex that development without extensive testing would be madness.

Regression testing

node.js based test framework is bundled. You can run it with node test.js or with make check. Note that the number of lines of the test framework almost match up with the number of lines of the actual implementation :)

The current results of this test are also tracked in the repository and can be viewed here. Note that this file uses ANSI escape code which can be interpreted by cat in the terminal. make check compares the test output with the output from the last commit and shows you a diff.

Testing with real data

Large scale

To see how this library performances in the real OpenStreetMap world you can run make osm-tag-data-check or node real_test.js (data needs to be exported first) to try to process every value which uses the opening_hours syntax from taginfo with this library.

Currently (Mai 2015) this library can parse 97 % (383990/396167) of all opening_hours values in OSM. If identical values appear multiple times then each value counts. This test is automated by now. Have a look at the opening_hours-statistics.

Small scale

Python script to search with regular expressions over OSM opening_hours style tags is bundled. You can run it with make run-regex_search or ./ which will search on the opening_hours tag. To search over different tags either use make run-regex_search "SEARCH=$tagname" (this also makes sure that the tag you would like to search on will be downloaded if necessary) or run ./ $path_to_downloaded_taginfo_json_file.

This script not only shows you if the found value can be processed with this library or not, it also indicates using different colors if the facility is currently open (open: green, unknown: magenta, closed: blue).

It also offers filter options (e.g. only errors) and additional things like a link to taginfo.

Hint: If you want to do quality assurance on tags like opening_hours you can also use this script and enter a regex for values you would like to check and correct (if you have no particular case just enter a dot which matches any character which results in every value being selected). Now you see how many values match your search pattern. As you do QA you probably only want to see values which can not be evaluated. To do this enter the filter "failed". To improve the speed of fixing errors, a feature was added to load those failed values in JOSM. To enable this, append " josm" to the input line. So you will have something like "failed josm" as argument. Now you can hit enter and go through the values.

Test it yourself (the geeky way)

You want to try some opening_hours yourself? Just run make run-interactive_testing or node interactive_testing.js which will open an primitive interpreter. Just write your opening_hours value and hit enter and you will see if it can be processed (with current state) or not (with error message). The answer is JSON encoded.

Testing is much easier by now. Have a look at the evaluation tool. The reason why this peace of code was written is to have an interface which can be accessed from other programming languages. It is used by the python module pyopening_hours.


Simple node.js based benchmark is bundled. You can run it with node benchmark.js or with make benchmark.

The library allows ~9k/sec constructor calls and ~9k/sec openIntervals() calls with one week period on author's Intel(R) Core(TM) i7-6600U CPU @ 2.60GHz running NodeJS 7.7.1, Linux 4.4.38-11 virtualized under Xen/Qubes OS). This may further improve in the future.

Used by other projects

This library is known to the used by the following projects:

Project Additional Information
OpenBeerMap issue for integration
JOSM ticket for integration
YoHours A simple editor for OpenStreetMap opening hours, GitHub
opening_hours_server.js A little server answering query‘s for opening_hours and check if they can be evaluated.
opening_hours-statistics Visualization of the data quality and growth over time in OSM. old version of this library, see also
osmopeninghours JavaScript library which provides a more abstract, specialized API and Italian localization. It returns a JavaScript object for a given time interval (see example.json).
ComplexAlarm Java/Android. Using the JS implementation through js-evaluator-for-android.

If you use this library please let me know.


YoHours currently only checks with this lib if the opening_hours value can be evaluated at all and links to the evaluation tool if yes. There might be more integration with YoHours and opening_hours.js in the future. See

Bindings and ports

Other implementations

Related links


List of missing features which can currently not be expressing in any other way without much pain. Please share your opinion on the talk page (or the discussion page of the proposal if that does exist) if you have any idea how to express this (better).

  • Select single (or more, comma separated) (school|public) holidays. Proposed syntax: SH(Sommerferien)
  • Depending on moon position like "low tide only". Suncalc lib does support moon position. Syntax needed.
  • If weekday is PH than the facility will be open weekday-1 this week. Syntax something like: We if (We +1 day == PH) else Th ???

List of features which can make writing easier:

  • May-Aug: (Mo-Th 9:00-20:00; Fr 11:00-22:00; Sa-Su 11:00-20:00)

  • Last day of the month.

    Jan 31,Mar 01 -1 day,Mar 31,Apr 30,May 31,Jun 30,Jul 31,Aug 31,Sep 30,Oct 31,Nov 30,Dec 31 open

    Better syntax needed? This example is valid even if the evaluation tool does not agree. It simily does not yet implement this.

    Ref and source:

How to contribute

You can contribute in the usual manner as known from git (and GitHub). Just fork, change and make a pull request.

Translating the evaluation tool and the map

This project uses for translation.

Translations can be made in the file js/i18n-resources.js. Just copy the whole English block, change the language code to the one you are adding and make your translation. You can open the index.html to see the result of your work. To complete your localization add the translated language name to the other languages (you don’t have to do this anymore. Importing that form somewhere, WIP, see gen_word_error_correction.js). Week and month names are translated by moment.js.

Note that this resource file does also provide the localization for the opening_hours_map. This can also be tested by cloning the project and linking your modified opening_hours.js working copy to the opening_hours.js directory (after renaming it) inside the opening_hours_map project. Or just follow the installation instructions from the opening_hours_map.

Translating error messages and warnings

Translations for error messages and warnings for the opening_hours.js library can be made in the file locales/core.js. You are encouraged to test your translations. Checkout the Makefile and the test framework for how this can be done.


Please do not open issues for missing holidays. It is obvious that there are more missing holidays then holidays which are defined in this library. Instead consider if you can add your missing holidays and send me a pull request or patch. If you are hitting a problem because some holidays depend on variable days or something like this, consider opening a unfinished PR so that the more complicated things can be discussed there.

Holidays can be added to the file index.js. Have a look at the current definitions for other holidays.

Please refer to the holiday documentation for more details about the data format.

Please consider adding a test (with a time range of one year for example) to see if everything works as expected and to ensure that it will stay that way. See under testing.

In case your holiday definition does only change the holiday_definitions variable (and not core code) it is also ok to test the definition using the PH_SH_exporter.js script. In that case writing a test is not required but still appreciated. Example: ./PH_SH_exporter.js --verbose --from=2016 --to=2016 --public-holidays --country dk --state dk /tmp/dk_holidays.txt

Core code

Be sure to add one or more tests if you add new features or enhance error tolerance or the like. See under testing.

Commit hooks

Note that there is a git pre-commit hook used to run and compare the test framework before each commit. To activate the hook, run:



All functions are documented, which should help contributers to get started.

The documentation looks like this:

/* List parser for constrained weekdays in month range {{{
 * e.g. Su[-1] which selects the last Sunday of the month.
 * :param tokens: List of token objects.
 * :param at: Position where to start.
 * :returns: Array:
 *            0. Constrained weekday number.
 *            1. Position at which the token does not belong to the list any more (after ']' token).
function getConstrainedWeekday(tokens, at) {

The opening brackets {{{ (and the corresponding closing onces) are used to fold the source code. See Vim folds.


Autor Contact Note
Dmitry Marakasov Initial coding and design and all basic features like time ranges, week ranges, month ranges and week ranges.
Robin Schneider Maintainer (since September 2013). Added support for years, holidays, unknown, comments, open end, fallback/additional rules (and more), wrote getWarnings, prettifyValue, translated demo page to English and German and extended it to enter values yourself (now called evaluation tool).


Refer to the Changelog


  • Netzwolf (He developed the first and very feature complete JS implementation for opening_hours (time_domain.js, mirror). His implementation did not create selector code to go through time as this library does (which is a more advanced design). time_domain.js has been withdrawn in favor of opening_hours.js but a few parts where reused (mainly the error tolerance and the online evaluation for the evaluation tool). It was also very useful as prove and motivation that all those complex things used in the opening_hours syntax are possible to evaluate with software :) )
  • Also thanks to FOSSGIS for hosting a public instance of this service. See the wiki.
  • The favicon.png is based on the file ic_action_add_alarm.png from the Android Design Icons which is licensed under Creative Commons Attribution 2.5. It represents a clock next to the most common opening_hours value (by far) which is 24/7 and a check mark.



As of version 3.4, opening_hours.js is licensed under the GNU Lesser General Public License v3.0 only.

Note that the original work from Dmitry Marakasov is published under the BSD 2-clause "Simplified" license which is included in this repository under the commit hash b2e11df02c76338a3a32ec0d4e964330d48bdd2d.