add opening hours tile for mairies (#22469)

This commit is contained in:
Elias Showk 2018-05-11 13:00:32 +02:00
parent dac00c54ed
commit fb6e29d9cd
2 changed files with 174 additions and 11 deletions

View File

@ -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:

View File

@ -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'})]