opening-hours / opening_hours.js

Library to parse and process the opening_hours tag from OpenStreetMap data
https://openingh.ypid.de/evaluation_tool/
222 stars 119 forks source link
javascript-library npm-package opening-hours openinghours openstreetmap openstreetmap-data openstreetmap-validator

opening_hours

GitHub CI license NPM required Node.js version version NPM monthly downloads via NPM total downloads via NPM

Summary

The opening_hours tag is used in OpenStreetMap to describe time ranges when a facility (for example, a café) is open. This library exists to easily extract 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) from the complex syntax.

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

An 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 of how this library can be used and what it is capable of.

A mirror is set up at https://openingh.ypid.de/evaluation_tool/

Installation

For Developer

Just clone the repository:

git clone --recursive https://github.com/opening-hours/opening_hours.js.git

and install the required dependencies:

npm install
pip install -r requirements.txt

and build the library:

npm run build

See the Testing section for details around writing and running tests

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:

https://openingh.openstreetmap.de/opening_hours.js/opening_hours+deps.min.js

To get started checkout the simple_index.html file.

NodeJS developer

Install using npm/yarn.

npm install opening_hours

or

yarn add opening_hours

Versions

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:

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

Synopsis

const opening_hours = require("opening_hours");
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"));
  else
    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

Simple API

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

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.

Iterator API

Features

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.

Time ranges

Points in time

Weekday ranges

Holidays

Month ranges

Monthday ranges

Week ranges

Year ranges

States

Comments

Testing

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

Regression testing

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

Included in the test directory are the log outputs of the previous testing runs. By comparing to these logs and assuming that the checkedd-in logs are always passing, it allows the developer to validate if the number of passed tests have changed since the last feature 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 scripts/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

A 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 ./scripts/regex_search.py 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 ./scripts/regex_search.py $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 ./scripts/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.

Performance

Simple node.js based benchmark is bundled. You can run it with node ./scripts/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
osm24.eu
OpenBeerMap issue for integration
opening_hours_map
ulm-opening-hours
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.
www.openstreetmap.hu old version of this library, see also https://github.com/AMDmi3/opening_hours.js/issues/19
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.
MapComplete An OpenStreetMap-editor which aims to be really simple to use by offering multiple themes

If you use this library please let me know.

Projects that previously used the library

Project Additional Information
JOSM ticket for integration in 13.11, ticket for removal in 20.03

YoHours

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 https://github.com/PanierAvide/panieravide.github.io/issues/2

Bindings and ports

Other implementations

Related links

ToDo

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).

List of features which can make writing easier:

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 https://www.i18next.com/ 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 the browser using the Date.toLocaleString function.

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.

Holiday Data

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 scripts/PH_SH_exporter.js script. In that case writing a test is not required but still appreciated. Example: ./scripts/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. Hooks are written as shell scripts using husky and should be installed to git automatically when running npm install. If this does not happen, you can manually run npm run postinstall.

Documentation

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.

Authors

Autor Contact Note
Dmitry Marakasov amdmi3@amdmi3.ru Initial coding and design and all basic features like time ranges, week ranges, month ranges and week ranges.
Robin Schneider ypid@riseup.net 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).

Contributors

Refer to the Changelog

Credits

Stats

License

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" (BSD-2-Clause) license which is included in this repository under the commit hash b2e11df02c76338a3a32ec0d4e964330d48bdd2d.