Skip to content

Store Hours – Multi-Store Implementation

This guide documents the multi-store store-hours implementation: how data is shaped, how the API works, and how callers use it.

Legacy (pre–multi-store)

Data flow (legacy)

  • Source: storeHours was built in scripts.template.html from:
    • [@CONFIG: Operating_Hours@] (injected as object body)
    • [@CONFIG: Operating_Exceptions@] (injected as array body)
  • Shape: single store with storeHours.openingHours and storeHours.closing_exceptions (array).
  • File: src/js/storeHours.js – all logic used that single-store shape.

Legacy API (storeHours.js)

FunctionPurposeRelies on
timeStringToMinutes(timeStr)”HH:MM ” → minutes since midnight
getCurrentAEST()Current time in Australia/Sydney
formatDateDDMMYYYY(date)Date → “dd/mm/yyyy”
isClosingException(currentDateStr)Is date in closing_exceptions?storeHours.closing_exceptions (array), currentDateStr in dd/mm/yyyy
isStoreOpen()Is “the” store open now?storeHours.openingHours (weekdays/saturday/sunday)

Legacy callers

LocationUsageStore context
contactModal.template.htmlisStoreOpen() === true → enable call/chat, set “open”/“Live & Ready”None – single global “open” state
storestock.template.htmlLists Kotara, Maitland, Artarmon, BurwoodPer-store rows, but no store-hours calls
structured_data.template.htmlPer-location openingHoursSpecificationHardcoded per store, not from storeHours

Data shape (implemented)

The runtime object uses store ids as keys and a shared closing_exceptions object. Store ids match stable ids used in templates (e.g. "kotara", "maitland", "artarmon", "burwood").

Config: Operating Hours

[@CONFIG: Operating_Hours@] provides per-store entries, for example:

"kotara": {
"store_name": "Altapac Kotara",
"store_address": "94 Park Ave, Kotara NSW 2289",
"store_phone": "+61 2 4941 4444",
"store_email": "hello@altapac.com",
"openingHours": {
"weekdays": { "open": "08:00", "close": "17:00" },
"saturday": { "open": "08:00", "close": "14:00" },
"sunday": { "closed": true }
}
},
"maitland": { /* ... */ },
"artarmon": { /* ... */ },
"burwood": { /* ... */ }

Config: Closing Exceptions

[@CONFIG: Operating_Exceptions@] provides date → reason entries (YYYY-MM-DD keys):

"2026-04-03": "Good Friday",
"2026-04-04": "Easter Saturday",
"2026-04-05": "Easter Sunday",
"2026-04-06": "Easter Monday",
"2026-04-25": "Anzac Day",
"2026-06-08": "King's Birthday",
"2026-08-03": "Bank Holiday",
"2026-10-05": "Labour Day",
"2026-12-25": "Christmas Day",
"2026-12-26": "Boxing Day",
"2026-12-28": "Additional Day",
"2027-01-01": "New Year's Day",
"2027-01-26": "Australia Day",
"2027-03-26": "Good Friday",
"2027-03-27": "Easter Saturday",
"2027-03-28": "Easter Sunday",
"2027-03-29": "Easter Monday",
"2027-04-25": "Anzac Day",
"2027-06-14": "King's Birthday",
"2027-08-02": "Bank Holiday",
"2027-10-04": "Labour Day",
"2027-12-25": "Christmas Day",
"2027-12-26": "Boxing Day",
"2027-12-27": "Additional Day",
"2027-12-28": "Additional Day"

Resolves to

const storeHours = {
"kotara": {
"store_name": "Altapac Kotara",
"store_address": "94 Park Ave, Kotara NSW 2289",
"store_phone": "+61 2 4941 4444",
"store_email": "hello@altapac.com",
"openingHours": {
"weekdays": { "open": "08:00", "close": "17:00" },
"saturday": { "open": "08:00", "close": "14:00" },
"sunday": { "closed": true }
}
},
"maitland": { /* ... */ },
"artarmon": { /* ... */ },
"burwood": { /* ... */ },
"closing_exceptions": {"2026-04-03": "Good Friday", "2026-12-25": "Christmas Day", ""}
};

Conventions

  • Store ids: lowercase, no spaces (e.g. kotara, maitland). These are the values you pass as storeId into the API.
  • closing_exceptions: object with date strings in YYYY-MM-DD as keys and the reason for the exception as the value. All stores share this list.

API (storeHours.js)

Core functions

FunctionSignaturePurpose
timeStringToMinutes(timeStr)(timeStr: string) => number | null”HH:MM ” → minutes since midnight.
getCurrentAEST()() => DateCurrent time in Australia/Sydney.
formatDateDDMMYYYY(date)(date: Date) => stringDate → “dd/mm/yyyy” (for UI).
formatDateYYYYMMDD(date)(date: Date) => stringDate → “YYYY-MM-DD”; used for exception matching.
isClosingException(dateStr, exceptions?)(dateStr: string, exceptions?) => booleanUses root closing_exceptions (object keys) by default; optional override for tests/reuse.
isStoreOpen(storeId?)(storeId?: string) => booleanSee below.
getStoreConfig(storeId)(storeId: string) => object | nullReturns full store config (name, openingHours, etc.) or null.
getTodayHours(storeId)(storeId: string) => object | nullReturns { open, close } or { closed: true } for today for that store.
getStoreIds()() => string[]Returns store ids (excludes closing_exceptions and reserved keys).

isStoreOpen(storeId?) behaviour

  • isStoreOpen() (no args)
    Uses the default store Kotara (store id "kotara"). The script defines DEFAULT_STORE_ID = "kotara" and uses it when no argument is passed.

  • isStoreOpen(storeId)
    Uses storeHours[storeId].openingHours and the shared closing_exceptions. If storeId is invalid, returns false.


Implementation details

Exception date format

  • closing_exceptions uses YYYY-MM-DD as object keys.
  • isClosingException compares “today” in YYYY-MM-DD (e.g. formatDateYYYYMMDD(getCurrentAEST())) against those keys.
  • formatDateDDMMYYYY is used only where display or legacy config needs dd/mm/yyyy.

Identifying store keys

All keys that are not closing_exceptions and not reserved (e.g. keys starting with _) are store ids. getStoreIds() filters them as:

Object.keys(storeHours).filter(
(k) => k !== "closing_exceptions" && !String(k).startsWith("_")
);

Default store

The default store for isStoreOpen() is Kotara, set via var DEFAULT_STORE_ID = "kotara" and documented in the JSDoc in storeHours.js.


Call sites

CallerUsage
contactModal.template.htmlUses isStoreOpen() (default store Kotara) to enable call/chat and set “open”/“Live & Ready”. Can call isStoreOpen(storeId) with a store id (e.g. from URL hash or a data attribute) for location-aware behaviour.
storestock.template.htmlCan show “Open / Closed” per row via isStoreOpen('kotara'), isStoreOpen('maitland'), etc., and/or getTodayHours(storeId) for “Opens 08:00” / “Closed today”.
structured_data.template.htmlPer-location openingHoursSpecification remains hardcoded per store; a future refactor could use getStoreConfig(storeId).openingHours as the source of truth.

Changes from legacy

ChangeImplemented behaviour
Data shapeConfig exposes one object per store and a root closing_exceptions object (YYYY-MM-DD keys, reason values). Legacy single-store shape (storeHours.openingHours at top level) is supported when present for backward compatibility.
isStoreOpen()No-arg form uses the default store (Kotara). Passing a store id uses that store’s openingHours and the shared closing_exceptions.
isClosingExceptionUses YYYY-MM-DD; “today” is compared against closing_exceptions object keys via formatDateYYYYMMDD(getCurrentAEST()).
storeHours globalSame name; structure is nested stores + shared closing_exceptions. New code should use getStoreConfig, getStoreIds, and isStoreOpen(storeId) rather than reading storeHours.openingHours directly.

Summary

  • Implemented: Multiple stores with per-store opening hours, one shared closing_exceptions object (YYYY-MM-DD keys), and a small, reusable API in storeHours.js.
  • Data: One storeHours object with store ids as keys and a root closing_exceptions object; exception dates use YYYY-MM-DD.
  • API: getStoreIds, getStoreConfig, getTodayHours, formatDateYYYYMMDD; isStoreOpen(storeId?) with default store Kotara; exception checking uses YYYY-MM-DD. Legacy single-store config is supported when present.