Common Types

Opening Hours

The Schema.org-style record for a weekly opening schedule with an IANA timezone and one-off date exceptions. Consumed by the ui-kit opening-hours primitives and the location-card components.

A store opens 09 00-18 00 on weekdays and 10 00-14 00 on Saturday. A doctor's office closes for two weeks in August. A restaurant takes a lunch break from 14:00 to 18:00. The shape connectors and editors return for all of these is the same OpeningHours record: a weekly schedule keyed by ISO weekday, an optional IANA timezone the wall-clock times are interpreted in, and an optional map of date-specific exceptions for closures and one-off openings.

import type { OpeningHours } from '@laioutr-core/core-types/common';

interface OpeningHours {
  timeZone?: string;
  schedule: Partial<Record<Weekday, OpeningWindow[]>>;
  exceptions?: Record<string, OpeningWindow[]>;
}

The type follows the shape of schema.org/OpeningHoursSpecification without forcing the verbose Schema.org structure on connectors. It is consumed by the ui-kit OpeningStatus, OpeningStatusIndicator, OpeningStatusDetail, and OpeningHoursWeeklyTable components, and by the LocationCard block.

Fields

timeZone

An optional IANA timezone identifier (e.g. 'Europe/Berlin', 'America/New_York'). The wall-clock HH:mm times inside schedule and exceptions are interpreted in this zone. When omitted, the browser's local zone is used, which is rarely what you want for a multi-location store.

Always return the location's local timezone from connectors. A London shopper looking at a Paris store should still see Paris's opening times in Paris's wall-clock hours, not their own offset.

schedule

The weekly schedule, keyed by ISO weekday (1 = Monday, 7 = Sunday). Each value is an array of OpeningWindow records that describe when the location is open that day. A missing key (or an empty array) means closed that day.

A typical "Monday to Friday, plus Saturday morning" schedule:

const schedule: OpeningHours['schedule'] = {
  1: [{ start: '09:00', end: '18:00' }],
  2: [{ start: '09:00', end: '18:00' }],
  3: [{ start: '09:00', end: '18:00' }],
  4: [{ start: '09:00', end: '18:00' }],
  5: [{ start: '09:00', end: '20:00' }],
  6: [{ start: '10:00', end: '14:00' }],
  // 7 omitted: closed Sundays
};

A day with multiple windows (e.g. a lunch break) carries two entries:

const tuesday: OpeningWindow[] = [
  { start: '09:00', end: '13:00' },
  { start: '14:30', end: '19:00' },
];

exceptions

An optional map of date-specific overrides, keyed by 'YYYY-MM-DD'. The date is interpreted in the schedule's timeZone. Use this for holidays, special hours, and one-off closures. An empty array means closed that day.

const exceptions: OpeningHours['exceptions'] = {
  '2026-12-24': [{ start: '09:00', end: '14:00' }], // Christmas Eve, short day
  '2026-12-25': [],                                 // Christmas Day, closed
  '2026-12-26': [],                                 // Boxing Day, closed
};

When a date appears in exceptions, it overrides the weekly schedule for that day completely. The components do not merge weekly windows with exception windows.

Weekday

type Weekday = 1 | 2 | 3 | 4 | 5 | 6 | 7;

ISO 8601 weekday, with 1 for Monday and 7 for Sunday. Storing days as numeric ISO weekdays keeps the type stable across locales and avoids the Sunday-vs-Monday "is day 0 the first day of the week?" trap.

OpeningWindow

interface OpeningWindow {
  start: string; // 'HH:mm'
  end: string;   // 'HH:mm'
}

A single open window expressed as wall-clock times in the parent schedule's timezone. Both start and end are 24-hour 'HH:mm' strings.

Overnight windows (where end is earlier than start, e.g. a bar that closes at 02:00) are not supported and are treated as same-day by the useOpeningStatus composable.

Consuming the data

The ui-kit ships a useOpeningStatus composable that turns an OpeningHours value into a reactive isOpen flag and the next state-change event:

import { useOpeningStatus } from '#imports';
import type { OpeningHours } from '@laioutr-core/core-types/common';

const openingHours: OpeningHours = {
  timeZone: 'Europe/Berlin',
  schedule: { 1: [{ start: '09:00', end: '18:00' }] },
};

const { isOpen, nextEvent } = useOpeningStatus(() => openingHours);

nextEvent is one of:

  • closes-at when currently open. Carries the closing time and a sortable instant.
  • opens-today when closed and the next opening is later the same calendar day.
  • opens-tomorrow when closed and the next opening is the next calendar day.
  • opens-on-weekday when closed and the next opening is within the next 7 days.
  • opens-on-date when closed and the next opening is more than 7 days out (within a 30-day horizon).

For an empty schedule or a 24/7 schedule with no upcoming state change, nextEvent is undefined.

The composable looks ahead a maximum of 30 days. If a closure runs longer than that (e.g. a seasonal location), nextEvent returns undefined and consumers should fall back to a "Closed for the season" message rather than a relative date.

For connector authors

Return the location's local timezone, the weekly schedule, and any one-off exceptions:

import type { OpeningHours } from '@laioutr-core/core-types/common';

const munichStore: OpeningHours = {
  timeZone: 'Europe/Berlin',
  schedule: {
    1: [{ start: '09:00', end: '20:00' }],
    2: [{ start: '09:00', end: '20:00' }],
    3: [{ start: '09:00', end: '20:00' }],
    4: [{ start: '09:00', end: '20:00' }],
    5: [{ start: '09:00', end: '20:00' }],
    6: [{ start: '09:00', end: '18:00' }],
  },
  exceptions: {
    '2026-12-24': [{ start: '09:00', end: '14:00' }],
    '2026-12-25': [],
    '2026-12-26': [],
    '2026-01-01': [],
  },
};

Three common pitfalls to avoid:

  1. Returning times in UTC. The schedule's HH:mm values are wall-clock times in the named timeZone, not UTC offsets. A Berlin store opens at 09:00 in 'Europe/Berlin', regardless of summer time. The composable resolves the right UTC instant per calendar day.
  2. Omitting timeZone. When timeZone is missing, the components fall back to the browser's local zone. This is correct only if your storefront serves a single timezone; for multi-location stores it shows wrong times to shoppers in other regions.
  3. Encoding closures as empty schedule keys. Use schedule for the repeating pattern and exceptions for one-offs.
Copyright © 2026 Laioutr GmbH