Meridian handbook
Content modellingFields

Phone and opening hours

The phone field (an E.164 number parsed in a region, with accepted number types) and the opening_hours field (a weekly pattern, seasons, exceptions and public holidays), with parameters, validation, stored shapes and template values.

phone

A telephone number, parsed and checked for its region and stored in international E.164 form beside what the editor typed. Held once for every locale.

fields:
  - { name: phone, type: phone, index: true }
  - { name: mobile, type: phone, region: GB, types: [mobile] }
ParameterTypeDefaultWhat it does
regionISO 3166 codethe site’s country, in the admin’s editorThe region a number typed without its country code is read in. A number typed with + needs none. A seed file and content create do not read the site’s country, so there a bare national number needs this parameter, or { raw, region } in the value.
typeslist of fixed_line, mobile, toll_free, premium_rate, voipanyThe number types accepted. A number the dataset cannot tell between fixed line and mobile is accepted by either.
CodeMessage
regionEnter the number with its country code, starting with + (no region to read it in)
phone_emptyEnter a telephone number
phone_invalidThis is not a valid telephone number
phone_regionThis is not a valid telephone number for GB
phone_typeMust be a mobile number

Stored value:

{ "raw": "01295 123456", "region": "GB", "e164": "+441295123456", "national": "01295 123456", "type": "fixed_line" }

raw and region are what the editor typed and where; the rest is derived on save. In a seed file or import, { raw, region } is enough, and a bare string is read in the field’s region parameter (without one it must start with +). With index: true the E.164 form is projected.

Template value:

PropertyWhat it is
displayThe number as a reader in this locale expects it: national inside the number’s own region, international outside it. The locale’s region decides, so a UK number shows as 01600 712345 under en-GB and +44 1600 712345 under plain en.
telHreftel: and the E.164 number, for a link. A variable in href must go through the url filter, which keeps tel:.
e164+441295123456.
national, internationalThe two written forms.
regionThe number’s region.
typefixed_line, mobile, toll_free and so on.
validWhether the number is valid. A number that cannot be parsed shows what was typed in display and has an empty telHref.
{% if entry.phone %}<a href="{{ entry.phone.telHref|url }}">{{ entry.phone.display }}</a>{% endif %}

opening_hours

When a place is open: a regular week, seasons that replace it between two dates each year, exceptions for single days, behaviour on public holidays, a note per locale and a by-appointment flag. The structure is held once for every locale; only the notes are per locale, inside the value.

fields:
  - name: openingHours
    type: opening_hours
    holiday_calendar: uk-england-wales
    timezone: Europe/London
    max_periods: 2
ParameterTypeDefaultWhat it does
holiday_calendarcalendar keynoneThe public holiday calendar offered by default; a value may choose another. Built in: uk-england-wales, uk-scotland, uk-northern-ireland.
timezoneIANA zonethe site’s, then the locale’sThe zone offered by default.
max_periodswhole number3How many periods per day the editor offers.

The editor takes one row per weekday with a closed switch and the periods as shorthand (12:00-15:00, 18:00-23:00; a close at or before the open runs into the next day), seasons and exceptions as rows, the holiday calendar and behaviour, and this locale’s note.

Stored value:

{
  "timezone": "Europe/London",
  "regular": [
    { "day": "mon", "periods": [{ "open": "09:30", "close": "16:30" }] },
    { "day": "sun", "closed": true }
  ],
  "seasons": [
    { "from": "11-01", "to": "02-28", "regular": [{ "day": "sat", "periods": [{ "open": "10:00", "close": "14:00" }] }] }
  ],
  "exceptions": [
    { "date": "2026-12-24", "periods": [{ "open": "09:30", "close": "12:00" }], "note": "Christmas Eve" }
  ],
  "holiday_calendar": "uk-england-wales",
  "holiday_behaviour": "closed",
  "notes": { "en-GB": "Tours at 11:00 and 14:00; booking essential." },
  "by_appointment": false
}
KeyWhat it is
timezoneRequired. The zone the times are in.
regularThe week: each day mon to sun at most once, closed or periods of { open, close } in HH:MM. A period whose close is at or before its open runs past midnight.
seasonsWeeks that replace regular between from and to, each MM-DD and inclusive; a season may wrap the year end.
exceptionsSingle dates, YYYY-MM-DD, either closed or with periods (not both), with an optional note.
holiday_calendarA calendar key, as above.
holiday_behaviourclosed, normal, or hours with holiday_periods.
notesA note per locale code.
by_appointmentOpen by appointment.

On save the value is normalised (Monday first, empty days dropped, exceptions sorted by date), so two edits that mean the same store the same bytes. Each problem is reported at its path with code opening_hours: timezone: a time zone is required, seasons[0].from: must be MM-DD, exceptions[1]: closed and periods cannot both be set, regular[2].day: mon appears twice, holiday_behaviour: must be closed, normal or hours, holiday_calendar: unknown holiday calendar.

With index: true the field projects one boolean, whether any hours are set, which is what a “places with no opening hours” list reads.

Template value. Methods take a time; pass the now global for the current moment.

Property or methodWhat it is
isOpenAt(t)Whether the place is open at t.
closesAt(t)When the current period ends, or nothing.
nextOpenAt(t)When it next opens, or nothing.
nextChange(t)When it next opens or closes, or nothing.
todayLabel(t)The day’s hours in words for the request locale.
week(t)The seven days from the locale’s first day of the week (Monday under en-GB, Sunday under plain en), each with date, weekday, label, isToday, closed, periods (each open, close, spansMidnight), periodsLabel, note and source (regular, season, exception or holiday).
jsonld(t)The schema.org openingHoursSpecification entries.
noteThis locale’s note, else one in the same language.
notesEvery note by locale.
timezone, calendar, byAppointment, isEmptyAs stored.
{% set hours = entry.openingHours %}
{% if hours and not hours.isEmpty %}
<p>{% if hours.isOpenAt(now) %}{{ t('hours.open_now') }}{% else %}{{ t('hours.closed_now') }}{% endif %}</p>
<table>
  <caption>{{ t('hours.caption') }}</caption>
  <thead><tr><th scope="col">{{ t('hours.day') }}</th><th scope="col">{{ t('hours.times') }}</th></tr></thead>
  <tbody>
    {% for day in hours.week(now) %}
    <tr{% if day.isToday %} aria-current="date"{% endif %}><th scope="row">{{ day.label }}</th><td>{{ day.periodsLabel }}</td></tr>
    {% endfor %}
  </tbody>
</table>
{% if hours.note %}<p>{{ hours.note }}</p>{% endif %}
{% endif %}

On this page