ionic-team/ionic-framework · error · Error
Invalid hour cycle "${hourCycle}"
Error message
Invalid hour cycle "${hourCycle}" What it means
Thrown by getFormattedHour() in format.ts when formatting hour 0 (midnight) and hourCycle is not one of the four known cycles. getFormattedHour is used to render the labels of the ion-datetime time columns, so the throw fires during rendering exactly at the midnight boundary.
Source
Thrown at core/src/components/datetime/utils/format.ts:123
export const getFormattedHour = (hour: number, hourCycle: DatetimeHourCycle): string => {
/**
* Midnight for h11 starts at 0:00am
* Midnight for h12 starts at 12:00am
* Midnight for h23 starts at 00:00
* Midnight for h24 starts at 24:00
*/
if (hour === 0) {
switch (hourCycle) {
case 'h11':
return '0';
case 'h12':
return '12';
case 'h23':
return '00';
case 'h24':
return '24';
default:
throw new Error(`Invalid hour cycle "${hourCycle}"`);
}
}
const use24Hour = is24Hour(hourCycle);
/**
* h23 and h24 use 24 hour times.
*/
if (use24Hour) {
return addTimePadding(hour);
}
return hour.toString();
};
/**
* Generates an aria-label to be read by screen readers
* given a local, a date, and whether or not that date is
* today's date.View on GitHub (pinned to 625f9c38ad)
Solutions
- Constrain hourCycle to the literal union before it reaches ion-datetime; prefer letting the component resolve it from locale.
- Validate the value with an allow-list guard at the boundary where you receive it.
- If you call getFormattedHour yourself, default the parameter: getFormattedHour(hour, hourCycle ?? 'h12').
- Audit 'as any'/'as DatetimeHourCycle' casts on hour-cycle related fields.
Example fix
// before const label = getFormattedHour(0, cycleFromUrl as DatetimeHourCycle); // after const VALID = ['h11','h12','h23','h24'] as const; const cycle = (VALID as readonly string[]).includes(cycleFromUrl) ? (cycleFromUrl as DatetimeHourCycle) : 'h12'; const label = getFormattedHour(0, cycle);
Defensive patterns
Strategy: type-guard
Validate before calling
const OK = ['h11','h12','h23','h24']; const safeCycle = OK.includes(cycle) ? cycle : 'h12'; getFormattedHour(0, safeCycle);
Type guard
function isDatetimeHourCycle(v: unknown): v is DatetimeHourCycle {
return typeof v === 'string' && ['h11','h12','h23','h24'].includes(v);
} Try / catch
try {
label = getFormattedHour(hour, cycle);
} catch (e) {
if (e instanceof Error && /Invalid hour cycle/.test(e.message)) {
label = getFormattedHour(hour, 'h12');
} else { throw e; }
} Prevention
- Default optional hourCycle parameters in your own helpers.
- Validate cycle at the trust boundary (config, URL, IPC).
- Keep a single source of truth for the allowed cycle list.
When it happens
Trigger: getFormattedHour(0, hourCycle) is called with an hourCycle outside 'h11'..'h24'. This happens when the hour column is built for midnight and the cycle value was cast unsafely, left undefined by a partial state, or corrupted upstream in generateTime.
Common situations: Same class of issue as error 0: unsafe casts, SSR with partial locale data, or a custom wrapper that forwards a user-supplied string into the datetime without validating it.
Related errors
- Invalid hour cycle "${hourCycle}"
- Hour value not found from DateTimeFormat
- Invalid hour cycle "${hourCycle}"
- No day of week provided
- No day provided
AI-assisted analysis of ionic-team/ionic-framework@625f9c38ad (2026-08-12).
Data as JSON: /api/errors/0c7aeda7425fbf83.
Report an issue: GitHub.