amruthpillai/reactive-resume · error · ORPCError

BAD_REQUEST

BAD_REQUEST

Error message

Date must use YYYY-MM-DD format.

What it means

Thrown by atFromDateString() when a date string passed to an application timeline operation cannot split on '-' into three non-zero numeric components (year, month, day). The applications service requires calendar dates as strict 'YYYY-MM-DD' strings so it can pin UTC time-of-day to 12:00:00Z while preserving any existing hours/minutes. This is the first of two format guards — it catches structurally malformed input before any Date is constructed.

Source

Thrown at packages/api/src/features/applications/service.ts:22

	ApplicationTimelineEntry,
	Contact,
} from "@reactive-resume/schema/applications/data";
import type { ApplicationDocumentKind } from "../../dto/application";
import { ORPCError } from "@orpc/client";
import { and, arrayContains, desc, eq, inArray, sql } from "drizzle-orm";
import { db } from "@reactive-resume/db/client";
import * as schema from "@reactive-resume/db/schema";
import { generateId } from "@reactive-resume/utils/string";
import { resumeService } from "../resume/service";
import { getStorageService, uploadFile } from "../storage/service";

function timelineDate(value: Date | string): Date {
	return value instanceof Date ? value : new Date(value);
}

function atFromDateString(date: string, existing?: Date | string): Date {
	const [year, month, day] = date.split("-").map(Number);
	if (!year || !month || !day) throw new ORPCError("BAD_REQUEST", { message: "Date must use YYYY-MM-DD format." });

	const existingDate = existing ? timelineDate(existing) : undefined;

	const parsed = new Date(
		Date.UTC(
			year,
			month - 1,
			day,
			existingDate?.getUTCHours() ?? 12,
			existingDate?.getUTCMinutes() ?? 0,
			existingDate?.getUTCSeconds() ?? 0,
			existingDate?.getUTCMilliseconds() ?? 0,
		),
	);
	if (timelineDay(parsed) !== date) throw new ORPCError("BAD_REQUEST", { message: "Date must use YYYY-MM-DD format." });

	return parsed;
}

View on GitHub (pinned to 3a5b12e2a4)

Solutions

  1. Normalize every date to 'YYYY-MM-DD' (e.g. new Date(value).toISOString().slice(0,10)) before calling create/importMany/addNote/updateTimelineEntry.
  2. If the source is a Date object, format it with Date.UTC(...) then toISOString().slice(0,10).
  3. Strip any time component or timezone offset before submission.

Example fix

// before
applicationService.create({ ..., stageEnteredAt: '01/15/2024' });
// after
const d = new Date('2024-01-15');
const stageEnteredAt = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())).toISOString().slice(0,10);
applicationService.create({ ..., stageEnteredAt });
Defensive patterns

Strategy: validation

Validate before calling

const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
function toIsoDate(value: string | Date): string {
  const d = value instanceof Date ? value : new Date(value);
  const iso = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())).toISOString().slice(0, 10);
  if (!ISO_DATE.test(iso)) throw new Error(`Not a YYYY-MM-DD date: ${String(value)}`);
  return iso;
}
// toIsoDate('01/15/2024') -> '2024-01-15'

Type guard

function isIsoDateString(value: unknown): value is string {
  return typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value);
}

Prevention

When it happens

Trigger: Calling applicationService.create or importMany with stageEnteredAt like '2024/01/15', 'Jan 15 2024', '2024-1', '' or 'abc'; or addNote/updateTimelineEntry with a date field in any non-dashed, time-bearing, or incomplete shape.

Common situations: A frontend date picker emitting locale-formatted strings (en-US 'MM/DD/YYYY'), an importer mapping spreadsheet dates verbatim, or a test fixture using ISO datetime ('2024-01-15T10:00:00Z') instead of date-only.

Related errors


AI-assisted analysis of amruthpillai/reactive-resume@3a5b12e2a4 (2026-08-12). Data as JSON: /api/errors/2f452c1a8c58d98c. Report an issue: GitHub.