Time Intervals
A timeInterval attribute is a recurrence rule, not a list of slots: an anchor date, daily time ranges and repeat flags. Resolve it into concrete [start, end] pairs for the window you actually render with the top-level helpers expandAttributeTimeIntervals, expandTimeIntervals and the type guard isTimeIntervalAttribute.
import { expandAttributeTimeIntervals } from 'oneentry';
const slots = expandAttributeTimeIntervals(page.attributeValues.interval, {
from: '2025-04-01',
to: '2025-04-30',
});
// [['2025-04-14T09:00:00.000Z', '2025-04-14T10:00:00.000Z'], …]
All three helpers are pure — they do not mutate their input and perform no requests.
Where schedules live
The API returns schedules in two shapes, and both are accepted by the helpers:
| Shape | Where to find it | Carries |
|---|---|---|
Entity (ITimeIntervalEntitySchedule) | attributeValues[marker].value[].values[] on pages, products, blocks and attribute sets | a dates range with times pairs |
Form (ITimeIntervalSchedule) | attributes[].localizeInfos.intervals[] on forms | a range with intervals that carry a slot period in minutes |
expandAttributeTimeIntervals(attr, window)
The one-call path for entity attributes: it walks the attribute's groups and schedules, expands each of them and merges the results. Merging matters — deduplication and ordering only hold within a single schedule, so combining groups by hand can yield duplicate or unsorted slots.
Anything that is not a timeInterval attribute yields an empty array, so it is safe to call without checking type first.
import { expandAttributeTimeIntervals } from 'oneentry';
const page = await Pages.getPageByUrl('booking');
const slots = expandAttributeTimeIntervals(page.attributeValues.interval, {
from: '2025-04-01',
to: '2025-04-30',
});
expandTimeIntervals(schedule, window)
Resolves a single schedule. Reach for it when you already hold one — most notably on forms, whose schedules are already typed at localizeInfos.intervals:
import { expandTimeIntervals } from 'oneentry';
const form = await Forms.getFormByMarker('booking_form');
const field = form.attributes.find((a) => a.marker === 'schedule');
const slots = (field?.localizeInfos.intervals ?? []).flatMap((schedule) =>
expandTimeIntervals(schedule, { from: '2025-05-01', to: '2025-05-31' }),
);
isTimeIntervalAttribute(attr)
IAttributeValue.value is typed unknown, because its shape depends on type. This type guard narrows an attribute to ITimeIntervalAttributeValue, which is what lets you reach the schedules without a cast:
import { isTimeIntervalAttribute } from 'oneentry';
const attr = page.attributeValues.interval;
if (isTimeIntervalAttribute(attr)) {
attr.value[0].values[0].dates; // fully typed
}
The window
const window = { from: '2025-04-01', to: '2025-04-30' };
fromandtoaccept aDate, an ISO string or epoch milliseconds.- Both bounds are inclusive and compared at UTC day granularity — the time-of-day part of
from/tois ignored. - The window is required: a schedule is an open-ended recurrence rule, and only you know how far it has to be resolved.
Recurrence semantics
dates[0]/range[0]is both the recurrence phase and the first valid day — nothing earlier is emitted, however wide the window.dates[1]/range[1]ends validity. When it does not extend past the start, the schedule is anchored to that single day; with a recurrence flag set, recurrence is then open-ended and the window alone bounds the result.inEveryWeekrepeats every 7 days from the anchor.inEveryMonthrepeats on the same day-of-month, skipping months that are too short.- With both flags set, the weekly rule applies — which is what it has always meant in practice.
- With neither flag the schedule is a plain date range: every day of it produces slots.
- The result is deduplicated and sorted by start, then by end.
- All arithmetic is UTC, so the result does not depend on the machine's timezone.
Migrating from the timeIntervals field
Earlier SDK versions injected a computed timeIntervals array into every timeInterval attribute value. That field no longer exists. It materialized a full year of slots regardless of what the caller needed — a single attribute with hourly slots expanded to roughly 2,000 lines of JSON, and finer slot periods reached megabytes, enough to blow past framework data-cache limits. It was never declared in any interface or schema either, so TypeScript consumers could only reach it through a cast.
// before — read the pre-computed field
const slots = page.attributeValues.interval.value[0].values[0].timeIntervals;
// now — expand the window you actually render
import { expandAttributeTimeIntervals } from 'oneentry';
const slots = expandAttributeTimeIntervals(page.attributeValues.interval, {
from: '2025-04-01',
to: '2025-04-30',
});
The source data being expanded (dates/range, times/intervals, inEveryWeek, inEveryMonth) is unchanged and still present on every schedule — nothing is lost, it is simply resolved on demand instead of eagerly, and the compact rule is what gets cached.
The _addTimeIntervalsToSchedules and _addTimeIntervalsToFormSchedules methods were removed from every module as well (despite the _ prefix they were callable, e.g. Pages._addTimeIntervalsToSchedules). Use expandTimeIntervals instead.
Types
All of these are exported from the package root and from oneentry/types (see Importing Types):
| Type | Describes |
|---|---|
ITimeIntervalAttributeValue | An IAttributeValue narrowed to type: 'timeInterval', whose value is an array of groups |
ITimeIntervalGroup | One entry of the attribute's value — schedules sharing an intervalId |
ITimeIntervalEntitySchedule | One entity schedule: dates, times, inEveryWeek, inEveryMonth |
ITimeIntervalSchedule | One form schedule: range, intervals, inEveryWeek, inEveryMonth |
ITimeIntervalRange | A daily range with start, end and a slot period in minutes (null when not sliced) |
ITimeIntervalPoint | A point in a day - { hours, minutes } |
ITimeIntervalWindow | The expansion window - { from, to } |
TimeIntervalPair | One resolved slot - [start, end], both ISO 8601 UTC strings |
Example: rendering a month of slots
import { expandAttributeTimeIntervals } from 'oneentry';
const page = await Pages.getPageByUrl('booking');
if ('statusCode' in page) {
throw new Error(page.message);
}
const slots = expandAttributeTimeIntervals(page.attributeValues.interval, {
from: '2025-04-01',
to: '2025-04-30',
});
// Group the slots by day for a calendar view
const byDay = slots.reduce((acc, [start, end]) => {
const day = start.slice(0, 10);
(acc[day] ??= []).push([start, end]);
return acc;
}, {});
🔗 Related Documentation
- Attribute Values - the normalized shape of every attribute value
- Importing Types - importing
ITimeIntervalWindowand friends - Forms Module - form attributes carrying
localizeInfos.intervals