# Opening Hours The opening hours feature lets editors describe when a place is open on a `Contact`. It supports general weekly opening hours, special opening hours that override the general ones for a date range, and a reference to a separate contact that supplies holidays. The serialized output is computed at request time and reports whether the place is currently open or closed, plus a human-readable state. ## Overview The opening hours behavior adds these fields to a `Contact`: - **show_opening_hours** -- a flag indicating whether the frontend should render opening hours at all. - **opening_hours_text** -- free rich-text shown alongside the structured hours. - **structured_opening_hours** -- the general weekly opening hours, stored following the schema.org `OpeningHoursSpecification` structure. - **special_opening_hours** -- entries that override the general hours for a validity date range (for example public holidays or seasonal hours). - **holidays** -- a reference to another `Contact` whose opening/special hours are merged in (lets you maintain a shared holiday calendar once and reuse it). The structured fields are edited with a dedicated widget but always serialize as plain JSON for consumers. ### Specification format Each entry in `structured_opening_hours.openingHoursSpecification` carries: - `dayOfWeek` -- a list of weekday names (`Monday` ... `Sunday`). - `opens` / `closes` -- times in `HH:MM` (or `HH:MM:SS`). An entry with `opens` and `closes` both `00:00` means closed. - `validFrom` / `validThrough` -- optional ISO date-times bounding the validity of the entry. If one is set, both must be set. Special opening hours use the same per-entry shape under `specialOpeningHoursSpecification`. ## REST API ### Computed opening hours in content The `structured_opening_hours` field is computed on the fly when a `Contact` is serialized. The computed value merges the general hours, special hours and referenced holidays, evaluates the current open/closed state, and adds the following keys: - `@type` -- always `Place`. - `name` -- the contact title. - `closed` -- boolean, whether the place is currently closed. - `current_isodate` -- the date-time the state was evaluated for. - `opens_at` / `closes_at` -- the next opening / current closing time, or `null`. - `human_readable_state` -- a translated message such as "Opens at 08:00" or "Closed". - `html` -- a pre-rendered HTML representation of the opening hours table. The raw `special_opening_hours` field is dropped from the `Contact` serialization; its entries are already merged into the computed `structured_opening_hours`. ```javascript const response = await fetch('/Plone/contacts/town-hall', { headers: { 'Accept': 'application/json' } }); const contact = await response.json(); const hours = contact.structured_opening_hours; console.log(hours.closed); // false console.log(hours.human_readable_state); // "Closes at 17:00" ``` ### GET @opening-hours The `@opening-hours` endpoint returns just the computed opening hours for a contact, without serializing the whole object. This is the lightweight endpoint to poll for the current open/closed state. ```http GET /Plone/contacts/town-hall/@opening-hours HTTP/1.1 Host: localhost:8080 Accept: application/json ``` ```json { "@id": "http://localhost:8080/Plone/contacts/town-hall/@opening-hours", "structured_opening_hours": { "@type": "Place", "name": "Town Hall", "openingHoursSpecification": [ { "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"], "opens": "08:00", "closes": "12:00" } ], "closed": false, "current_isodate": "2026-06-03T10:00:00", "opens_at": null, "closes_at": "2026-06-03T12:00:00", "human_readable_state": "Closes at 12:00", "html": "