can1357/oh-my-pi · warning · RangeError

Invalid ISO datetime: ${value}

Error message

Invalid ISO datetime: ${value}

What it means

parseIsoDateTimeUtc() builds a Date from the (timezone-normalized) string and throws this RangeError when the result is NaN — meaning the text was non-empty but not a valid ISO 8601 datetime. The original input value is included in the message for diagnosis.

Source

Thrown at packages/mnemopi/src/util/datetime.ts:16

import { recencyHalflifeHours } from "../config";
import { LruCache } from "./lru";

const TZ_RE = /(?:Z|[+-]\d\d:?\d\d)$/;
const DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
const TS_CACHE = new LruCache<string, Date>(2000);

export type QueryTime = string | Date | null | undefined;

export function parseIsoDateTimeUtc(value: string): Date {
	let text = value.trim();
	if (!text) throw new RangeError("Invalid ISO datetime: empty string");
	if (DATE_ONLY_RE.test(text)) text += "T00:00:00Z";
	else if (!TZ_RE.test(text)) text += "Z";
	const date = new Date(text);
	if (Number.isNaN(date.getTime())) throw new RangeError(`Invalid ISO datetime: ${value}`);
	return date;
}

export function normalizeDateTimeUtc(value: Date): Date {
	const time = value.getTime();
	if (Number.isNaN(time)) throw new RangeError("Invalid Date");
	return new Date(time);
}

export function parseQueryTime(value: QueryTime): Date {
	if (value === null || value === undefined) return new Date();
	return typeof value === "string" ? parseIsoDateTimeUtc(value) : normalizeDateTimeUtc(value);
}

export function parseTsFast(value: string): Date | undefined {
	if (!value) return undefined;
	const cached = TS_CACHE.get(value);
	if (cached !== undefined) return cached;

View on GitHub (pinned to 9690622007)

Solutions

  1. Send a valid ISO 8601 string: '2026-08-31' or '2026-08-31T14:30:00Z'.
  2. Convert epoch-millis inputs with new Date(Number(value)) instead of passing them here.
  3. Pre-validate with a regex/Date.parse check and return a 400-level error to the client before calling.
  4. Normalize non-ISO formats at the API boundary (e.g. dayjs/Temporal) before parsing.

Example fix

// before
const t = parseQueryTime(req.query.since); // '08/31/2026' -> RangeError
// after
const raw = String(req.query.since ?? '');
const iso = /^\d{4}-\d{2}-\d{2}(T[\d:.]+Z?)?$/.test(raw) ? raw : null;
if (!iso) throw new HttpError(400, 'since must be ISO 8601, e.g. 2026-08-31T00:00:00Z');
const t = parseQueryTime(iso);
Defensive patterns

Strategy: validation

Validate before calling

const ISO_RE = /^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/;
function isValidIso(value) {
  return typeof value === 'string' && ISO_RE.test(value.trim()) && !Number.isNaN(Date.parse(value));
}
if (!isValidIso(input)) throw new HttpError(400, `since must be ISO 8601, got: ${input}`);
const t = parseQueryTime(input);

Type guard

function isIsoDatetimeString(v) {
  return typeof v === 'string' && !Number.isNaN(Date.parse(v)) && /^\d{4}-\d{2}-\d{2}/.test(v.trim());
}

Try / catch

try {
  const t = parseIsoDateTimeUtc(value);
} catch (err) {
  if (err instanceof RangeError && err.message.startsWith('Invalid ISO datetime')) {
    throw new HttpError(400, `Timestamp must be ISO 8601 (e.g. 2026-08-31T00:00:00Z), got: ${err.message}`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling parseIsoDateTimeUtc('not-a-date'), '2026-13-45T99:00:00Z', 'Aug 31 2026', or any non-ISO format (timestamps, locale dates); also via parseQueryTime() with such strings.

Common situations: Free-form user input in time filters, clients sending epoch millis or RFC 2822 dates instead of ISO 8601, copy-pasted dates with locale formatting, or swapped day/month values that overflow.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/a4101db1703c976c. Report an issue: GitHub.