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

  1. Constrain hourCycle to the literal union before it reaches ion-datetime; prefer letting the component resolve it from locale.
  2. Validate the value with an allow-list guard at the boundary where you receive it.
  3. If you call getFormattedHour yourself, default the parameter: getFormattedHour(hour, hourCycle ?? 'h12').
  4. 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

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


AI-assisted analysis of ionic-team/ionic-framework@625f9c38ad (2026-08-12). Data as JSON: /api/errors/0c7aeda7425fbf83. Report an issue: GitHub.