Skip to content

Commit

Permalink
Merge branch 'dev' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
MoralCode committed May 30, 2021
2 parents 0dbbdd4 + 34d33f1 commit 2a58ca7
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 41 deletions.
3 changes: 2 additions & 1 deletion opening_hours/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .parse_opening_hours import OpeningHours, create_entry
from .parse_opening_hours import OpeningHours, create_entry
from .models.times import Times
6 changes: 6 additions & 0 deletions opening_hours/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import unicodedata

def normalize_string(string):
string = unicodedata.normalize('NFC', string)
string = string.strip()
return string
90 changes: 58 additions & 32 deletions opening_hours/models/times.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from opening_hours.models.time import Time, TimeType
from opening_hours.patterns import timerange
import logging, os
from opening_hours.helpers import normalize_string

logger = logging.getLogger(__name__)

Expand All @@ -13,36 +15,28 @@ class Times():
end_time = None

@classmethod
def from_shortcut_string(cls, times_string, assume_type=None):
def parse(cls, times_string, assume_type=None):
"""
create a time object from a string
parse a time object from a string using the pyparsing patterns for a time range
This function will normalize the input value first and will raise a TypeError if None is given.
"""
logger.debug("creating times object from shortcut: " + times_string)
logger.debug("creating times object from string: " + times_string)
if times_string is None:
raise TypeError("Cannot create Times Object from value None")

day = times_string.lower()
times_string = normalize_string(times_string)

# set up some shortcut ranges
allday = cls(Time(0, 0, TimeType.AM), Time(11, 59, TimeType.PM))
workhours = cls(Time(9, 0, TimeType.AM), Time(5, 0, TimeType.PM))
return cls.from_parse_results(timerange.parseString(times_string), assume_type=assume_type)

if "24" in day:
return allday
elif "business" in day:
return workhours
elif "work" in day:
return workhours
elif "all day" in day:
return allday

raise ValueError("string '" + times_string + "' does not match a known pattern")

@classmethod
def from_parse_results(cls, result, assume_type=None):
""" Takes values from the pyparsing results and converts them to the appropriate internal objects """
"""
Takes values from pyparsing results and converts them to an instance of this object. This makes heavy use of the output names defined for certain patterns to help pick out only the relevant data.
This is primarily for internal use and is helpful when combined with the parse() functions of this or other objects.
"""
# assumes that all three (hours, minutes, am_pm) are the same length
res_dct = result.asDict()
logger.debug(res_dct)
if "starttime" in res_dct and "endtime" in res_dct:
logger.info("time range detected")
start = res_dct.get("starttime")[0]
Expand Down Expand Up @@ -74,33 +68,47 @@ def from_parse_results(cls, result, assume_type=None):
@classmethod
def from_shortcut_string(cls, times_shortcut, assume_type=None):
"""
create a times object from a shortcut string
create a times object from a shortcut string, such as are used to represent time ranges such as "24 hours", or "work hours".
This is primarily for internal use and is helpful when combined with the parse() functions of this or other objects.
"""
logger.debug("creating times object from shortcut: " + times_shortcut)
logger.debug("creating times object from shortcut: " + (times_shortcut or "None"))
if times_shortcut is None:
raise TypeError("Cannot create Times Object from value None")

day = times_shortcut.lower()
times = times_shortcut.lower()

# set up some shortcut ranges
allday = cls(Time(0, 0, TimeType.AM), Time(11, 59, TimeType.PM))
workhours = cls(Time(9, 0, TimeType.AM), Time(5, 0, TimeType.PM))
closed = cls(None, None)

if "all day" in day:
return allday
elif "24" in day:
if "24" in times:
return allday
elif day == "":
# if no day is specified, assume the intention is all day
elif "business" in times:
return workhours
elif "work" in times:
return workhours
elif "all day" in times:
return allday
elif "closed" in times:
return closed
elif "null" in times:
return closed

raise ValueError("string '" + times_shortcut + "' does not match a known pattern")

raise ValueError("string '" + times_shortcut or "[NoneType]" + "' does not match a known pattern")


def __init__(self, start_time, end_time):
if start_time is None or end_time is None:
raise TypeError("Cannot create Times Object from value None")
"""
Creates a Times object from two Time objects
"""
# if start_time is None or end_time is None:
# raise TypeError("Cannot create Times Object from value None")

logger.debug("creating times from " + str(start_time) + " and " + str(end_time))
logger.debug("creating times from " + str(start_time or "None") + " and " + str(end_time or "None"))

self.start_time = start_time
self.end_time = end_time
Expand All @@ -110,6 +118,11 @@ def get_start_time(self):

def get_end_time(self):
return self.end_time

def is_closed(self):
has_none = self.start_time is None or self.end_time is None
times_match = self.start_time == self.end_time
return has_none or times_match

#TODO: possibly add a function to see if a single Time is within the range
# specified by this Times object
Expand All @@ -118,7 +131,20 @@ def get_end_time(self):
#TODO: getduration function

def __str__(self):
return self.start_time + to + self.end_time
if self.is_closed():
return "closed"
else:
return str(self.start_time) + " to " + str(self.end_time)


def json(self):
if self.is_closed():
return {}
else:
return {
"opens": str(self.start_time.get_as_military_time()),
"closes": str(self.end_time.get_as_military_time())
}


def __eq__(self, other):
Expand Down
6 changes: 2 additions & 4 deletions opening_hours/parse_opening_hours.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from opening_hours.models.days import Days
from opening_hours.models.time import Time, TimeType
from opening_hours.models.times import Times
import unicodedata
from opening_hours.helpers import normalize_string
import os
import logging

Expand All @@ -33,7 +33,7 @@ class OpeningHours():
def parse(cls, hours_string, assume_type=None):
"""This parse function allows an OpeningHours instance to be created from most arbitrary strings representing opening hours using pyparsing."""

hours_string = unicodedata.normalize('NFC', hours_string)
hours_string = normalize_string(hours_string)
# TODO: handle unicode confuseables
# TODO: handle special cases taht apply to beoth data and time, like "24/7"

Expand All @@ -46,7 +46,6 @@ def parse(cls, hours_string, assume_type=None):
for p in pattern.scanString(hours_string):
logger.debug(p)

hours_string = hours_string.strip()

return cls(opening_hours_format.parseString(hours_string), assume_type=assume_type)

Expand Down Expand Up @@ -90,5 +89,4 @@ def create_entry(day, opening, closing, notes=None):
entry["notes"] = notes
return entry


# print(OpeningHours.parse("by appointment Sunday \u2013 Wednesday from 9 a.m. to 5 p.m."))
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# This call to setup() does all the work
setup(
name="parse-opening-hours",
version="0.4.0",
version="0.4.1",
description="Parses opening hours from various human-readable strings into a standard JSON format",
long_description=README,
long_description_content_type="text/markdown",
Expand Down
37 changes: 34 additions & 3 deletions tests/test_times.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,47 @@

class TestTimes(unittest.TestCase):

def test_create_closed(self):
closed_list = [
Times(None, None),
Times(Time(12,0), Time(12,0))
]
for time in closed_list:
self.assertTrue(time.is_closed())

def test_create_from_none(self):
with self.assertRaises(TypeError):
Times.from_shortcut_string(None)
with self.assertRaises(TypeError):
Times(None, None)
Times.parse(None)

def test_create_from_unknown(self):
with self.assertRaises(ValueError):
Times.from_shortcut_string("cheeseburger")

def test_parse_time_formats(self):
expected_value = Times(Time(7,0, TimeType.AM), Time(5,0, TimeType.PM))
input_strings = [
"700AM-500PM",
]

self.run_tests(input_strings, expected_value)

# def test_shortcuts(self):
# "24/7",
# "Closed",
# for time in input_strings:
# self.assertTrue(Times.from_shortcut_string(time).is_closed())


def test_is_closed(self):
input_strings = [
"Closed",
"null"
]
for time in input_strings:
self.assertTrue(Times.from_shortcut_string(time).is_closed())

# def test_from_parse_regular(self):
# test_dict = {
# "hour": 5,
Expand Down Expand Up @@ -128,8 +159,8 @@ def test_equals(self):

def run_tests(self, input_strings, expected_result, **kwargs):
for input_str in input_strings:
print("Testing String: '", input_str)
self.assertEqual(list(Times.from_shortcut_string(input_str, **kwargs)), expected_result)
print("Testing String: '"+ input_str + "'")
self.assertEqual(Times.parse(input_str, **kwargs), expected_result)

if __name__ == '__main__':
unittest.main()

0 comments on commit 2a58ca7

Please sign in to comment.