{"record":{"id":"cf2dd7bc143ab3e4","repo":"actualbudget/actual","slug":"bad-type-format-str","errorCode":null,"errorMessage":"Bad ${type} format: ${str}","messagePattern":"Bad (.+?) format: (.+?)","errorType":"exception","errorClass":"CompileError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/aql/compiler.ts","lineNumber":219,"sourceCode":"\nfunction parseMonth(str) {\n  const m = str.match(/^(\\d{4}-\\d{2})$/);\n  if (m) {\n    return typed(dateToInt(m[1]), 'date', { literal: true });\n  }\n  return null;\n}\n\nfunction parseYear(str) {\n  const m = str.match(/^(\\d{4})$/);\n  if (m) {\n    return typed(dateToInt(m[1]), 'date', { literal: true });\n  }\n  return null;\n}\n\nfunction badDateFormat(str, type) {\n  throw new CompileError(`Bad ${type} format: ${str}`);\n}\n\nfunction inferParam(param, type) {\n  const existingType = param.paramType;\n  if (existingType) {\n    const casts = {\n      date: ['string'],\n      'date-month': ['date'],\n      'date-year': ['date', 'date-month'],\n      id: ['string'],\n      float: ['integer'],\n    };\n\n    if (\n      existingType !== type &&\n      (!casts[type] || !casts[type].includes(existingType))\n    ) {\n      throw new Error(","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/aql/compiler.ts#L201-L237","documentation":"When a literal string is cast to a date-like type, castInput runs it through parseDate/parseMonth/parseYear; on failure badDateFormat (compiler.ts:218) throws this CompileError. AQL stores dates as integers, so literal date strings must strictly match YYYY-MM-DD, YYYY-MM, or YYYY.","triggerScenarios":"q('transactions').filter({ date: '01/15/2024' }) or { 'date': '2024-1-5' } — any literal string not matching the strict patterns; also 'date-month' casts like { $month: 'foo' } with a non-parseable value.","commonSituations":"Locale-formatted dates (MM/DD/YYYY, DD.MM.YYYY); dates with time components ('2024-01-15T00:00:00Z'); user-supplied dates pasted from UI inputs with loose formats; missing zero-padding like '2024-1-5'.","solutions":["Reformat the literal to strict ISO: YYYY-MM-DD for date, YYYY-MM for month, YYYY for year.","Normalize user input before querying, e.g. day/month zero-padded and no time component.","If the value is dynamic and can't be a literal, convert it to a date-machinable string in your code first; the compiler only auto-parses literal strings."],"exampleFix":"// before\nq('transactions').filter({ date: '01/15/2024' })\n// after\nq('transactions').filter({ date: '2024-01-15' })","handlingStrategy":"validation","validationCode":"function isIsoDate(s) { return /^\\d{4}-\\d{2}-\\d{2}$/.test(s); }\nfunction isIsoMonth(s) { return /^\\d{4}-\\d{2}$/.test(s); }\nfunction normalizeDateInput(s) {\n  const d = new Date(s);\n  if (isNaN(d)) throw new Error('Unparseable date: ' + s);\n  return d.toISOString().slice(0, 10);\n}","typeGuard":"function isMachineDate(v) {\n  return typeof v === 'string' && /^(\\d{4}|\\d{4}-\\d{2}|\\d{4}-\\d{2}-\\d{2})$/.test(v);\n}","tryCatchPattern":"try {\n  runQuery(q.filter({ date: userInput }));\n} catch (e) {\n  if (e.message.startsWith('Bad date format')) {\n    q = q.filter({ date: normalizeDateInput(userInput) });\n  } else throw e;\n}","preventionTips":["Always store/compare dates in Actual's machine format (YYYY-MM-DD)","Zero-pad month and day; strip time components","Convert locale-formatted user input before querying"],"tags":["aql","date","format","compile-error"],"backgroundTag":"bad-date-format","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}