opening_hours: global rework to make module more atomic and add somes new helper methods
Some checks failed
Run tests / tests (push) Failing after 1m35s
Some checks failed
Run tests / tests (push) Failing after 1m35s
This commit is contained in:
parent
eb87516e1a
commit
3cf6a2682c
2 changed files with 1132 additions and 98 deletions
|
@ -11,6 +11,7 @@ week_days = ["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "diman
|
||||||
date_format = "%d/%m/%Y"
|
date_format = "%d/%m/%Y"
|
||||||
date_pattern = re.compile("^([0-9]{2})/([0-9]{2})/([0-9]{4})$")
|
date_pattern = re.compile("^([0-9]{2})/([0-9]{2})/([0-9]{4})$")
|
||||||
time_pattern = re.compile("^([0-9]{1,2})h([0-9]{2})?$")
|
time_pattern = re.compile("^([0-9]{1,2})h([0-9]{2})?$")
|
||||||
|
_nonworking_french_public_days_of_the_year_cache = {}
|
||||||
|
|
||||||
|
|
||||||
def easter_date(year):
|
def easter_date(year):
|
||||||
|
@ -37,8 +38,9 @@ def nonworking_french_public_days_of_the_year(year=None):
|
||||||
"""Compute dict of nonworking french public days for the specified year"""
|
"""Compute dict of nonworking french public days for the specified year"""
|
||||||
if year is None:
|
if year is None:
|
||||||
year = datetime.date.today().year
|
year = datetime.date.today().year
|
||||||
|
if year not in _nonworking_french_public_days_of_the_year_cache:
|
||||||
dp = easter_date(year)
|
dp = easter_date(year)
|
||||||
return {
|
_nonworking_french_public_days_of_the_year_cache[year] = {
|
||||||
"1janvier": datetime.date(year, 1, 1),
|
"1janvier": datetime.date(year, 1, 1),
|
||||||
"paques": dp,
|
"paques": dp,
|
||||||
"lundi_paques": (dp + datetime.timedelta(1)),
|
"lundi_paques": (dp + datetime.timedelta(1)),
|
||||||
|
@ -54,6 +56,7 @@ def nonworking_french_public_days_of_the_year(year=None):
|
||||||
"noel": datetime.date(year, 12, 25),
|
"noel": datetime.date(year, 12, 25),
|
||||||
"saint_etienne": datetime.date(year, 12, 26),
|
"saint_etienne": datetime.date(year, 12, 26),
|
||||||
}
|
}
|
||||||
|
return _nonworking_french_public_days_of_the_year_cache[year]
|
||||||
|
|
||||||
|
|
||||||
def parse_exceptional_closures(values):
|
def parse_exceptional_closures(values):
|
||||||
|
@ -155,7 +158,153 @@ def parse_normal_opening_hours(values):
|
||||||
if not days and not hours_periods:
|
if not days and not hours_periods:
|
||||||
raise ValueError(f'No days or hours period found in this value: "{value}"')
|
raise ValueError(f'No days or hours period found in this value: "{value}"')
|
||||||
normal_opening_hours.append({"days": days, "hours_periods": hours_periods})
|
normal_opening_hours.append({"days": days, "hours_periods": hours_periods})
|
||||||
return normal_opening_hours
|
for idx, noh in enumerate(normal_opening_hours):
|
||||||
|
normal_opening_hours[idx]["hours_periods"] = sorted_hours_periods(noh["hours_periods"])
|
||||||
|
return sorted_opening_hours(normal_opening_hours)
|
||||||
|
|
||||||
|
|
||||||
|
def sorted_hours_periods(hours_periods):
|
||||||
|
"""Sort hours periods"""
|
||||||
|
return sorted(hours_periods, key=lambda hp: (hp["start"], hp["stop"]))
|
||||||
|
|
||||||
|
|
||||||
|
def sorted_opening_hours(opening_hours):
|
||||||
|
"""Sort opening hours"""
|
||||||
|
return sorted(
|
||||||
|
opening_hours,
|
||||||
|
key=lambda x: (
|
||||||
|
week_days.index(x["days"][0]) if x["days"] else None,
|
||||||
|
x["hours_periods"][0]["start"] if x["hours_periods"] else datetime.datetime.min.time(),
|
||||||
|
x["hours_periods"][0]["stop"] if x["hours_periods"] else datetime.datetime.max.time(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def its_nonworking_day(nonworking_public_holidays_values, date=None):
|
||||||
|
"""Check if is a non-working day"""
|
||||||
|
if not nonworking_public_holidays_values:
|
||||||
|
return False
|
||||||
|
date = date if date else datetime.date.today()
|
||||||
|
log.debug("its_nonworking_day(%s): values=%s", date, nonworking_public_holidays_values)
|
||||||
|
nonworking_days = nonworking_french_public_days_of_the_year(year=date.year)
|
||||||
|
for day in nonworking_public_holidays_values:
|
||||||
|
if day in nonworking_days and nonworking_days[day] == date:
|
||||||
|
log.debug("its_nonworking_day(%s): %s", date, day)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def its_exceptionally_closed(exceptional_closures_values, when=None, parse=True, all_day=False):
|
||||||
|
"""Check if it's exceptionally closed"""
|
||||||
|
if not exceptional_closures_values:
|
||||||
|
return False
|
||||||
|
when = when if when else datetime.datetime.now()
|
||||||
|
assert isinstance(when, (datetime.date, datetime.datetime))
|
||||||
|
when_date = when.date() if isinstance(when, datetime.datetime) else when
|
||||||
|
exceptional_closures = (
|
||||||
|
parse_exceptional_closures(exceptional_closures_values)
|
||||||
|
if parse
|
||||||
|
else exceptional_closures_values
|
||||||
|
)
|
||||||
|
log.debug("its_exceptionally_closed(%s): exceptional closures=%s", when, exceptional_closures)
|
||||||
|
for cl in exceptional_closures:
|
||||||
|
if when_date not in cl["days"]:
|
||||||
|
log.debug(
|
||||||
|
"its_exceptionally_closed(%s): %s not in days (%s)", when, when_date, cl["days"]
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
if not cl["hours_periods"]:
|
||||||
|
# All day exceptional closure
|
||||||
|
return True
|
||||||
|
if all_day:
|
||||||
|
# Wanted an all day closure, ignore it
|
||||||
|
continue
|
||||||
|
for hp in cl["hours_periods"]:
|
||||||
|
if hp["start"] <= when.time() <= hp["stop"]:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_exceptional_closures_hours(exceptional_closures_values, date=None, parse=True):
|
||||||
|
"""Get exceptional closures hours of the day"""
|
||||||
|
if not exceptional_closures_values:
|
||||||
|
return []
|
||||||
|
date = date if date else datetime.date.today()
|
||||||
|
exceptional_closures = (
|
||||||
|
parse_exceptional_closures(exceptional_closures_values)
|
||||||
|
if parse
|
||||||
|
else exceptional_closures_values
|
||||||
|
)
|
||||||
|
log.debug(
|
||||||
|
"get_exceptional_closures_hours(%s): exceptional closures=%s", date, exceptional_closures
|
||||||
|
)
|
||||||
|
exceptional_closures_hours = []
|
||||||
|
for cl in exceptional_closures:
|
||||||
|
if date not in cl["days"]:
|
||||||
|
log.debug("get_exceptional_closures_hours(%s): not in days (%s)", date, cl["days"])
|
||||||
|
continue
|
||||||
|
if not cl["hours_periods"]:
|
||||||
|
log.debug(
|
||||||
|
"get_exceptional_closures_hours(%s): it's exceptionally closed all the day", date
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"start": datetime.datetime.min.time(),
|
||||||
|
"stop": datetime.datetime.max.time(),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
exceptional_closures_hours.extend(cl["hours_periods"])
|
||||||
|
log.debug(
|
||||||
|
"get_exceptional_closures_hours(%s): exceptional closures hours=%s",
|
||||||
|
date,
|
||||||
|
exceptional_closures_hours,
|
||||||
|
)
|
||||||
|
return sorted_hours_periods(exceptional_closures_hours)
|
||||||
|
|
||||||
|
|
||||||
|
def its_normally_open(normal_opening_hours_values, when=None, parse=True, ignore_time=False):
|
||||||
|
"""Check if it's normally open"""
|
||||||
|
when = when if when else datetime.datetime.now()
|
||||||
|
if not normal_opening_hours_values:
|
||||||
|
log.debug(
|
||||||
|
"its_normally_open(%s): no normal opening hours defined, consider as opened", when
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
when_weekday = week_days[when.timetuple().tm_wday]
|
||||||
|
log.debug("its_normally_open(%s): week day=%s", when, when_weekday)
|
||||||
|
normal_opening_hours = (
|
||||||
|
parse_normal_opening_hours(normal_opening_hours_values)
|
||||||
|
if parse
|
||||||
|
else normal_opening_hours_values
|
||||||
|
)
|
||||||
|
log.debug("its_normally_open(%s): normal opening hours=%s", when, normal_opening_hours)
|
||||||
|
for oh in normal_opening_hours:
|
||||||
|
if oh["days"] and when_weekday not in oh["days"]:
|
||||||
|
log.debug("its_normally_open(%s): %s not in days (%s)", when, when_weekday, oh["days"])
|
||||||
|
continue
|
||||||
|
if not oh["hours_periods"] or ignore_time:
|
||||||
|
return True
|
||||||
|
for hp in oh["hours_periods"]:
|
||||||
|
if hp["start"] <= when.time() <= hp["stop"]:
|
||||||
|
return True
|
||||||
|
log.debug("its_normally_open(%s): not in normal opening hours", when)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def its_opening_day(
|
||||||
|
normal_opening_hours_values=None,
|
||||||
|
exceptional_closures_values=None,
|
||||||
|
nonworking_public_holidays_values=None,
|
||||||
|
date=None,
|
||||||
|
parse=True,
|
||||||
|
):
|
||||||
|
"""Check if it's an opening day"""
|
||||||
|
date = date if date else datetime.date.today()
|
||||||
|
if its_nonworking_day(nonworking_public_holidays_values, date=date):
|
||||||
|
return False
|
||||||
|
if its_exceptionally_closed(exceptional_closures_values, when=date, all_day=True, parse=parse):
|
||||||
|
return False
|
||||||
|
return its_normally_open(normal_opening_hours_values, when=date, parse=parse, ignore_time=True)
|
||||||
|
|
||||||
|
|
||||||
def is_closed(
|
def is_closed(
|
||||||
|
@ -193,76 +342,578 @@ def is_closed(
|
||||||
when_time,
|
when_time,
|
||||||
when_weekday,
|
when_weekday,
|
||||||
)
|
)
|
||||||
if nonworking_public_holidays_values:
|
# Handle non-working days
|
||||||
log.debug("Nonworking public holidays: %s", nonworking_public_holidays_values)
|
if its_nonworking_day(nonworking_public_holidays_values, date=when_date):
|
||||||
nonworking_days = nonworking_french_public_days_of_the_year()
|
|
||||||
for day in nonworking_public_holidays_values:
|
|
||||||
if day in nonworking_days and when_date == nonworking_days[day]:
|
|
||||||
log.debug("Non working day: %s", day)
|
|
||||||
return {
|
return {
|
||||||
"closed": True,
|
"closed": True,
|
||||||
"exceptional_closure": exceptional_closure_on_nonworking_public_days,
|
"exceptional_closure": exceptional_closure_on_nonworking_public_days,
|
||||||
"exceptional_closure_all_day": exceptional_closure_on_nonworking_public_days,
|
"exceptional_closure_all_day": exceptional_closure_on_nonworking_public_days,
|
||||||
}
|
}
|
||||||
|
|
||||||
if exceptional_closures_values:
|
# Handle exceptional closures
|
||||||
try:
|
try:
|
||||||
exceptional_closures = parse_exceptional_closures(exceptional_closures_values)
|
if its_exceptionally_closed(exceptional_closures_values, when=when):
|
||||||
log.debug("Exceptional closures: %s", exceptional_closures)
|
return {
|
||||||
|
"closed": True,
|
||||||
|
"exceptional_closure": True,
|
||||||
|
"exceptional_closure_all_day": its_exceptionally_closed(
|
||||||
|
exceptional_closures_values, when=when, all_day=True
|
||||||
|
),
|
||||||
|
}
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
log.error("Fail to parse exceptional closures, consider as closed", exc_info=True)
|
|
||||||
if on_error_result is None:
|
if on_error_result is None:
|
||||||
|
log.error("Fail to parse exceptional closures", exc_info=True)
|
||||||
raise e from e
|
raise e from e
|
||||||
|
log.error("Fail to parse exceptional closures, consider as %s", on_error, exc_info=True)
|
||||||
return on_error_result
|
return on_error_result
|
||||||
for cl in exceptional_closures:
|
|
||||||
if when_date not in cl["days"]:
|
|
||||||
log.debug("when_date (%s) no in days (%s)", when_date, cl["days"])
|
|
||||||
continue
|
|
||||||
if not cl["hours_periods"]:
|
|
||||||
# All day exceptional closure
|
|
||||||
return {
|
|
||||||
"closed": True,
|
|
||||||
"exceptional_closure": True,
|
|
||||||
"exceptional_closure_all_day": True,
|
|
||||||
}
|
|
||||||
for hp in cl["hours_periods"]:
|
|
||||||
if hp["start"] <= when_time <= hp["stop"]:
|
|
||||||
return {
|
|
||||||
"closed": True,
|
|
||||||
"exceptional_closure": True,
|
|
||||||
"exceptional_closure_all_day": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
if normal_opening_hours_values:
|
# Finally, handle normal opening hours
|
||||||
try:
|
try:
|
||||||
normal_opening_hours = parse_normal_opening_hours(normal_opening_hours_values)
|
return {
|
||||||
log.debug("Normal opening hours: %s", normal_opening_hours)
|
"closed": not its_normally_open(normal_opening_hours_values, when=when),
|
||||||
|
"exceptional_closure": False,
|
||||||
|
"exceptional_closure_all_day": False,
|
||||||
|
}
|
||||||
except ValueError as e: # pylint: disable=broad-except
|
except ValueError as e: # pylint: disable=broad-except
|
||||||
log.error("Fail to parse normal opening hours, consider as closed", exc_info=True)
|
|
||||||
if on_error_result is None:
|
if on_error_result is None:
|
||||||
|
log.error("Fail to parse normal opening hours", exc_info=True)
|
||||||
raise e from e
|
raise e from e
|
||||||
|
log.error("Fail to parse normal opening hours, consider as %s", on_error, exc_info=True)
|
||||||
return on_error_result
|
return on_error_result
|
||||||
for oh in normal_opening_hours:
|
|
||||||
if oh["days"] and when_weekday not in oh["days"]:
|
|
||||||
log.debug("when_weekday (%s) no in days (%s)", when_weekday, oh["days"])
|
|
||||||
continue
|
|
||||||
if not oh["hours_periods"]:
|
|
||||||
# All day opened
|
|
||||||
return {
|
|
||||||
"closed": False,
|
|
||||||
"exceptional_closure": False,
|
|
||||||
"exceptional_closure_all_day": False,
|
|
||||||
}
|
|
||||||
for hp in oh["hours_periods"]:
|
|
||||||
if hp["start"] <= when_time <= hp["stop"]:
|
|
||||||
return {
|
|
||||||
"closed": False,
|
|
||||||
"exceptional_closure": False,
|
|
||||||
"exceptional_closure_all_day": False,
|
|
||||||
}
|
|
||||||
log.debug("Not in normal opening hours => closed")
|
|
||||||
return {"closed": True, "exceptional_closure": False, "exceptional_closure_all_day": False}
|
|
||||||
|
|
||||||
# Not a nonworking day, not during exceptional closure and no normal opening
|
|
||||||
# hours defined => Opened
|
def next_opening_date(
|
||||||
return {"closed": False, "exceptional_closure": False, "exceptional_closure_all_day": False}
|
normal_opening_hours_values=None,
|
||||||
|
exceptional_closures_values=None,
|
||||||
|
nonworking_public_holidays_values=None,
|
||||||
|
date=None,
|
||||||
|
max_anaylse_days=None,
|
||||||
|
parse=True,
|
||||||
|
):
|
||||||
|
"""Search for the next opening day"""
|
||||||
|
date = date if date else datetime.date.today()
|
||||||
|
max_anaylse_days = max_anaylse_days if max_anaylse_days is not None else 30
|
||||||
|
if parse:
|
||||||
|
try:
|
||||||
|
normal_opening_hours_values = (
|
||||||
|
parse_normal_opening_hours(normal_opening_hours_values)
|
||||||
|
if normal_opening_hours_values
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
exceptional_closures_values = (
|
||||||
|
parse_exceptional_closures(exceptional_closures_values)
|
||||||
|
if exceptional_closures_values
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
except ValueError: # pylint: disable=broad-except
|
||||||
|
log.error(
|
||||||
|
"next_opening_date(%s): fail to parse normal opening hours or exceptional closures",
|
||||||
|
date,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
added_days = 0
|
||||||
|
while added_days <= max_anaylse_days:
|
||||||
|
test_date = date + datetime.timedelta(days=added_days)
|
||||||
|
if its_opening_day(
|
||||||
|
normal_opening_hours_values=normal_opening_hours_values,
|
||||||
|
exceptional_closures_values=exceptional_closures_values,
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays_values,
|
||||||
|
date=test_date,
|
||||||
|
parse=False,
|
||||||
|
):
|
||||||
|
return test_date
|
||||||
|
added_days += 1
|
||||||
|
log.debug(
|
||||||
|
"next_opening_date(%s): no opening day found in the next %d days", date, max_anaylse_days
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def next_opening_hour(
|
||||||
|
normal_opening_hours_values=None,
|
||||||
|
exceptional_closures_values=None,
|
||||||
|
nonworking_public_holidays_values=None,
|
||||||
|
when=None,
|
||||||
|
max_anaylse_days=None,
|
||||||
|
parse=True,
|
||||||
|
):
|
||||||
|
"""Search for the next opening hour"""
|
||||||
|
when = when if when else datetime.datetime.now()
|
||||||
|
max_anaylse_days = max_anaylse_days if max_anaylse_days is not None else 30
|
||||||
|
if parse:
|
||||||
|
try:
|
||||||
|
normal_opening_hours_values = (
|
||||||
|
parse_normal_opening_hours(normal_opening_hours_values)
|
||||||
|
if normal_opening_hours_values
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
exceptional_closures_values = (
|
||||||
|
parse_exceptional_closures(exceptional_closures_values)
|
||||||
|
if exceptional_closures_values
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
except ValueError: # pylint: disable=broad-except
|
||||||
|
log.error(
|
||||||
|
"next_opening_hour(%s): fail to parse normal opening hours or exceptional closures",
|
||||||
|
when,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
date = next_opening_date(
|
||||||
|
normal_opening_hours_values=normal_opening_hours_values,
|
||||||
|
exceptional_closures_values=exceptional_closures_values,
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays_values,
|
||||||
|
date=when.date(),
|
||||||
|
max_anaylse_days=max_anaylse_days,
|
||||||
|
parse=False,
|
||||||
|
)
|
||||||
|
if not date:
|
||||||
|
log.debug(
|
||||||
|
"next_opening_hour(%s): no opening day found in the next %d days",
|
||||||
|
when,
|
||||||
|
max_anaylse_days,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
log.debug("next_opening_hour(%s): next opening date=%s", when, date)
|
||||||
|
weekday = week_days[date.timetuple().tm_wday]
|
||||||
|
log.debug("next_opening_hour(%s): next opening week day=%s", when, weekday)
|
||||||
|
exceptional_closures_hours = get_exceptional_closures_hours(
|
||||||
|
exceptional_closures_values, date=date, parse=False
|
||||||
|
)
|
||||||
|
log.debug(
|
||||||
|
"next_opening_hour(%s): next opening day exceptional closures hours=%s",
|
||||||
|
when,
|
||||||
|
exceptional_closures_hours,
|
||||||
|
)
|
||||||
|
next_opening_datetime = None
|
||||||
|
exceptionally_closed = False
|
||||||
|
exceptionally_closed_all_day = False
|
||||||
|
in_opening_hours = date != when.date()
|
||||||
|
for oh in normal_opening_hours_values:
|
||||||
|
if exceptionally_closed_all_day:
|
||||||
|
break
|
||||||
|
|
||||||
|
if oh["days"] and weekday not in oh["days"]:
|
||||||
|
log.debug("next_opening_hour(%s): %s not in days (%s)", when, weekday, oh["days"])
|
||||||
|
continue
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"next_opening_hour(%s): %s in days (%s), handle opening hours %s",
|
||||||
|
when,
|
||||||
|
weekday,
|
||||||
|
oh["days"],
|
||||||
|
oh["hours_periods"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if not oh["hours_periods"]:
|
||||||
|
log.debug(
|
||||||
|
"next_opening_hour(%s): %s is an all day opening day, handle exceptional closures "
|
||||||
|
"hours %s to find the minimal opening time",
|
||||||
|
when,
|
||||||
|
weekday,
|
||||||
|
exceptional_closures_hours,
|
||||||
|
)
|
||||||
|
if date == when.date():
|
||||||
|
in_opening_hours = True
|
||||||
|
test_time = when.time() if when.date() == date else datetime.datetime.min.time()
|
||||||
|
for cl in exceptional_closures_hours:
|
||||||
|
if cl["start"] <= test_time < cl["stop"]:
|
||||||
|
if cl["stop"] >= datetime.datetime.max.time():
|
||||||
|
exceptionally_closed = True
|
||||||
|
exceptionally_closed_all_day = True
|
||||||
|
next_opening_datetime = None
|
||||||
|
break
|
||||||
|
test_time = cl["stop"]
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
if not exceptionally_closed_all_day:
|
||||||
|
candidate_next_opening_datetime = datetime.datetime.combine(date, test_time)
|
||||||
|
next_opening_datetime = (
|
||||||
|
candidate_next_opening_datetime
|
||||||
|
if not next_opening_datetime
|
||||||
|
or candidate_next_opening_datetime < next_opening_datetime
|
||||||
|
else next_opening_datetime
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"next_opening_hour(%s): only opened during some hours periods (%s) on %s, find the "
|
||||||
|
"minimal starting time",
|
||||||
|
when,
|
||||||
|
oh["hours_periods"],
|
||||||
|
weekday,
|
||||||
|
)
|
||||||
|
test_time = datetime.datetime.max.time()
|
||||||
|
for hp in oh["hours_periods"]:
|
||||||
|
if date == when.date() and hp["stop"] < when.time():
|
||||||
|
log.debug(
|
||||||
|
"next_opening_hour(%s): ignore opening hours %s before specified when time %s",
|
||||||
|
when,
|
||||||
|
hp,
|
||||||
|
when.time(),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
if date == when.date() and hp["start"] <= when.time() < hp["stop"]:
|
||||||
|
in_opening_hours = True
|
||||||
|
if exceptional_closures_hours:
|
||||||
|
log.debug(
|
||||||
|
"next_opening_hour(%s): check if opening hours %s match with exceptional "
|
||||||
|
"closure hours %s",
|
||||||
|
when,
|
||||||
|
hp,
|
||||||
|
exceptional_closures_hours,
|
||||||
|
)
|
||||||
|
for cl in exceptional_closures_hours:
|
||||||
|
if cl["start"] <= hp["start"] and cl["stop"] >= hp["stop"]:
|
||||||
|
log.debug(
|
||||||
|
"next_opening_hour(%s): opening hour %s is included in exceptional "
|
||||||
|
"closure hours %s",
|
||||||
|
when,
|
||||||
|
hp,
|
||||||
|
cl,
|
||||||
|
)
|
||||||
|
exceptionally_closed = True
|
||||||
|
break
|
||||||
|
if hp["start"] < cl["start"]:
|
||||||
|
log.debug(
|
||||||
|
"next_opening_hour(%s): opening hour %s start before closure hours %s",
|
||||||
|
when,
|
||||||
|
hp,
|
||||||
|
cl,
|
||||||
|
)
|
||||||
|
test_time = hp["start"] if hp["start"] < test_time else test_time
|
||||||
|
elif cl["stop"] >= hp["start"] and cl["stop"] < hp["stop"]:
|
||||||
|
log.debug(
|
||||||
|
"next_opening_hour(%s): opening hour %s end after closure hours %s",
|
||||||
|
when,
|
||||||
|
hp,
|
||||||
|
cl,
|
||||||
|
)
|
||||||
|
test_time = cl["stop"] if cl["stop"] < test_time else test_time
|
||||||
|
elif hp["start"] < test_time:
|
||||||
|
log.debug(
|
||||||
|
"next_opening_hour(%s): no exceptional closure hours, use opening hours start "
|
||||||
|
"time %s",
|
||||||
|
when,
|
||||||
|
hp["start"],
|
||||||
|
)
|
||||||
|
test_time = hp["start"]
|
||||||
|
|
||||||
|
if test_time < datetime.datetime.max.time():
|
||||||
|
if date == when.date() and test_time < when.time():
|
||||||
|
test_time = when.time()
|
||||||
|
candidate_next_opening_datetime = datetime.datetime.combine(date, test_time)
|
||||||
|
next_opening_datetime = (
|
||||||
|
candidate_next_opening_datetime
|
||||||
|
if not next_opening_datetime
|
||||||
|
or candidate_next_opening_datetime < next_opening_datetime
|
||||||
|
else next_opening_datetime
|
||||||
|
)
|
||||||
|
|
||||||
|
if not next_opening_datetime and (
|
||||||
|
exceptionally_closed or (date == when.date() and not in_opening_hours)
|
||||||
|
):
|
||||||
|
new_max_anaylse_days = max_anaylse_days - (date - when.date()).days
|
||||||
|
if new_max_anaylse_days > 0:
|
||||||
|
log.debug(
|
||||||
|
"next_opening_hour(%s): exceptionally closed on %s, try on following %d days",
|
||||||
|
when,
|
||||||
|
date,
|
||||||
|
new_max_anaylse_days,
|
||||||
|
)
|
||||||
|
next_opening_datetime = next_opening_hour(
|
||||||
|
normal_opening_hours_values=normal_opening_hours_values,
|
||||||
|
exceptional_closures_values=exceptional_closures_values,
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays_values,
|
||||||
|
when=datetime.datetime.combine(
|
||||||
|
date + datetime.timedelta(days=1), datetime.datetime.min.time()
|
||||||
|
),
|
||||||
|
max_anaylse_days=new_max_anaylse_days,
|
||||||
|
parse=False,
|
||||||
|
)
|
||||||
|
if not next_opening_datetime:
|
||||||
|
log.debug(
|
||||||
|
"next_opening_hour(%s): no opening hours found in next %d days", when, max_anaylse_days
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
log.debug("next_opening_hour(%s): next opening hours=%s", when, next_opening_datetime)
|
||||||
|
return next_opening_datetime
|
||||||
|
|
||||||
|
|
||||||
|
def previous_opening_date(
|
||||||
|
normal_opening_hours_values=None,
|
||||||
|
exceptional_closures_values=None,
|
||||||
|
nonworking_public_holidays_values=None,
|
||||||
|
date=None,
|
||||||
|
max_anaylse_days=None,
|
||||||
|
parse=True,
|
||||||
|
):
|
||||||
|
"""Search for the previous opening day"""
|
||||||
|
date = date if date else datetime.date.today()
|
||||||
|
max_anaylse_days = max_anaylse_days if max_anaylse_days is not None else 30
|
||||||
|
if parse:
|
||||||
|
try:
|
||||||
|
normal_opening_hours_values = (
|
||||||
|
parse_normal_opening_hours(normal_opening_hours_values)
|
||||||
|
if normal_opening_hours_values
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
exceptional_closures_values = (
|
||||||
|
parse_exceptional_closures(exceptional_closures_values)
|
||||||
|
if exceptional_closures_values
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
except ValueError: # pylint: disable=broad-except
|
||||||
|
log.error(
|
||||||
|
"previous_opening_date(%s): fail to parse normal opening hours or exceptional "
|
||||||
|
"closures",
|
||||||
|
date,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
days = 0
|
||||||
|
while days <= max_anaylse_days:
|
||||||
|
test_date = date - datetime.timedelta(days=days)
|
||||||
|
if its_opening_day(
|
||||||
|
normal_opening_hours_values=normal_opening_hours_values,
|
||||||
|
exceptional_closures_values=exceptional_closures_values,
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays_values,
|
||||||
|
date=test_date,
|
||||||
|
parse=False,
|
||||||
|
):
|
||||||
|
return test_date
|
||||||
|
days += 1
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_date(%s): no opening day found in the next %d days",
|
||||||
|
date,
|
||||||
|
max_anaylse_days,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def previous_opening_hour(
|
||||||
|
normal_opening_hours_values=None,
|
||||||
|
exceptional_closures_values=None,
|
||||||
|
nonworking_public_holidays_values=None,
|
||||||
|
when=None,
|
||||||
|
max_anaylse_days=None,
|
||||||
|
parse=True,
|
||||||
|
):
|
||||||
|
"""Search for the previous opening hour"""
|
||||||
|
when = when if when else datetime.datetime.now()
|
||||||
|
max_anaylse_days = max_anaylse_days if max_anaylse_days is not None else 30
|
||||||
|
if parse:
|
||||||
|
try:
|
||||||
|
normal_opening_hours_values = (
|
||||||
|
parse_normal_opening_hours(normal_opening_hours_values)
|
||||||
|
if normal_opening_hours_values
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
exceptional_closures_values = (
|
||||||
|
parse_exceptional_closures(exceptional_closures_values)
|
||||||
|
if exceptional_closures_values
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
except ValueError: # pylint: disable=broad-except
|
||||||
|
log.error(
|
||||||
|
"previous_opening_hour(%s): fail to parse normal opening hours or exceptional "
|
||||||
|
"closures",
|
||||||
|
when,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
date = previous_opening_date(
|
||||||
|
normal_opening_hours_values=normal_opening_hours_values,
|
||||||
|
exceptional_closures_values=exceptional_closures_values,
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays_values,
|
||||||
|
date=when.date(),
|
||||||
|
max_anaylse_days=max_anaylse_days,
|
||||||
|
parse=False,
|
||||||
|
)
|
||||||
|
if not date:
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): no opening day found in the previous %d days",
|
||||||
|
when,
|
||||||
|
max_anaylse_days,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
log.debug("previous_opening_hour(%s): previous opening date=%s", when, date)
|
||||||
|
weekday = week_days[date.timetuple().tm_wday]
|
||||||
|
log.debug("previous_opening_hour(%s): previous opening week day=%s", when, weekday)
|
||||||
|
exceptional_closures_hours = get_exceptional_closures_hours(
|
||||||
|
exceptional_closures_values, date=date, parse=False
|
||||||
|
)
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): previous opening day exceptional closures hours=%s",
|
||||||
|
when,
|
||||||
|
exceptional_closures_hours,
|
||||||
|
)
|
||||||
|
previous_opening_datetime = None
|
||||||
|
exceptionally_closed = False
|
||||||
|
exceptionally_closed_all_day = False
|
||||||
|
in_opening_hours = date != when.date()
|
||||||
|
for oh in reversed(normal_opening_hours_values):
|
||||||
|
if exceptionally_closed_all_day:
|
||||||
|
break
|
||||||
|
|
||||||
|
if oh["days"] and weekday not in oh["days"]:
|
||||||
|
log.debug("previous_opening_hour(%s): %s not in days (%s)", when, weekday, oh["days"])
|
||||||
|
continue
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): %s in days (%s), handle opening hours %s",
|
||||||
|
when,
|
||||||
|
weekday,
|
||||||
|
oh["days"],
|
||||||
|
oh["hours_periods"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if not oh["hours_periods"]:
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): %s is an all day opening day, handle exceptional "
|
||||||
|
"closures hours %s to find the maximal opening time",
|
||||||
|
when,
|
||||||
|
weekday,
|
||||||
|
exceptional_closures_hours,
|
||||||
|
)
|
||||||
|
if date == when.date():
|
||||||
|
in_opening_hours = True
|
||||||
|
test_time = when.time() if when.date() == date else datetime.datetime.max.time()
|
||||||
|
for cl in exceptional_closures_hours:
|
||||||
|
if cl["start"] <= test_time < cl["stop"]:
|
||||||
|
if cl["start"] <= datetime.datetime.min.time():
|
||||||
|
exceptionally_closed = True
|
||||||
|
exceptionally_closed_all_day = True
|
||||||
|
previous_opening_datetime = None
|
||||||
|
break
|
||||||
|
test_time = cl["start"]
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
if not exceptionally_closed_all_day:
|
||||||
|
candidate_previous_opening_datetime = datetime.datetime.combine(date, test_time)
|
||||||
|
previous_opening_datetime = (
|
||||||
|
candidate_previous_opening_datetime
|
||||||
|
if not previous_opening_datetime
|
||||||
|
or candidate_previous_opening_datetime > previous_opening_datetime
|
||||||
|
else previous_opening_datetime
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): only opened during some hours periods (%s) on %s, find the "
|
||||||
|
"maximal opening time",
|
||||||
|
when,
|
||||||
|
oh["hours_periods"],
|
||||||
|
weekday,
|
||||||
|
)
|
||||||
|
test_time = datetime.datetime.min.time()
|
||||||
|
for hp in reversed(oh["hours_periods"]):
|
||||||
|
if date == when.date() and hp["start"] > when.time():
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): ignore opening hours %s starting before specified "
|
||||||
|
"when time %s",
|
||||||
|
when,
|
||||||
|
hp,
|
||||||
|
when.time(),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
if date == when.date() and hp["start"] <= when.time() < hp["stop"]:
|
||||||
|
in_opening_hours = True
|
||||||
|
if exceptional_closures_hours:
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): check if opening hours %s match with exceptional "
|
||||||
|
"closure hours %s",
|
||||||
|
when,
|
||||||
|
hp,
|
||||||
|
exceptional_closures_hours,
|
||||||
|
)
|
||||||
|
for cl in reversed(exceptional_closures_hours):
|
||||||
|
if cl["start"] <= hp["start"] and cl["stop"] >= hp["stop"]:
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): opening hour %s is included in exceptional "
|
||||||
|
"closure hours %s",
|
||||||
|
when,
|
||||||
|
hp,
|
||||||
|
cl,
|
||||||
|
)
|
||||||
|
exceptionally_closed = True
|
||||||
|
break
|
||||||
|
if cl["stop"] < hp["stop"]:
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): opening hour %s end after closure hours %s",
|
||||||
|
when,
|
||||||
|
hp,
|
||||||
|
cl,
|
||||||
|
)
|
||||||
|
test_time = hp["stop"] if hp["stop"] > test_time else test_time
|
||||||
|
elif cl["start"] > hp["stop"]:
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): opening hour %s start before closure hours "
|
||||||
|
"%s",
|
||||||
|
when,
|
||||||
|
hp,
|
||||||
|
cl,
|
||||||
|
)
|
||||||
|
test_time = hp["stop"] if hp["stop"] > test_time else test_time
|
||||||
|
elif cl["stop"] >= hp["stop"] and cl["start"] > hp["start"]:
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): opening hour %s start before closure hours "
|
||||||
|
"%s",
|
||||||
|
when,
|
||||||
|
hp,
|
||||||
|
cl,
|
||||||
|
)
|
||||||
|
test_time = cl["start"] if cl["start"] > test_time else test_time
|
||||||
|
elif hp["stop"] > test_time:
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): no exceptional closure hours, use opening hours "
|
||||||
|
"stop time %s",
|
||||||
|
when,
|
||||||
|
hp["stop"],
|
||||||
|
)
|
||||||
|
test_time = hp["stop"]
|
||||||
|
|
||||||
|
if test_time > datetime.datetime.min.time():
|
||||||
|
if date == when.date() and test_time > when.time():
|
||||||
|
test_time = when.time()
|
||||||
|
candidate_previous_opening_datetime = datetime.datetime.combine(date, test_time)
|
||||||
|
previous_opening_datetime = (
|
||||||
|
candidate_previous_opening_datetime
|
||||||
|
if not previous_opening_datetime
|
||||||
|
or candidate_previous_opening_datetime > previous_opening_datetime
|
||||||
|
else previous_opening_datetime
|
||||||
|
)
|
||||||
|
|
||||||
|
if not previous_opening_datetime and (
|
||||||
|
exceptionally_closed or (date == when.date() and not in_opening_hours)
|
||||||
|
):
|
||||||
|
new_max_anaylse_days = max_anaylse_days - (when.date() - date).days
|
||||||
|
if new_max_anaylse_days > 0:
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): exceptionally closed on %s, try on previous %d days",
|
||||||
|
when,
|
||||||
|
date,
|
||||||
|
new_max_anaylse_days,
|
||||||
|
)
|
||||||
|
previous_opening_datetime = previous_opening_hour(
|
||||||
|
normal_opening_hours_values=normal_opening_hours_values,
|
||||||
|
exceptional_closures_values=exceptional_closures_values,
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays_values,
|
||||||
|
when=datetime.datetime.combine(
|
||||||
|
date - datetime.timedelta(days=1), datetime.datetime.max.time()
|
||||||
|
),
|
||||||
|
max_anaylse_days=new_max_anaylse_days,
|
||||||
|
parse=False,
|
||||||
|
)
|
||||||
|
if not previous_opening_datetime:
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): no opening hours found in previous %d days",
|
||||||
|
when,
|
||||||
|
max_anaylse_days,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
log.debug(
|
||||||
|
"previous_opening_hour(%s): previous opening hours=%s", when, previous_opening_datetime
|
||||||
|
)
|
||||||
|
return previous_opening_datetime
|
||||||
|
|
|
@ -182,27 +182,96 @@ def test_parse_normal_opening_hours_multiple_periods():
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
#
|
def test_parse_normal_opening_hours_is_sorted():
|
||||||
# Tests on is_closed
|
assert opening_hours.parse_normal_opening_hours(
|
||||||
#
|
[
|
||||||
|
"samedi 9h30-18h",
|
||||||
|
"lundi-vendredi 14h-18h 9h30-12h30",
|
||||||
|
"samedi 9h30-12h",
|
||||||
|
"dimanche 9h30-12h",
|
||||||
|
]
|
||||||
|
) == [
|
||||||
|
{
|
||||||
|
"days": ["lundi", "mardi", "mercredi", "jeudi", "vendredi"],
|
||||||
|
"hours_periods": [
|
||||||
|
{"start": datetime.time(9, 30), "stop": datetime.time(12, 30)},
|
||||||
|
{"start": datetime.time(14, 0), "stop": datetime.time(18, 0)},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"days": ["samedi"],
|
||||||
|
"hours_periods": [
|
||||||
|
{"start": datetime.time(9, 30), "stop": datetime.time(12, 0)},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"days": ["samedi"],
|
||||||
|
"hours_periods": [
|
||||||
|
{"start": datetime.time(9, 30), "stop": datetime.time(18, 0)},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"days": ["dimanche"],
|
||||||
|
"hours_periods": [
|
||||||
|
{"start": datetime.time(9, 30), "stop": datetime.time(12, 0)},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
exceptional_closures = [
|
#
|
||||||
"22/09/2017",
|
# Tests on normal opening hours
|
||||||
"20/09/2017-22/09/2017",
|
#
|
||||||
"20/09/2017-22/09/2017 18/09/2017",
|
|
||||||
"25/11/2017",
|
|
||||||
"26/11/2017 9h30-12h30",
|
|
||||||
]
|
|
||||||
normal_opening_hours = [
|
normal_opening_hours = [
|
||||||
"lundi-mardi jeudi 9h30-12h30 14h-16h30",
|
"lundi-mardi jeudi 9h30-12h30 14h-16h30",
|
||||||
"mercredi vendredi 9h30-12h30 14h-17h",
|
"mercredi vendredi 9h30-12h30 14h-17h",
|
||||||
|
"samedi",
|
||||||
]
|
]
|
||||||
|
normally_opened_datetime = datetime.datetime(2024, 3, 1, 10, 15)
|
||||||
|
normally_opened_all_day_datetime = datetime.datetime(2024, 4, 6, 10, 15)
|
||||||
|
normally_closed_datetime = datetime.datetime(2017, 3, 1, 20, 15)
|
||||||
|
normally_closed_all_day_datetime = datetime.datetime(2024, 4, 7, 20, 15)
|
||||||
|
|
||||||
|
|
||||||
|
def test_its_normally_open():
|
||||||
|
assert opening_hours.its_normally_open(normal_opening_hours, when=normally_opened_datetime)
|
||||||
|
|
||||||
|
|
||||||
|
def test_its_normally_open_all_day():
|
||||||
|
assert opening_hours.its_normally_open(
|
||||||
|
normal_opening_hours, when=normally_opened_all_day_datetime
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_its_normally_closed():
|
||||||
|
assert not opening_hours.its_normally_open(normal_opening_hours, when=normally_closed_datetime)
|
||||||
|
|
||||||
|
|
||||||
|
def test_its_normally_closed_all_day():
|
||||||
|
assert not opening_hours.its_normally_open(
|
||||||
|
normal_opening_hours, when=normally_closed_all_day_datetime
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_its_normally_open_ignore_time():
|
||||||
|
assert opening_hours.its_normally_open(
|
||||||
|
normal_opening_hours, when=normally_closed_datetime.date(), ignore_time=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_its_normally_closed_ignore_time():
|
||||||
|
assert not opening_hours.its_normally_open(
|
||||||
|
normal_opening_hours, when=normally_closed_all_day_datetime.date(), ignore_time=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Tests on non working days
|
||||||
|
#
|
||||||
nonworking_public_holidays = [
|
nonworking_public_holidays = [
|
||||||
"1janvier",
|
"1janvier",
|
||||||
"paques",
|
"paques",
|
||||||
"lundi_paques",
|
"lundi_paques",
|
||||||
"1mai",
|
|
||||||
"8mai",
|
"8mai",
|
||||||
"jeudi_ascension",
|
"jeudi_ascension",
|
||||||
"lundi_pentecote",
|
"lundi_pentecote",
|
||||||
|
@ -212,6 +281,120 @@ nonworking_public_holidays = [
|
||||||
"11novembre",
|
"11novembre",
|
||||||
"noel",
|
"noel",
|
||||||
]
|
]
|
||||||
|
nonworking_date = datetime.date(2017, 1, 1)
|
||||||
|
not_included_nonworking_date = datetime.date(2017, 5, 1)
|
||||||
|
not_nonworking_date = datetime.date(2017, 5, 2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_its_nonworking_day():
|
||||||
|
assert (
|
||||||
|
opening_hours.its_nonworking_day(nonworking_public_holidays, date=nonworking_date) is True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_its_not_nonworking_day():
|
||||||
|
assert (
|
||||||
|
opening_hours.its_nonworking_day(
|
||||||
|
nonworking_public_holidays,
|
||||||
|
date=not_nonworking_date,
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_its_not_included_nonworking_day():
|
||||||
|
assert (
|
||||||
|
opening_hours.its_nonworking_day(
|
||||||
|
nonworking_public_holidays,
|
||||||
|
date=not_included_nonworking_date,
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Tests in exceptional closures
|
||||||
|
#
|
||||||
|
exceptional_closures = [
|
||||||
|
"22/09/2017",
|
||||||
|
"20/09/2017-22/09/2017",
|
||||||
|
"20/09/2017-22/09/2017 18/09/2017",
|
||||||
|
"25/11/2017",
|
||||||
|
"26/11/2017 9h30-12h30",
|
||||||
|
"27/11/2017 17h-18h 9h30-12h30",
|
||||||
|
]
|
||||||
|
exceptional_closure_all_day_date = datetime.date(2017, 9, 22)
|
||||||
|
exceptional_closure_all_day_datetime = datetime.datetime.combine(
|
||||||
|
exceptional_closure_all_day_date, datetime.time(20, 15)
|
||||||
|
)
|
||||||
|
exceptional_closure_datetime = datetime.datetime(2017, 11, 26, 10, 30)
|
||||||
|
exceptional_closure_datetime_hours_period = {
|
||||||
|
"start": datetime.time(9, 30),
|
||||||
|
"stop": datetime.time(12, 30),
|
||||||
|
}
|
||||||
|
not_exceptional_closure_date = datetime.date(2019, 9, 22)
|
||||||
|
|
||||||
|
|
||||||
|
def test_its_exceptionally_closed():
|
||||||
|
assert (
|
||||||
|
opening_hours.its_exceptionally_closed(
|
||||||
|
exceptional_closures, when=exceptional_closure_all_day_datetime
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_its_not_exceptionally_closed():
|
||||||
|
assert (
|
||||||
|
opening_hours.its_exceptionally_closed(
|
||||||
|
exceptional_closures, when=not_exceptional_closure_date
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_its_exceptionally_closed_all_day():
|
||||||
|
assert (
|
||||||
|
opening_hours.its_exceptionally_closed(
|
||||||
|
exceptional_closures, when=exceptional_closure_all_day_datetime, all_day=True
|
||||||
|
)
|
||||||
|
is True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_its_not_exceptionally_closed_all_day():
|
||||||
|
assert (
|
||||||
|
opening_hours.its_exceptionally_closed(
|
||||||
|
exceptional_closures, when=exceptional_closure_datetime, all_day=True
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_exceptional_closures_hours():
|
||||||
|
assert opening_hours.get_exceptional_closures_hours(
|
||||||
|
exceptional_closures, date=exceptional_closure_datetime.date()
|
||||||
|
) == [exceptional_closure_datetime_hours_period]
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_exceptional_closures_hours_all_day():
|
||||||
|
assert opening_hours.get_exceptional_closures_hours(
|
||||||
|
exceptional_closures, date=exceptional_closure_all_day_date
|
||||||
|
) == [{"start": datetime.datetime.min.time(), "stop": datetime.datetime.max.time()}]
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_exceptional_closures_hours_is_sorted():
|
||||||
|
assert opening_hours.get_exceptional_closures_hours(
|
||||||
|
["27/11/2017 17h-18h 9h30-12h30"], date=datetime.date(2017, 11, 27)
|
||||||
|
) == [
|
||||||
|
{"start": datetime.time(9, 30), "stop": datetime.time(12, 30)},
|
||||||
|
{"start": datetime.time(17, 0), "stop": datetime.time(18, 0)},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Tests on is_closed
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
def test_is_closed_when_normaly_closed_by_hour():
|
def test_is_closed_when_normaly_closed_by_hour():
|
||||||
|
@ -255,7 +438,7 @@ def test_is_closed_when_normaly_closed_by_day():
|
||||||
normal_opening_hours_values=normal_opening_hours,
|
normal_opening_hours_values=normal_opening_hours,
|
||||||
exceptional_closures_values=exceptional_closures,
|
exceptional_closures_values=exceptional_closures,
|
||||||
nonworking_public_holidays_values=nonworking_public_holidays,
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
when=datetime.datetime(2017, 5, 6, 14, 15),
|
when=datetime.datetime(2017, 5, 7, 14, 15),
|
||||||
) == {"closed": True, "exceptional_closure": False, "exceptional_closure_all_day": False}
|
) == {"closed": True, "exceptional_closure": False, "exceptional_closure_all_day": False}
|
||||||
|
|
||||||
|
|
||||||
|
@ -300,3 +483,203 @@ def test_nonworking_french_public_days_of_the_year():
|
||||||
"noel": datetime.date(2021, 12, 25),
|
"noel": datetime.date(2021, 12, 25),
|
||||||
"saint_etienne": datetime.date(2021, 12, 26),
|
"saint_etienne": datetime.date(2021, 12, 26),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_next_opening_date():
|
||||||
|
assert opening_hours.next_opening_date(
|
||||||
|
normal_opening_hours_values=normal_opening_hours,
|
||||||
|
exceptional_closures_values=exceptional_closures,
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
date=datetime.date(2021, 4, 4),
|
||||||
|
) == datetime.date(2021, 4, 6)
|
||||||
|
|
||||||
|
|
||||||
|
def test_next_opening_hour():
|
||||||
|
assert opening_hours.next_opening_hour(
|
||||||
|
normal_opening_hours_values=normal_opening_hours,
|
||||||
|
exceptional_closures_values=exceptional_closures,
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2021, 4, 4, 10, 30),
|
||||||
|
) == datetime.datetime(2021, 4, 6, 9, 30)
|
||||||
|
|
||||||
|
|
||||||
|
def test_next_opening_hour_with_exceptionnal_closure_hours():
|
||||||
|
assert opening_hours.next_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=["06/04/2021 9h-13h 14h-16h"],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2021, 4, 4, 10, 30),
|
||||||
|
) == datetime.datetime(2021, 4, 6, 16, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_next_opening_hour_with_exceptionnal_closure_day():
|
||||||
|
assert opening_hours.next_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=["06/04/2021"],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2021, 4, 4, 10, 30),
|
||||||
|
) == datetime.datetime(2021, 4, 7, 9, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_next_opening_hour_with_overlapsed_opening_hours():
|
||||||
|
assert opening_hours.next_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h", "mardi 8h-19h"],
|
||||||
|
exceptional_closures_values=["06/04/2021 9h-13h 14h-16h"],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2021, 4, 4, 10, 30),
|
||||||
|
) == datetime.datetime(2021, 4, 6, 8, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_next_opening_hour_with_too_large_exceptionnal_closure_days():
|
||||||
|
assert (
|
||||||
|
opening_hours.next_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=["06/04/2021-16-04/2021"],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2021, 4, 4, 10, 30),
|
||||||
|
max_anaylse_days=10,
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_next_opening_hour_on_opened_moment():
|
||||||
|
assert opening_hours.next_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=[],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2021, 4, 6, 10, 30),
|
||||||
|
) == datetime.datetime(2021, 4, 6, 10, 30)
|
||||||
|
|
||||||
|
|
||||||
|
def test_next_opening_hour_on_same_day():
|
||||||
|
assert opening_hours.next_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=[],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2021, 4, 6, 13, 0),
|
||||||
|
) == datetime.datetime(2021, 4, 6, 14, 0)
|
||||||
|
assert opening_hours.next_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=[],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2021, 4, 6, 16, 0),
|
||||||
|
) == datetime.datetime(2021, 4, 6, 16, 0)
|
||||||
|
assert opening_hours.next_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi"],
|
||||||
|
exceptional_closures_values=[],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2021, 4, 6, 16, 0),
|
||||||
|
) == datetime.datetime(2021, 4, 6, 16, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_next_opening_hour_on_opened_day_but_too_late():
|
||||||
|
assert opening_hours.next_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=[],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2021, 4, 6, 23, 0),
|
||||||
|
) == datetime.datetime(2021, 4, 7, 9, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_previous_opening_date():
|
||||||
|
assert opening_hours.previous_opening_date(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-18h"],
|
||||||
|
exceptional_closures_values=[],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
date=datetime.date(2024, 4, 1),
|
||||||
|
) == datetime.date(2024, 3, 29)
|
||||||
|
|
||||||
|
|
||||||
|
def test_previous_opening_hour():
|
||||||
|
assert opening_hours.previous_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-18h"],
|
||||||
|
exceptional_closures_values=[],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2024, 4, 1, 10, 30),
|
||||||
|
) == datetime.datetime(2024, 3, 29, 18, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_previous_opening_hour_with_exceptionnal_closure_hours():
|
||||||
|
assert opening_hours.previous_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=["29/03/2024 14h-18h"],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2024, 4, 1, 10, 30),
|
||||||
|
) == datetime.datetime(2024, 3, 29, 12, 0)
|
||||||
|
assert opening_hours.previous_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=["29/03/2024 16h-18h"],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2024, 4, 1, 10, 30),
|
||||||
|
) == datetime.datetime(2024, 3, 29, 16, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_previous_opening_hour_with_exceptionnal_closure_day():
|
||||||
|
assert opening_hours.previous_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=["29/03/2024"],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2024, 4, 1, 10, 30),
|
||||||
|
) == datetime.datetime(2024, 3, 28, 18, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_previous_opening_hour_with_overlapsed_opening_hours():
|
||||||
|
assert opening_hours.previous_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h", "mardi 8h-19h"],
|
||||||
|
exceptional_closures_values=[],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2024, 4, 3, 8, 30),
|
||||||
|
) == datetime.datetime(2024, 4, 2, 19, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_previous_opening_hour_with_too_large_exceptionnal_closure_days():
|
||||||
|
assert (
|
||||||
|
opening_hours.previous_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=["06/03/2024-16-04/2024"],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2024, 4, 17, 8, 30),
|
||||||
|
max_anaylse_days=10,
|
||||||
|
)
|
||||||
|
is False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_previous_opening_hour_on_opened_moment():
|
||||||
|
assert opening_hours.previous_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=[],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2024, 4, 5, 10, 30),
|
||||||
|
) == datetime.datetime(2024, 4, 5, 10, 30)
|
||||||
|
|
||||||
|
|
||||||
|
def test_previous_opening_hour_on_same_day():
|
||||||
|
assert opening_hours.previous_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=[],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2024, 4, 5, 13, 0),
|
||||||
|
) == datetime.datetime(2024, 4, 5, 12, 0)
|
||||||
|
assert opening_hours.previous_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=[],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2024, 4, 5, 16, 0),
|
||||||
|
) == datetime.datetime(2024, 4, 5, 16, 0)
|
||||||
|
assert opening_hours.previous_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi"],
|
||||||
|
exceptional_closures_values=[],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2024, 4, 5, 16, 0),
|
||||||
|
) == datetime.datetime(2024, 4, 5, 16, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_previous_opening_hour_on_opened_day_but_too_early():
|
||||||
|
assert opening_hours.previous_opening_hour(
|
||||||
|
normal_opening_hours_values=["lundi-vendredi 9h-12h 14h-18h"],
|
||||||
|
exceptional_closures_values=[],
|
||||||
|
nonworking_public_holidays_values=nonworking_public_holidays,
|
||||||
|
when=datetime.datetime(2024, 4, 5, 8, 0),
|
||||||
|
) == datetime.datetime(2024, 4, 4, 18, 0)
|
||||||
|
|
Loading…
Reference in a new issue