Giter Club home page Giter Club logo

humanized_opening_hours's Introduction

Humanized Opening Hours - A parser for the opening_hours fields from OSM

Humanized Opening Hours is a Python 3 module allowing a simple usage of the opening_hours fields used in OpenStreetMap.

Due to a lack of free time, the developpement of this module is paused. You can of course use it, but its features won't evolve before a (long) moment. If you want to become maintainer, don't hesitate to create an issue!

>>> import humanized_opening_hours as hoh
>>> field = "Mo-Fr 06:00-21:00; Sa,Su 08:00-12:00"
>>> oh = hoh.OHParser(field, locale="en")
>>> oh.is_open()
True
>>> oh.next_change()
datetime.datetime(2017, 12, 24, 12, 0)
>>> print('\n'.join(oh.description()))
"""
From Monday to Friday: 6:00 AM – 9:00 PM.
From Saturday to Sunday: 8:00 AM – 12:00 PM.
"""

This module is in beta. It should be production ready, but some bugs or minor modifications are still possible. Don't hesitate to create an issue!

Table of contents

Installation

This library is so small, you can include it directly into your project. Also, it is available on PyPi.

$ pip3 install osm-humanized-opening-hours

Dependencies

This module requires the following modules, which should be automatically installed when installing HOH with pip.

lark-parser
babel
astral

How to use it

The only mandatory argument to give to the constructor is the field, which must be a string. It can also take a locale argument, which can be any valid locale name. You can change it later by changing the locale attribute (which is, in fact, a property). However, to be able to use the most of the rendering methods, it must be in hoh.AVAILABLE_LOCALES (a warning will be printed otherwise).

>>> import humanized_opening_hours as hoh
>>> field = "Mo-Fr 06:00-21:00; Sa,Su 07:00-21:00"
>>> oh = hoh.OHParser(field)

If you have a GeoJSON, you can use a dedicated classmethod: from_geojson(), which returns an OHParser instance. It takes the GeoJSON, and optionally the following arguments:

  • timezone_getter (callable): A function to call, which takes two arguments (latitude and longitude, as floats), and returns a timezone name or None, allowing to get solar hours for the facility;
  • locale (str): the locale to use ("en" default).

Basic methods

To know if the facility is open at the present time. Returns a boolean. Can take a datetime.datetime moment to check for another time.

>>> oh.is_open()
True

To know at which time the facility status (open / closed) will change. Returns a datetime.datetime object. It can take a datetime.datetime moment to get next change from another time. If we are on December 24 before 21:00 / 09:00PM...

>>> oh.next_change()
datetime.datetime(2017, 12, 24, 21, 0)

For fields with consecutive days fully open, next_change() will try to get the true next change by recursion. You can change this behavior with the max_recursion argument, which is set to 31 default, meaning next_change() will try a maximum of 31 recursions (i.e. 31 days, or a month) to get the true next change. If this limit is reached, a NextChangeRecursionError will be raised. You can deny recursion by setting the max_recursion argument to 0.

The NextChangeRecursionError has a last_change attribute, containing the last change got just before raising of the exception. You can get it with a except NextChangeRecursionError as e: block.

>>> oh = hoh.OHParser("Mo-Fr 00:00-24:00")
>>> oh.next_change(dt=datetime.datetime(2018, 1, 8, 0, 0))
datetime.datetime(2018, 1, 11, 23, 59, 59, 999999)

To get a list of the opening periods between to dates, you can the use opening_periods_between() method. It takes two arguments, which can be datetime.date or datetime.datetime objects. If you pass datetime.date objects, it will return all opening periods between these dates (inclusive). If you pass datetime.datetime, the returned opening periods will be "choped" on these times.

The returned opening periods are tuples of two datetime.datetime objects, representing the beginning and the end of the period.

>>> oh = hoh.OHParser("Mo-Fr 06:00-21:00; Sa,Su 07:00-21:00")
>>> oh.opening_periods_between(datetime.date(2018, 1, 1), datetime.date(2018, 1, 7))
[
    (datetime.datetime(2018, 1, 1, 6, 0), datetime.datetime(2018, 1, 1, 21, 0)),
    (datetime.datetime(2018, 1, 2, 6, 0), datetime.datetime(2018, 1, 2, 21, 0)),
    (datetime.datetime(2018, 1, 3, 6, 0), datetime.datetime(2018, 1, 3, 21, 0)),
    (datetime.datetime(2018, 1, 4, 6, 0), datetime.datetime(2018, 1, 4, 21, 0)),
    (datetime.datetime(2018, 1, 5, 6, 0), datetime.datetime(2018, 1, 5, 21, 0)),
    (datetime.datetime(2018, 1, 6, 7, 0), datetime.datetime(2018, 1, 6, 21, 0)),
    (datetime.datetime(2018, 1, 7, 7, 0), datetime.datetime(2018, 1, 7, 21, 0))
]

You can also set the merge parameter to True, to merge continuous opening periods.


You can get a sanitized version of the field given to the constructor with the sanitize() function or the field attribute.

>>> field = "mo-su 09:30-20h;jan off"
>>> print(hoh.sanitize(field))
"Mo-Su 09:30-20:00; Jan off"

If sanitization is the only thing you need, use HOH for this is probably overkill. You might be interested in the OH Sanitizer module, or you can copy directly the code of the sanitize function in your project.


If you try to parse a field which is invalid or contains a pattern which is not supported, an humanized_opening_hours.exceptions.ParseError (inheriting from humanized_opening_hours.exceptions.HOHError) will be raised.

If a field contains only a comment (like "on appointment"), a CommentOnlyField exception (inheriting from ParseError) will be raised. It contains a comment attribute, allowing you to display it instead of the opening hours.

The OHParser contains an is_24_7 attribute, which is true if the field is simply 24/7 or 00:00-24:00, and false either. The next_change() method won't try recursion if this attribute is true and will directly raise a NextChangeRecursionError (except if you set max_recursion to zero, in this case it will just return the last time of the current day).

You can check equality between two OHParser instances. It will be true if both have the same field and the same location.

>>> import humanized_opening_hours as hoh
>>> 
>>> oh1 = hoh.OHParser("Mo 10:00-20:00")
>>> oh2 = hoh.OHParser("Mo 10:00-20:00")
>>> oh3 = hoh.OHParser("Mo 09:00-21:00")
>>> oh1 == oh2
True
>>> oh1 == oh3
False

The OHParser object contains two other attributes: PH_dates and SH_dates, which are empty lists default. To indicate a date is a public or a school holiday, you can pass its datetime.date into these lists. You can also use the python-holidays module to get dynamic dictionnary (which updates the year) to replace these lists. In fact, any iterable object with a __contains__ method (receiving datetime.date objects) will work. If you have GPS coordinates and want to have a country name, you can use the countries module.

Solar hours

If the field contains solar hours, here is how to deal with them.

First of all, you can easily know if you need to set them by checking the OHParser.needs_solar_hours_setting variable. If one of its values is True, it appears in the field and you should give to HOH a mean to retrive its time.

You have to ways to do this. The first is to give to the OHParser the location of the facility, to allow it to calculate solar hours. The second is to use the SolarHours object (which inherits from dict), via the OHParser.solar_hours attribute.

# First method. You can use either an 'astral.Location' object or a tuple.
location = astral.Location(["Greenwich", "England", 51.168, 0.0, "Europe/London", 24])
location = (51.168, 0.0, "Europe/London", 24)
oh = hoh.OHParser(field, location=location)

# Second method.
solar_hours = {
    "sunrise": datetime.time(8, 0), "sunset": datetime.time(20, 0),
    "dawn": datetime.time(7, 30), "dusk": datetime.time(20, 30)
}
oh.solar_hours[datetime.date.today()] = solar_hours

Attention, except if the facility is on the equator, this setting will be valid only for a short period (except if you provide coordinates, because they will be automatically updated).

If you try to do something with a field containing solar hours without providing a location, a humanized_opening_hours.exceptions.SolarHoursError exception will be raised.

In some very rare cases, it might be impossible to get solar hours. For example, in Antactica, the sun may never reach the dawn / dusk location in the sky, so the astral module can't return the down time. So, if you try to get, for example, the next change with a field containing solar hours and located in such location, a humanized_opening_hours.exceptions.SolarHoursError exception will also be raised.


Sometimes, especially if you work with numerous fields, you may want to apply the same methods to the same field but for different locations. To do so, you can use a dedicated method called this_location(), which is intended to be used as a context manager. It allows you to temporarily set a specific location to the OHParser instance.

oh = hoh.OHParser(
    "Mo-Fr sunrise-sunset",
    location=(51.168, 0.0, "Europe/London", 24)
)

str(oh.solar_hours.location) == 'Location/Region, tz=Europe/London, lat=51.17, lon=0.00'

with oh.temporary_location("Paris"):
    str(oh.solar_hours.location) == 'Paris/France, tz=Europe/Paris, lat=48.83, lon=2.33'

str(oh.solar_hours.location) == 'Location/Region, tz=Europe/London, lat=51.17, lon=0.00'

Have nice schedules

You can pass any valid locale name to OHParser, it will work for the majority of methods, cause they only need Babel's translations. However, the description() and plaintext_week_description() methods need more translations, so it works only with a few locales, whose list is available with hoh.AVAILABLE_LOCALES. Use another one will make methods return inconsistent sentences.

Currently, the following locales are supported:

  • en: english (default);
  • fr_FR: french;
  • de: deutsch;
  • nl: dutch;
  • pl: polish;
  • pt: portuguese;
  • it: italian;
  • ru_RU: russian.

The get_localized_names() method returns a dict of lists with the names of months and weekdays in the current locale.

Example:

>>> oh.get_localized_names()
{
    'months': [
        'January', 'February', 'March',
        'April', 'May', 'June', 'July',
        'August', 'September', 'October',
        'November', 'December'
    ],
    'days': [
        'Monday', 'Tuesday', 'Wednesday',
        'Thursday', 'Friday', 'Saturday',
        'Sunday'
    ]
}

time_before_next_change() returns a humanized delay before the next change in opening status. Like next_change(), it can take a datetime.datetime moment to get next change from another time.

>>> oh.time_before_next_change()
"in 3 hours"
>>> oh.time_before_next_change(word=False)
"3 hours"

description() returns a list of strings (sentences) describing the whole field.

# Field: "Mo-Fr 10:00-19:00; Sa 10:00-12:00; Dec 25 off"
>>> print(oh.description())
['From Monday to Friday: 10:00 AM – 7:00 PM.', 'On Saturday: 10:00 AM – 12:00 PM.', 'December 25: closed.']
>>> print('\n'.join(oh.description()))
"""
From Monday to Friday: 10:00 AM – 7:00 PM.
On Saturday: 10:00 AM – 12:00 PM.
December 25: closed.
"""

plaintext_week_description() returns a plaintext description of the opening periods of a week. This method takes a year and a weeknumber (both int). You can also specify the first day of the week with the first_weekday parameter (as int). Its default value is 0, meaning "Monday".

It can also take no parameter, so the described week will be the current one.

>>> print(oh.plaintext_week_description(year=2018, weeknumber=1, first_weekday=0))
"""
Monday: 8:00 AM – 7:00 PM
Tuesday: 8:00 AM – 7:00 PM
Wednesday: 8:00 AM – 7:00 PM
Thursday: 8:00 AM – 7:00 PM
Friday: 8:00 AM – 7:00 PM
Saturday: 8:00 AM – 12:00 PM
Sunday: closed
"""

This method uses the days_of_week() function to get the datetimes of the days of the requested week. It is accessible directly through the HOH namespace, and takes the same parameters.


get_day() returns a Day object, which contains opening periods and useful methods for a day. It can take a datetime.date argument to get the day you want.

The returned object contains the following attributes.

  • ohparser (OHParser) : the OHParser instance where the object come from;
  • date (datetime.date) : the date of the day;
  • weekday_name (str) : the name of the day (ex: "Monday");
  • timespans : (list[ComputedTimeSpan]) : the computed timespans of the day (containing datetime.datetime objects);
  • locale (babel.Locale) : the locale given to OHParser.

Attention, the datetime.datetime objects in the computed timespans may be in another day, if it contains a period which spans over midnight (like Mo-Fr 20:00-02:00).

Supported field formats

Here are the field formats officialy supported and tested (examples).

24/7
Mo 10:00-20:00
Mo-Fr 10:00-20:00
Sa,Su 10:00-20:00
Su,PH off  # or "closed"
10:00-20:00
20:00-02:00
sunrise-sunset  # or "dawn" / "dusk"
(sunrise+01:00)-20:00
Jan 10:00-20:00
Jan-Feb 10:00-20:00
Jan,Dec 10:00-20:00
Jan Mo 10:00-20:00
Jan,Feb Mo 10:00-20:00
Jan-Feb Mo 10:00-20:00
Jan Mo-Fr 10:00-20:00
Jan,Feb Mo-Fr 10:00-20:00
Jan-Feb Mo-Fr 10:00-20:00
SH Mo 10:00-20:00
SH Mo-Fr 10:00-20:00
easter 10:00-20:00
SH,PH Mo-Fr 10:00-20:00
SH,PH Mo-Fr,Su 10:00-20:00
Jan-Feb,Aug Mo-Fr,Su 10:00-20:00
week 1 Mo 09:00-12:00
week 1-10 Su 09:00-12:00
week 1-10/2 Sa-Su 09:00-12:00
2018 Mo-Fr 10:00-20:00
2018-2022 Mo-Fr 10:00-20:00
2018-2022/2 Mo-Fr 10:00-20:00

The following formats are NOT supported yet and their parsing will raise a ParseError.

Su[1] 10:00-20:00
easter +1 day 10:00-20:00
easter +2 days 10:00-20:00
Mo-Fr 10:00+
Mo-Fr 10:00,12:00,20:00  # Does not support points in time.

For fields like 24/7; Su 10:00-13:00 off, Sundays are considered as entirely closed. This should be fixed in a later version.

Alternatives

If you want to parse opening_hours fields but HOH doesn't fit your needs, here are a few other libraries which might interest you.

Performances

HOH uses the module Lark (with the Earley parser) to parse the fields.

It is very optimized (about 20 times faster) for the simplest fields (like Mo-Fr 10:00-20:00), so their parsing will be very fast:

  • 0.0002 seconds for a single field;
  • 0.023 seconds for a hundred;
  • 0.23 seconds for a thousand.

For more complex fields (like Jan-Feb Mo-Fr 08:00-19:00), the parsing is slower:

  • 0.006 seconds for a single field;
  • 0.57 seconds for a hundred;
  • 5.7 seconds for a thousand.

Licence

This module is published under the AGPLv3 license, the terms of which can be found in the LICENCE file.

humanized_opening_hours's People

Contributors

adam2809 avatar agrendalath avatar alexelvers avatar amatissart avatar fklement avatar giobonvi avatar jbgriesner avatar klada avatar ogabrielluiz avatar rezemika avatar sashman avatar tijs-b avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

humanized_opening_hours's Issues

get_locale_day() Missing

A recent commit (ce5d7ae) removed two "useless" methods.
I'm not sure but it seems that at least one of these methods was not useless:

>>> oh = hoh.OHParser("24/7")
>>> ohr = oh.render()
>>> ohr.plaintext_week_description()

AttributeError   Traceback (most recent call last)
<ipython-input-37-912ee1dfa78> in <module>() --> 1 ohr.plaintext_week_description()

~/.local/share/virtualenvs/idunn-8FJC_dD6/lib/python3.6/site-packages/humanized_opening_hours/main.py in plaintext_week_description(self, obj)
    718         output = ''
    719         for day in obj:
--> 720             d = self.periods_of_day(day)
    721             description = d.description if d.description else _("closed")
    722             output += _("{name}: {periods}").format(

~/.local/share/virtualenvs/idunn-8FJC_dD6/lib/python3.6/site-packages/humanized_opening_hours/main.py in periods_of_day(self, day)
    694             )
    695         rendered_periods = self._join_list(rendered_periods)
--> 696         name = self.get_locale_day(day.weekday())
    697         return RenderableDay(name=name, description=rendered_periods, dt=d.date)
    698 

AttributeError: 'OHRenderer' object has no attribute 'get_locale_day'

It looks like the get_locale_day() function should not have been removed ... ?

Unspecified closing time not supported

According to https://wiki.openstreetmap.org/wiki/Key:opening_hours, the following syntax is allowed:

Su 10:00+
Sunday from 10:00 to an unknown or unspecified closing time.

However, hoh returns an error:

>>> import humanized_opening_hours as hoh
>>> oh = hoh.OHParser("Su 10:00+")
Traceback (most recent call last):
  File "/home/klomp/.local/lib/python3.7/site-packages/humanized_opening_hours/main.py", line 91, in __init__
    self._tree = field_parser.parse_field(self.sanitized_field)
  File "/home/klomp/.local/lib/python3.7/site-packages/humanized_opening_hours/field_parser.py", line 267, in parse_field
    tree = PARSER.parse(field)
  File "/home/klomp/.local/lib/python3.7/site-packages/lark/lark.py", line 292, in parse
    return self.parser.parse(text)
  File "/home/klomp/.local/lib/python3.7/site-packages/lark/parser_frontends.py", line 79, in parse
    return self.parser.parse(token_stream, *[sps] if sps is not NotImplemented else [])
  File "/home/klomp/.local/lib/python3.7/site-packages/lark/parsers/lalr_parser.py", line 36, in parse
    return self.parser.parse(*args)
  File "/home/klomp/.local/lib/python3.7/site-packages/lark/parsers/lalr_parser.py", line 81, in parse
    for token in stream:
  File "/home/klomp/.local/lib/python3.7/site-packages/lark/lexer.py", line 354, in lex
    for x in l.lex(stream, self.root_lexer.newline_types, self.root_lexer.ignore_types):
  File "/home/klomp/.local/lib/python3.7/site-packages/lark/lexer.py", line 183, in lex
    raise UnexpectedCharacters(stream, line_ctr.char_pos, line_ctr.line, line_ctr.column, allowed=allowed, state=self.state)
lark.exceptions.UnexpectedCharacters: No terminal defined for '+' at line 1 col 9

Su 10:00+
        ^

Expecting: {'COMMA', 'MINUS', '__ANON_3'}

Avoid sys.path[0] for finding setup.py directory

I wanted to use a fork (temporarily), and found that the default setup.py fails on my system (Python 3.5, Ubuntu 16.04) as it uses sys.path[0] in an attempt to find the development directory. I assume this is because the main dev environment involves prepending to $PYTHONPATH. However, this makes the package non-portable - system.path[0] is set to '' by default, so setup.py raises a FileNotFoundError when attempting to change to that directory.

To indicate the directory of setup.py, use __file__:

os.path.chdir(os.path.dirname(os.path.realpath(__file__)))

Can't parse comma-separated ranges

OHParser raises a ParseError on day ranges after a comma:

hoh.OHParser('We,Fr-Su 10:00-17:00')

ParseError: The field could not be parsed, it may be invalid. Error happened on column 5 when parsing '-'.

This error doesn't happen if you have the range first: hoh.OHParser('We-Fr,Su 10:00-17:00')

I've opened a pull request at #6, but since I'm new to EBNF it probably contains some issues.

Allow times which span over midnight

When using the example from the OSM documentation, which has times spanning over midnight, I am getting an exception:

>>> import humanized_opening_hours as hoh
>>> hoh.OHParser("Su-Tu 11:00-01:00, We-Th 11:00-03:00, Fr 11:00-06:00, Sa 11:00-07:00")
Traceback (most recent call last):
  File "/tmp/osm/lib/python3.7/site-packages/humanized_opening_hours/main.py", line 91, in __init__
    self._tree = field_parser.parse_field(self.sanitized_field)
  File "/tmp/osm/lib/python3.7/site-packages/humanized_opening_hours/field_parser.py", line 267, in parse_field
    tree = PARSER.parse(field)
  File "/tmp/osm/lib/python3.7/site-packages/lark/lark.py", line 292, in parse
    return self.parser.parse(text)
  File "/tmp/osm/lib/python3.7/site-packages/lark/parser_frontends.py", line 79, in parse
    return self.parser.parse(token_stream, *[sps] if sps is not NotImplemented else [])
  File "/tmp/osm/lib/python3.7/site-packages/lark/parsers/lalr_parser.py", line 36, in parse
    return self.parser.parse(*args)
  File "/tmp/osm/lib/python3.7/site-packages/lark/parsers/lalr_parser.py", line 92, in parse
    reduce(arg)
  File "/tmp/osm/lib/python3.7/site-packages/lark/parsers/lalr_parser.py", line 73, in reduce
    value = self.callbacks[rule](s)
  File "/tmp/osm/lib/python3.7/site-packages/humanized_opening_hours/field_parser.py", line 174, in field_part
    "The field contains a period which spans "
humanized_opening_hours.exceptions.SpanOverMidnight: The field contains a period which spans over midnight, which not yet supported.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/osm/lib/python3.7/site-packages/humanized_opening_hours/main.py", line 92, in __init__
    except lark.lexer.UnexpectedInput as e:
AttributeError: module 'lark.lexer' has no attribute 'UnexpectedInput'

It seems like spanning over midnight is already detected, but currently not handled.

MonthDayRange over 2 implicit years

The opening hours of this place are:

Oct-Mar 07:30-19:30; Apr-Sep 07:00-21:00

It means that the 30th of november at 14h30 it should be open.
However:

>>> oh = hoh.OHParser('Oct-Mar 07:30-19:30; Apr-Sep 07:00-21:00')
>>> oh.is_open()
False

Now if I add the year as your grammar allows it, then it works as expected:

>>> oh = hoh.OHParser('2018 Oct - 2019 Mar 07:30-19:30; Apr-Sep 07:00-21:00')
>>> oh.is_open()
True

The problem appears when the MonthDayRange spans between two different years without the explicit information in the monthday_date_month.

Do you have a solution to this problem ?

Thanks!

Alternate week_description() returning a list

Hello !
Thanks for creating this package :)

I was slightly surprised that oh.description() returned a list, but oh.plaintext_week_description() returned a string using \n.
Is there a historical reason or issue for that ? Would you be ok if I considered doing a PR to add oh.week_description() that returns a list ? (and why not oh.plaintext_description() that returned a string using \n - and you could even push it by choosing the delimiter)

Anyway I know I can also just simply do oh.plaintext_week_description().split("\n") so I'm using it like this for now :)

Sorry for the bother

Ajouter une méthode pour récupérer un objet HOHRenderer

Pour l'instant, pour pouvoir faire un rendu, il faut faire tout ça.

import humanized_opening_hours
field = "Mo-Fr 06:00-21:00; Sa,Su 07:00-21:00"
hoh = humanized_opening_hours.HumanizedOpeningHours(field)
hohr = humanized_opening_hours.HOHRenderer(hoh)
print(hohr.description())

Il faudrait ajouter une méthode à HumanizedOpeningHours pour récupérer un objet hohr prêt à être utilisé, pour pouvoir faire quelque chose comme ça.

import humanized_opening_hours
field = "Mo-Fr 06:00-21:00; Sa,Su 07:00-21:00"
hoh = humanized_opening_hours.HumanizedOpeningHours(field)
print(hoh.render().description())

Parsing Failure against Non-standard DoW and presence of 'AM' and / or 'PM'

The parser will fail against opening hours that contain nonstandard days of the week abbreviations and the presence of 'AM' and / or 'PM'.

For example, "Mon-Sat 11:30AM-10PM" will fail on both of the counts above (see OSM node 30899821). Moreover, the lack of :00 seems to confuse it as well.

  • Python Version: Python 3.6.5 :: Anaconda, Inc.
  • OS: macOS
  • Steps to reproduce:
import humanized_opening_hours as hoh

hoh.OHParser("Mon-Sat 11:30AM-10PM")

Error:

Traceback (most recent call last):
  File "/anaconda3/lib/python3.6/site-packages/humanized_opening_hours/main.py", line 326, in __init__
    self.field, optimize
  File "/anaconda3/lib/python3.6/site-packages/humanized_opening_hours/field_parser.py", line 296, in get_tree_and_rules
    tree = PARSER.parse(field)
  File "/anaconda3/lib/python3.6/site-packages/lark/lark.py", line 223, in parse
    return self.parser.parse(text)
  File "/anaconda3/lib/python3.6/site-packages/lark/parser_frontends.py", line 118, in parse
    return self.parser.parse(text)
  File "/anaconda3/lib/python3.6/site-packages/lark/parsers/xearley.py", line 130, in parse
    column = scan(i, column)
  File "/anaconda3/lib/python3.6/site-packages/lark/parsers/xearley.py", line 119, in scan
    raise UnexpectedCharacters(stream, i, text_line, text_column, {item.expect for item in to_scan}, set(to_scan))
lark.exceptions.UnexpectedCharacters: No terminal defined for 'n' at line 1 col 3
Mon-Sat 11:30AM-10PM
  ^
Expecting: {Terminal('MINUS'), Terminal('CLOSED'), Terminal('COMMA'), Terminal('__IGNORE_0'), Terminal('OPEN')}
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/anaconda3/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2963, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-36-384ae46503c2>", line 1, in <module>
    hoh.OHParser(opening_hours)
  File "/anaconda3/lib/python3.6/site-packages/humanized_opening_hours/main.py", line 332, in __init__
    col=e.column
humanized_opening_hours.exceptions.ParseError: The field could not be parsed, it may be invalid. Error happened on column 3.

In order for the string to be parsed correctly, it must be modified to: 'Mo-Sa 11:30-22:00'.


Great job on the package overall. I know that this is a hard problem!

Add translations for other languages

Currently, only english and french are supported for rendering. It would be great if HOH support other languages. So, if there's any language that you would like to see HOH work with, please don't hesitate to create a pull request! :)

To add a new translation:

  • add its locale name in AVAILABLE_LOCALES in rendering.py;
  • go to the humanized_opening_hours folder;
  • create a folder for you translation: mkdir -p locales/<LOCALE>/LC_MESSAGES (replace <LOCALE> with the name of the locale);
  • create a new POT file with : xgettext -o locales/<LOCALE>/LC_MESSAGES/hoh.pot temporal_objects.py rendering.py;
  • update this new file to add translations;
  • compile it to a MO file with msgfmt -o locales/<LOCALE>/LC_MESSAGES/hoh.mo locales/fr_FR/LC_MESSAGES/hoh.pot;
  • test the rendering of some fields with you new locale.

Thank you!

unexpected keyword argument 'locale'

When using the intro code:

import humanized_opening_hours as hoh
field = "Mo-Fr 06:00-21:00; Sa,Su 08:00-12:00"
oh = hoh.OHParser(field, locale="en")

I get

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'locale'

Wrong first day of week

As stated in #17 when running the basic example found at the beginning of README

>>> import humanized_opening_hours as hoh
>>> field = "Mo-Fr 06:00-21:00; Sa,Su 08:00-12:00"
>>> oh = hoh.OHParser(field, locale="en")
>>> oh.is_open()
True
>>> oh.next_change()
datetime.datetime(2017, 12, 24, 12, 0)
>>> print('\n'.join(oh.description()))
"""
From Monday to Friday: 6:00 AM – 9:00 PM.
From Saturday to Sunday: 8:00 AM – 12:00 PM.
"""

I get a completely different result:

>>> import humanized_opening_hours as hoh
>>> field = "Mo-Fr 06:00-21:00; Sa,Su 08:00-12:00"
>>> oh = hoh.OHParser(field, locale="en")
>>> print('\n'.join(oh.description()))
From Sunday to Thursday: 6:00 AM9:00 PM.
From Friday to Saturday: 8:00 AM12:00 PM.

I suspect this might be related to some settings, possibily regarding Python internal localization as I live in Italy.
Let me know if I can help you, for example with any details on localization settings of my Python installation (which by the way is Python 3.6.5 on Ubuntu 18.04.1 in WSL running on Windows 10).

Max recursions error in next_change() function

It happens with Su off;Tu-Fr off;Sa 15:00-20:00;Mo 15:00-20:00

....
    return _current_or_next_timespan(new_dt, i=i+1)
  File "/home/ramin/workspace/map/POI/humanized_opening_hours/main.py", line 522, in _current_or_next_timespan
    return _current_or_next_timespan(new_dt, i=i+1)
  File "/home/ramin/workspace/map/POI/humanized_opening_hours/main.py", line 519, in _current_or_next_timespan
    new_dt.date()+datetime.timedelta(i),
OverflowError: date value out of range

seems that Tu-Fr off is selected here:

current_rule = self.get_current_rule(

it is fixed changing this line:

to:

if matching_rules and matching_rules[0].status != 'closed':

Améliorer et vérifier la documentation

Il y a quelques imprécisions dans le README et les docstrings. Il faudrait harmoniser et corriger tout cela.

Aussi, il y a une petite erreur dans le README.

>>> import humanized_opening_hours
>>> field = "Mo-Fr 06:00-21:00; Sa,Su 07:00-21:00"
>>> hoh = HumanizedOpeningHours(field)  # Devrait être "humanized_opening_hours.HumanizedOpeningHours(field)"
[...]

Allow (again) recursion for the `next_change()` method

The following concerns the new-parsing branch.

The commit 754060b rewrites a large part of the code, but makes the recursion of the next_change() method inoperative. If someone has any idea of how to do that (or to do better!), please don't hesitate to propose a pull request!

The goal is to get the true next change for a field like Mo-Fr 00:00-24:00. Currently, for a test from a Monday, it will return 24:00 on Monday, it should return 24:00 on Friday.

Here is the concerned method:

def next_change(self, dt=None):
"""Gets the next opening status change.
Parameters
----------
dt : datetime.datetime, optional
The moment for which to check the opening. None default,
meaning use the present time.
Returns
-------
datetime.datetime
The datetime of the next change.
"""
if not dt:
dt = datetime.datetime.now()
days_offset, next_timespan = self._current_or_next_timespan(dt)
new_time = dt.time() if days_offset == 0 else datetime.time.min
new_dt = datetime.datetime.combine(
dt.date() + datetime.timedelta(days_offset),
new_time
)
beginning_time, end_time = next_timespan.get_times(
new_dt, self.solar_hours
)
if dt < beginning_time:
return beginning_time
return end_time

Need some feedbacks!

Hey there!

HOH is currently in v1.0.0 beta 1, so it is almost production-ready. However, I would greatly appreciate some feedbacks on the API, to have a module as developer-friendly as possible. So, if you have any notice, complaint or question about the methods or the usage of HOH, please feel free to post them in this issue.

Thank you in advance!

incomplete parsing when weekday is defined twice

The opening_hours of this place are: Mo-Fr 11:30-15:00, We-Mo 18:00-23:00

The plain text description goes like this:

>>> oh = hoh.OHParser(field, locale="en")
>>> print(oh.plaintext_week_description(year=2018, weeknumber=1, first_weekday=0))
Monday: 6:00 PM – 11:00 PM
Tuesday: 11:30 AM – 3:00 PM
Wednesday: 6:00 PM – 11:00 PM
Thursday: 6:00 PM – 11:00 PM
Friday: 6:00 PM – 11:00 PM
Saturday: 6:00 PM – 11:00 PM
Sunday: 6:00 PM – 11:00 PM

whereas the expected is:

Monday: 11:30 AM – 3:00 PM and 6:00 PM – 11:00 PM
Tuesday: 11:30 AM – 3:00 PM
Wednesday: 11:30 AM – 3:00 PM and 6:00 PM – 11:00 PM
Thursday: 11:30 AM – 3:00 PM and 6:00 PM – 11:00 PM
Friday: 11:30 AM – 3:00 PM and 6:00 PM – 11:00 PM
Saturday: 6:00 PM – 11:00 PM
Sunday: 6:00 PM – 11:00 PM

Allow the separator_for_readability

The OSM entry for the Louvre museum in Paris has the following opening_hours:

"Mo,Th,Sa,Su 09:00-18:00; We,Fr 09:00-21:45; Tu off; Jan 1,May 1,Dec 25: off"

If you try to parse it with the hoh parser, you get the following error:

>>> hoh.OHParser("Mo,Th,Sa,Su 09:00-18:00; We,Fr 09:00-21:45; Tu off; Jan 1,May 1,Dec 25: off")

ParseError: The field could not be parsed, it may be invalid. Error happened on column 71 when parsing '5:off'.

The problem is the colon at the end just before the "off" which is not allowed by your grammar and makes the hoh parser crashing to avoid confusion with "DIGITAL_MOMENT":

This colon is allowed by the specifications as a separator_for_readability though it's an optional token.

Could you adapt your grammar to allow this separator ?
Thanks :)

Parsing a description ? (reverse parsing)

This tool is really great for parsing (or sanitizing) strings that have the osm opening_hours format, and getting them in a readable description. 💯

hoh.OHParser(" Mo-Fr 08:30-16:30", locale="fr").description()
// ['Du lundi au vendredi : 08:30 – 16:30.']

I was wondering if it was possible to leverage this tool (or another tool ?) to do the other way around ? 👇

hoh.parse_description("Du lundi au vendredi : 08:30 – 16:30", locale="fr")
// Mo-Fr 08:30-16:30

fyi my current implementation/workaround for this is with string replace 🙃

import re

oh_description = "Du lundi au vendredi : 08:30 – 16:30"
oh_description = re.sub("du", "", oh_description, flags=re.IGNORECASE)
oh_description = re.sub("lundi", "Mo", oh_description, flags=re.IGNORECASE)
...

Thanks anyway

Memoize get_parser

Hi, you wrote in your readme:

you can save some time by passing the parser to the constructor, instead to recreate it each time. To do 
this, get the Lark parser with the humanized_opening_hours.field_parser.get_parser() function, and pass 
it to the OHParser constructor via the parser argument.

Perhaps a more user-friendly approach is to save the lark instance, either as a property in the OHParser instance like:

if not self._parser:
    self._parser = field_parser.get_parser()
return self._parser

Or as a global variable in the field_parser module.

Just a suggestion :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.