can1357/oh-my-pi · warning · RangeError

Invalid ISO datetime: empty string

Error message

Invalid ISO datetime: empty string

What it means

parseIsoDateTimeUtc() normalizes ISO datetime strings to UTC dates, appending T00:00:00Z for date-only values and Z when no timezone is present. An empty (or whitespace-only) string has no parseable datetime, so it throws a RangeError with a dedicated message distinct from the generic invalid-format error.

Source

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

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);
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Omit the field entirely instead of sending an empty string so parseQueryTime falls back to now.
  2. Coerce empty/whitespace values to undefined/null before parsing: value?.trim() ? value : undefined.
  3. Validate user/config input at the boundary and reject empty datetimes with a clear client-side message.
  4. If an empty value should mean 'beginning of time', substitute a sentinel date explicitly.

Example fix

// before
const since = parseQueryTime(searchParams.get('since')); // '' -> RangeError
// after
const raw = searchParams.get('since');
const since = parseQueryTime(raw && raw.trim() ? raw : null); // null -> now
Defensive patterns

Strategy: validation

Validate before calling

function nonEmptyIsoOrNull(value) {
  if (typeof value !== 'string') return null;
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : null; // null -> parseQueryTime defaults to now
}
const since = parseQueryTime(nonEmptyIsoOrNull(req.query.since));

Type guard

function isNonEmptyIsoString(v) {
  return typeof v === 'string' && v.trim().length > 0;
}

Try / catch

try {
  const t = parseIsoDateTimeUtc(raw);
} catch (err) {
  if (err instanceof RangeError && err.message.includes('empty string')) {
    return new Date(); // treat blank input as 'now' (parseQueryTime default)
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling parseIsoDateTimeUtc('') or parseIsoDateTimeUtc(' ') — directly, or via parseQueryTime('') when a query/filter parameter was provided but empty.

Common situations: Empty query-string parameters (since=), empty env vars or config fields interpolated into time filters, API clients sending "" instead of omitting the field, or .trim() results from blank user input.

Related errors


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