{"record":{"id":"8b709a29b72282e0","repo":"koala73/worldmonitor","slug":"end-date-must-be-yyyy-mm-dd","errorCode":null,"errorMessage":"end_date must be YYYY-MM-DD","messagePattern":"end_date must be YYYY-MM-DD","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"server/worldmonitor/intelligence/v1/search-sec-filings.ts","lineNumber":49,"sourceCode":"  const startDateValid = !req.startDate || isEdgarIsoDate(req.startDate);\n  const endDateValid = !req.endDate || isEdgarIsoDate(req.endDate);\n  const violations = [\n    ...(formsNormalized === null\n      ? [{ field: 'forms', description: 'forms must be a comma-separated form list such as \"8-K\" or \"10-K,10-Q\"' }]\n      : []),\n    ...(!startDateValid\n      ? [{ field: 'start_date', description: 'start_date must be YYYY-MM-DD' }]\n      : []),\n    ...(!endDateValid\n      ? [{ field: 'end_date', description: 'end_date must be YYYY-MM-DD' }]\n      : []),\n    ...(req.startDate && req.endDate\n      && startDateValid && endDateValid\n      && req.startDate > req.endDate\n      ? [{ field: 'start_date', description: 'start_date must not be after end_date' }]\n      : []),\n  ];\n  if (violations.length > 0) throw new ValidationError(violations);\n\n  const limit = req.limit > 0 ? Math.min(req.limit, MAX_LIMIT) : DEFAULT_LIMIT;\n\n  const result = await searchEdgarFullText({\n    query,\n    forms: formsNormalized || undefined,\n    startDate: req.startDate,\n    endDate: req.endDate,\n    size: limit,\n  });\n\n  if (!result) {\n    return { results: [], total: 0, unavailable: true, fetchedAtMs: Date.now() };\n  }\n\n  return {\n    results: result.results.slice(0, limit),\n    total: result.total,","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/server/worldmonitor/intelligence/v1/search-sec-filings.ts#L31-L67","documentation":"Thrown by searchSecFilings when the endDate filter fails isEdgarIsoDate (server/_shared/sec-edgar.ts:492). The check is deliberately strict: the value must match /^\\d{4}-\\d{2}-\\d{2}$/ AND round-trip through Date.UTC, so calendar-impossible dates like 2024-02-30 are rejected even though Date.parse would silently normalize them. The handler fails closed instead of dropping the malformed filter, because silently dropping a date filter would widen the EDGAR result set while the caller believes the range was applied.","triggerScenarios":"Calling the searchSecFilings RPC with endDate=\"01/31/2024\" (US format), \"2024-1-5\" (unpadded), \"20240131\", \"2024-01-31T00:00:00Z\" (datetime instead of date-only), \" 2024-01-31\" (whitespace), or impossible dates like \"2023-02-29\" or \"2024-13-01\". Only leaving endDate unset/undefined skips the check.","commonSituations":"Building the date from a JS Date without zero-padding (getMonth() is 0-indexed, getDay() vs getDate() confusion), passing free-text user input straight through, or reusing an ISO 8601 datetime string from another API where a date-only string is required.","solutions":["Format dates as zero-padded YYYY-MM-DD, e.g. d.toISOString().slice(0,10)","Validate client-side with the same regex + calendar round-trip before calling the RPC","Omit endDate entirely when no upper bound is needed","If the user typed the date, parse and re-serialize it in the UI before submit"],"exampleFix":"// before\nconst resp = await client.searchSecFilings({ query: 'material cyber', endDate: new Date().toString() }); // throws\n\n// after\nconst today = new Date().toISOString().slice(0, 10); // \"2026-08-21\"\nconst resp = await client.searchSecFilings({ query: 'material cyber', endDate: today });","handlingStrategy":"validation","validationCode":"const EDGAR_ISO_DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\nfunction isEdgarIsoDate(value: string): boolean {\n  if (!EDGAR_ISO_DATE_RE.test(value)) return false;\n  const y = Number(value.slice(0, 4));\n  const m = Number(value.slice(5, 7));\n  const d = Number(value.slice(8, 10));\n  const dt = new Date(Date.UTC(y, m - 1, d));\n  return dt.getUTCFullYear() === y && dt.getUTCMonth() === m - 1 && dt.getUTCDate() === d;\n}\nif (req.endDate !== undefined && !isEdgarIsoDate(req.endDate)) {\n  throw new Error(`endDate must be YYYY-MM-DD, got: ${req.endDate}`);\n}\nawait client.searchSecFilings(req);","typeGuard":"function isEdgarIsoDate(value: unknown): value is `${number}${number}${number}${number}-${number}${number}-${number}${number}` {\n  return typeof value === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(value)\n    && isEdgarIsoDate(value); // shape + calendar round-trip\n}","tryCatchPattern":"try { await client.searchSecFilings(req); }\ncatch (e) {\n  if (e instanceof ValidationError) {\n    const bad = e.violations?.filter(v => v.field === 'end_date');\n    if (bad) markEndDateInvalid(bad.map(v => v.description).join('; '));\n  } throw e;\n}","preventionTips":["Always emit date filters with toISOString().slice(0,10)","Never pass raw user text as startDate/endDate — parse and re-serialize in the UI","Copy isEdgarIsoDate into a shared client util so both sides use identical rules"],"tags":["sec-edgar","date-format","validation","typescript"],"backgroundTag":"date-format-validation","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}