add opening hours tile for mairies (#22469)
This commit is contained in:
parent
dac00c54ed
commit
fb6e29d9cd
|
@ -53,6 +53,7 @@ EN_ABBREV_WEEKDAYS = OrderedDict([
|
|||
])
|
||||
EN_ABBREV_WEEKDAYS_LIST = list(EN_ABBREV_WEEKDAYS.keys())
|
||||
WEEKDAYS = list(EN_ABBREV_WEEKDAYS.values())
|
||||
FR_ABBREV_WEEKDAYS_LIST = OrderedDict(zip(EN_ABBREV_WEEKDAYS_LIST, FR_WEEKDAYS))
|
||||
|
||||
|
||||
class TimeSlot(object):
|
||||
|
@ -68,18 +69,59 @@ class TimeSlot(object):
|
|||
return '<TimeSlot start=%s - end=%s>' % (self.start.strftime('%c'), self.end.strftime('%c'))
|
||||
|
||||
|
||||
def get_open_close_from_specification(specification, valid_from, base_datetime):
|
||||
'''Parse geojson 'openinghoursspecification' fields data
|
||||
'''
|
||||
opening_time = datetime.datetime.combine(base_datetime, dateutil_parse(specification['opens']).time())
|
||||
closing_time = datetime.datetime.combine(base_datetime, dateutil_parse(specification['closes']).time())
|
||||
opening_time = opening_time.replace(tzinfo=valid_from.tzinfo)
|
||||
closing_time = closing_time.replace(tzinfo=valid_from.tzinfo)
|
||||
day_of_week = WEEKDAYS.index(specification['dayOfWeek'].split('/')[-1])
|
||||
opening_time = opening_time + datetime.timedelta(
|
||||
days=(7 + (day_of_week - opening_time.weekday())) % 7)
|
||||
closing_time = closing_time + datetime.timedelta(
|
||||
days=(7 + (day_of_week - closing_time.weekday())) % 7)
|
||||
return (opening_time, closing_time, day_of_week)
|
||||
|
||||
|
||||
def openinghours_to_datetime(codename, hour, minute, default=None):
|
||||
try:
|
||||
weekday = EN_ABBREV_WEEKDAYS.get(codename, None)
|
||||
# if default is None, return the next date and time after now()
|
||||
# a default datetime instance is used to replace absent parameters for datetime
|
||||
return dateutil_parse('%s %d:%d:00' % (weekday, hour, minute), default=default)
|
||||
datetime_obj = dateutil_parse('%s %d:%d:00' % (weekday, hour, minute), default=default)
|
||||
if is_naive(datetime_obj):
|
||||
datetime_obj = make_aware(datetime_obj)
|
||||
return datetime_obj
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def get_period_from_data(weekday, open_close_time_string=None, opening_time=None, closing_time=None):
|
||||
"""Return am or pm and all_day_hours from opening_time and closing_time
|
||||
"""
|
||||
if open_close_time_string is not None:
|
||||
(start_hour, start_minute, end_hour, end_minute) = open_close_time_string
|
||||
if closing_time is None:
|
||||
closing_time = openinghours_to_datetime(weekday, int(end_hour), int(end_minute))
|
||||
if opening_time is None:
|
||||
opening_time = openinghours_to_datetime(weekday, int(start_hour), int(start_minute))
|
||||
|
||||
all_day_hours = False
|
||||
if closing_time.hour <= 12:
|
||||
period = 'am'
|
||||
elif opening_time.hour <= 12:
|
||||
period = 'am'
|
||||
all_day_hours = True
|
||||
else:
|
||||
period = 'pm'
|
||||
|
||||
return (period, all_day_hours)
|
||||
|
||||
|
||||
def get_slots_from_mdr_format(data, today):
|
||||
"""Process data from /ws/grandlyon/ter_territoire.maison_du_rhone/all.json
|
||||
"""Process data from Maison du rhone geojson data from data.grandlyon.fr
|
||||
(/ws/grandlyon/ter_territoire.maison_du_rhone/all.json)
|
||||
add to slots all the next opening hours in chronological order & beginning from today()
|
||||
"""
|
||||
if 'properties' in data:
|
||||
|
@ -131,15 +173,8 @@ def get_slots_from_mairie_format(data, base_datetime):
|
|||
if 'opens' in specification and 'closes' in specification:
|
||||
# case when opening periods are defined
|
||||
if base_datetime >= valid_from and base_datetime < valid_through:
|
||||
opening_time = datetime.datetime.combine(base_datetime, dateutil_parse(specification['opens']).time())
|
||||
closing_time = datetime.datetime.combine(base_datetime, dateutil_parse(specification['closes']).time())
|
||||
opening_time = opening_time.replace(tzinfo=valid_from.tzinfo)
|
||||
closing_time = closing_time.replace(tzinfo=valid_from.tzinfo)
|
||||
day_of_week = WEEKDAYS.index(specification['dayOfWeek'].split('/')[-1])
|
||||
opening_time = opening_time + datetime.timedelta(
|
||||
days=(7 + (day_of_week - opening_time.weekday())) % 7)
|
||||
closing_time = closing_time + datetime.timedelta(
|
||||
days=(7 + (day_of_week - closing_time.weekday())) % 7)
|
||||
(opening_time, closing_time, day_of_week) = get_open_close_from_specification(
|
||||
specification, valid_from, base_datetime)
|
||||
slots.append(TimeSlot(opening_time, closing_time))
|
||||
else:
|
||||
# case when exclusions are defined
|
||||
|
@ -170,6 +205,87 @@ def get_slots_from_mairie_format(data, base_datetime):
|
|||
return (slots, exclusion_slots, known_format)
|
||||
|
||||
|
||||
@register.assignment_tag
|
||||
def get_mairie_opening_hours(mairie_data):
|
||||
"""Process Mairie Geojson to extract data of each day's opening hours
|
||||
"""
|
||||
if not mairie_data:
|
||||
return ''
|
||||
|
||||
if 'properties' in mairie_data:
|
||||
mairie_data = mairie_data['properties']
|
||||
|
||||
end_day = None
|
||||
base_datetime = make_aware(datetime.datetime.now())
|
||||
opening_hours_dict = OrderedDict(zip(EN_ABBREV_WEEKDAYS_LIST, [{
|
||||
'am': None, 'pm': None
|
||||
} for i in range(7)]))
|
||||
known_format = False
|
||||
for openinghours in mairie_data.get('openinghours', []):
|
||||
known_format = True
|
||||
try:
|
||||
# days interval
|
||||
parts = re.match('(\w\w)-(\w\w) (\d\d):(\d\d)-(\d\d):(\d\d)', openinghours).groups()
|
||||
(start_day, end_day, start_hour, start_minute, end_hour, end_minute) = parts
|
||||
days_list = EN_ABBREV_WEEKDAYS_LIST[
|
||||
EN_ABBREV_WEEKDAYS_LIST.index(start_day.lower()):
|
||||
EN_ABBREV_WEEKDAYS_LIST.index(end_day.lower()) + 1]
|
||||
except AttributeError:
|
||||
try:
|
||||
# one day
|
||||
parts = re.match('(\w\w) (\d\d):(\d\d)-(\d\d):(\d\d)', openinghours).groups()
|
||||
(start_day, start_hour, start_minute, end_hour, end_minute) = parts
|
||||
days_list = [EN_ABBREV_WEEKDAYS_LIST[
|
||||
EN_ABBREV_WEEKDAYS_LIST.index(start_day.lower())]]
|
||||
except AttributeError:
|
||||
try:
|
||||
# comma-separated days list
|
||||
parts = re.match('(\w\w(?:,\w\w)+) (\d\d):(\d\d)-(\d\d):(\d\d)', openinghours).groups()
|
||||
start_day = parts[0].split(',')
|
||||
(start_hour, start_minute, end_hour, end_minute) = parts[1:]
|
||||
days_list = [EN_ABBREV_WEEKDAYS_LIST[
|
||||
EN_ABBREV_WEEKDAYS_LIST.index(day.lower())] for day in start_day]
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
for weekday in days_list:
|
||||
(period, all_day_hours) = get_period_from_data(weekday,
|
||||
open_close_time_string=(start_hour, start_minute, end_hour, end_minute))
|
||||
if all_day_hours and period == 'am':
|
||||
opening_hours_dict[weekday]['pm'] = '' # empty string to avoid displaying fermé
|
||||
opening_hours_dict[weekday][period] = "%sh%s-%sh%s" % (start_hour, start_minute, end_hour, end_minute)
|
||||
|
||||
if not known_format:
|
||||
# some mairie only have openinghoursspecification (e.g. Jonage)
|
||||
for specification in mairie_data.get('openinghoursspecification', []):
|
||||
valid_from, valid_through = (
|
||||
parse_datetime(specification.get('validFrom')),
|
||||
parse_datetime(specification.get('validThrough'))
|
||||
)
|
||||
if not valid_from or not valid_through:
|
||||
continue
|
||||
# case when opening periods are defined
|
||||
if 'opens' in specification and 'closes' in specification:
|
||||
# parse specification only for the current period relative to utcnow()
|
||||
if base_datetime >= valid_from and base_datetime < valid_through:
|
||||
(opening_time, closing_time, day_of_week) = get_open_close_from_specification(
|
||||
specification, valid_from, base_datetime)
|
||||
abbr_day_of_week = EN_ABBREV_WEEKDAYS_LIST[day_of_week]
|
||||
(period, all_day_hours) = get_period_from_data(abbr_day_of_week,
|
||||
opening_time=opening_time, closing_time=closing_time)
|
||||
if all_day_hours and period == 'am':
|
||||
opening_hours_dict[weekday]['pm'] = '' # empty string to avoid displaying fermé
|
||||
|
||||
opening_hours_dict[abbr_day_of_week][period] = "%sh%s-%sh%s" % (
|
||||
opening_time.strftime('%H'), opening_time.strftime('%M'),
|
||||
closing_time.strftime('%H'), closing_time.strftime('%M'))
|
||||
|
||||
return [
|
||||
(FR_ABBREV_WEEKDAYS_LIST[weekday], hours) for weekday, hours in opening_hours_dict.items()
|
||||
if not (weekday in ['sa', 'su'] and not hours['am'] and not hours['pm'])
|
||||
]
|
||||
|
||||
|
||||
@register.filter
|
||||
def as_opening_hours_badge(data, base_datetime=None):
|
||||
if not data:
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import os
|
||||
|
||||
from combo_plugin_gnm.templatetags.gnm import get_mairie_opening_hours
|
||||
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
GEOJSON = json.load(open(os.path.join(
|
||||
BASE_DIR, 'tests/data/mairie-geojson.json')))['features']
|
||||
|
||||
TZOFFSETS = {"Europe/Paris": 3600}
|
||||
|
||||
|
||||
def test_mairie_hours_parsing():
|
||||
"""everything got parsed correctly"""
|
||||
opening_hours = [get_mairie_opening_hours(x) for x in GEOJSON]
|
||||
assert len(opening_hours) == len(GEOJSON)
|
||||
|
||||
|
||||
def test_mairie_hours_nodata():
|
||||
""""no data return the right empty list of tuples"""
|
||||
test_result = [get_mairie_opening_hours(GEOJSON[0]['properties'])]
|
||||
assert len(test_result) == 1
|
||||
for (day, hours) in test_result[0]:
|
||||
assert hours['am'] is None
|
||||
assert hours['pm'] is None
|
||||
|
||||
|
||||
def test_mairie_hours_special_data():
|
||||
"""test results for various data examples"""
|
||||
for x in GEOJSON:
|
||||
if x['properties']['nom'] == 'Mairie de Jonage':
|
||||
# openinghoursspecification data format (only, no openinghours data)
|
||||
assert get_mairie_opening_hours(x) == [('lundi', {'am': '08h30-12h30', 'pm': '14h00-17h00'}), ('mardi', {'am': '08h30-12h30', 'pm': '14h00-17h00'}), ('mercredi', {'am': '08h30-12h30', 'pm': '14h00-17h00'}), ('jeudi', {'am': '08h30-12h30', 'pm': '14h00-17h00'}), ('vendredi', {'am': '08h30-12h30', 'pm': '14h00-17h00'}), ('samedi', {'am': '09h00-12h00', 'pm': '14h00-17h00'})]
|
||||
|
||||
if x['properties']['identifiant'] == 'S1376':
|
||||
# La Mulatière
|
||||
assert get_mairie_opening_hours(x) == [('lundi', {'am': u'08h30-12h00', 'pm': None}), ('mardi', {'am': u'08h30-12h30', 'pm': u'13h30-17h00'}), ('mercredi', {'am': u'08h30-12h30', 'pm': u'13h30-17h00'}), ('jeudi', {'am': u'08h30-12h30', 'pm': u'13h30-17h00'}), ('vendredi', {'am': u'08h30-12h30', 'pm': u'13h30-17h00'}), ('samedi', {'am': u'09h00-11h45', 'pm': None})]
|
||||
|
||||
if x['properties']['identifiant'] == 'S1437':
|
||||
# special openinghours format with days intervals, comma-separated list and one day definition with a saturday
|
||||
assert get_mairie_opening_hours(x) == [('lundi', {'am': u'08h45-12h30', 'pm': u'14h00-16h45'}), ('mardi', {'am': u'08h45-16h45', 'pm': ''}), ('mercredi', {'am': u'08h45-16h45', 'pm': ''}), ('jeudi', {'am': u'08h45-18h00', 'pm': ''}), ('vendredi', {'am': u'08h45-16h45', 'pm': ''}), ('samedi', {'am': u'09h00-12h00', 'pm': None})]
|
||||
|
||||
if x['properties']['identifiant'] == 'S5564':
|
||||
# classic openinghours days interval for am and pm
|
||||
assert get_mairie_opening_hours(x) == [('lundi', {'am': u'08h30-12h15', 'pm': u'13h15-17h00'}), ('mardi', {'am': u'08h30-12h15', 'pm': u'13h15-17h00'}), ('mercredi', {'am': u'08h30-12h15', 'pm': u'13h15-17h00'}), ('jeudi', {'am': u'08h30-12h15', 'pm': u'13h15-17h00'}), ('vendredi', {'am': u'08h30-12h15', 'pm': u'13h15-17h00'})]
|
Loading…
Reference in New Issue