Time intervals
A timeInterval attribute is a recurrence rule, not a list of slots: an anchor date, daily time ranges and repeat flags. To turn it into concrete [start, end] pairs for the window you render, use the top-level helpers expandAttributeTimeIntervals and 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 don't mutate their input and make no requests.
Where schedules live
The API returns schedules in two shapes, and the helpers accept both:
| 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)
Expands an entity attribute in one call: it walks the attribute's groups and schedules, expands each one and merges the results. The merge matters, because deduplication and ordering only hold within a single schedule; combining groups by hand can give you duplicate or unsorted slots.
For anything that isn't a timeInterval attribute it returns an empty array, so you can call it without checking type first.
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',
});
expandTimeIntervals(schedule, window)
Expands a single schedule. Use it when you already have one, typically on forms, whose schedules are already typed at localizeInfos.intervals:
import { expandTimeIntervals } from 'oneentry';
const form = await Forms.getFormByMarker('booking_form');
if ('statusCode' in form) throw new Error(form.message);
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, so you can 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 by UTC day; the time of day in
from/tois ignored. - The window is required: a schedule is an open-ended recurrence rule, and only you know how far to expand it.
Recurrence semantics
dates[0]/range[0]is both the recurrence phase and the first valid day. Nothing earlier is returned, however wide the window.dates[1]/range[1]ends validity. If it isn't later than the start, the schedule is anchored to that single day; with a recurrence flag set, the recurrence is then open-ended and only the window bounds the result.inEveryWeekrepeats every 7 days from the anchor.inEveryMonthrepeats on the same day of the month, skipping months that are too short.- With both flags set, the weekly rule applies.
- With neither flag, the schedule is a plain date range: every day in it produces slots.
- The result is deduplicated and sorted by start, then by end.
- All arithmetic is in UTC, so the result doesn't depend on the machine's timezone.
Migrating from the timeIntervals field
Earlier SDK versions added a computed timeIntervals array to every timeInterval attribute value. That field no longer exists. It held 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 exceed framework data-cache limits. It was never declared in any interface or schema either, so TypeScript code could only reach it through a cast.
Before, you 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 (dates/range, times/intervals, inEveryWeek, inEveryMonth) is unchanged and still present on every schedule. Nothing is lost: slots are computed on demand instead of up front, 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 that share 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;
}, {} as Record<string, Array<[string, string]>>);
Related
- Attribute values: the normalized shape of every attribute value
- Importing types: importing
ITimeIntervalWindowand related types - Forms Module: form attributes with
localizeInfos.intervals