ReactiveX/rxjs · error · TypeError

Invalid time format: ${time}

Error message

Invalid time format: ${time}

What it means

Thrown by timeToMilliseconds when a string delay passed to ScheduledObservable.wait() does not end in a unit character the switch statement recognizes. The switch only inspects the LAST character of the string ('s', 'd', etc.), so although assertTimeFormat accepts units like 'min' and 'hr', any string ending in 'n' or 'r' falls through to the default branch and throws a TypeError. This is the deeper of the two format errors because the value can pass the regex assertion and still fail here.

Source

Thrown at packages/rxjs/src/testing/scheduled-observable.ts:95

    return time;
  }

  assertTimeFormat(time);
  const value = parseInt(time.slice(0, -1), 10);

  switch (time.slice(-1)) {
    case 's':
      return value * 1000;
    case 'ms':
      return value;
    case 'min':
      return value * 60 * 1000;
    case 'hr':
      return value * 60 * 60 * 1000;
    case 'd':
      return value * 24 * 60 * 60 * 1000;
    default:
      throw new TypeError(`Invalid time format: ${time}`);
  }
}

function assertTimeFormat(time: string): asserts time is TimeString {
  if (!/^\d+(s|ms|min|hr|d)$/.test(time)) {
    throw new Error(`Invalid time format: ${time}`);
  }
}

View on GitHub (pinned to 54796b38a5)

Solutions

  1. Use only units the switch actually handles correctly: plain seconds ('5s'), days ('1d'), or pass a raw millisecond number instead of a string for other units (e.g. wait(5 * 60 * 1000) for 5 minutes).
  2. If you control the call site, convert 'min'/'hr'/'ms' values to milliseconds yourself before calling wait().
  3. Fix the helper: switch on the full unit suffix (extract with a regex capture like /^(\d+)(ms|s|min|hr|d)$/) so 'min' and 'hr' are handled, eliminating the fall-through TypeError and the 'ms'-treated-as-'s' bug; add tests for every unit.

Example fix

// before
scheduled.wait('5min'); // throws TypeError: Invalid time format: 5min

// after
scheduled.wait(5 * 60 * 1000); // milliseconds
// or, if fixing the library, parse the full suffix:
// const m = /^(\d+)(ms|s|min|hr|d)$/.exec(time)!;
// const mult = { ms: 1, s: 1000, min: 60000, hr: 3600000, d: 86400000 }[m[2]];
Defensive patterns

Strategy: validation

Validate before calling

import type { TimeString } from './scheduled-observable';

// Only units the current switch handles safely end-to-end
const SAFE = /^\d+(s|d)$/;
function isSafeDelay(v: unknown): v is number | `${number}s` | `${number}d` {
  return typeof v === 'number' || (typeof v === 'string' && SAFE.test(v));
}

const delay: unknown = '5min';
if (isSafeDelay(delay)) {
  scheduled.wait(delay);
} else {
  scheduled.wait(toMillis(delay as string)); // convert min/hr/ms yourself
}

Type guard

function isSafeTimeString(t: string): t is `${number}s` | `${number}d` {
  return /^\d+s$/.test(t) || /^\d+d$/.test(t);
}

Try / catch

try {
  scheduled.wait(delay as any);
} catch (e) {
  if (e instanceof TypeError && /Invalid time format/.test(String(e.message))) {
    scheduled.wait(toMillisecondsManually(delay)); // fallback: convert to ms number
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling scheduled.wait('5min') or scheduled.wait('2hr') (or any accepted TimeString whose last character is not 's' or 'd'): assertTimeFormat passes, then switch(time.slice(-1)) hits 'n'/'r' and throws `Invalid time format: 5min`. Also note wait('500ms') silently passes but is mis-multiplied as seconds because the switch only sees 's'.

Common situations: Migrating RxJS 7 marble/scheduler tests to the new ScheduledObservable test helper and using natural unit strings like '10min' or '1hr' that the TimeString type appears to allow. Passing dynamically built strings or user-supplied config values into wait(). TypeScript template-literal type TimeString suggests 'min'/'hr' are valid, luring developers into the throwing path.

Related errors


AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28). Data as JSON: /api/errors/70f3c9204e8bbe14. Report an issue: GitHub.