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
- 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).
- If you control the call site, convert 'min'/'hr'/'ms' values to milliseconds yourself before calling wait().
- 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
- Prefer passing raw millisecond numbers to wait() to sidestep string parsing entirely.
- Avoid 'min'/'hr' strings until the library's unit-switch bug is fixed; they pass the type check but throw at runtime.
- Beware 'ms' strings: they currently pass but are scaled as seconds — always verify computed delays in tests.
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
- Must combine observable instance with an array of observable
- A combineLatest projection requires an array of observable v
- Cannot install the RxJS create protocol: ${CREATE_SYMBOL_KEY
- RxJS Next expand does not support a scheduler argument.
- RxJS Next expand options must be an object.
AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28).
Data as JSON: /api/errors/70f3c9204e8bbe14.
Report an issue: GitHub.