{"record":{"id":"2c3f81e86252298c","repo":"actualbudget/actual","slug":"can-t-cast-expr-type-to-date-year","errorCode":null,"errorMessage":"Can't cast ${expr.type} to date-year","messagePattern":"Can't cast (.+?) to date-year","errorType":"exception","errorClass":"CompileError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/aql/compiler.ts","lineNumber":312,"sourceCode":"      );\n    } else {\n      return typed(\n        `CAST(SUBSTR(${expr2.value}, 1, 6) AS integer)`,\n        'date-month',\n      );\n    }\n  } else if (type === 'date-year') {\n    let expr2;\n    if (expr.type === 'date' || expr.type === 'date-month') {\n      expr2 = expr;\n    } else if (expr.type === 'string') {\n      expr2 =\n        parseYear(expr.value) ||\n        parseMonth(expr.value) ||\n        parseDate(expr.value) ||\n        badDateFormat(expr.value, 'date-year');\n    } else {\n      throw new CompileError(`Can't cast ${expr.type} to date-year`);\n    }\n\n    if (expr2.literal) {\n      return typed(dateToInt(expr2.value.toString().slice(0, 4)), 'date-year', {\n        literal: true,\n      });\n    } else {\n      return typed(\n        `CAST(SUBSTR(${expr2.value}, 1, 4) AS integer)`,\n        'date-year',\n      );\n    }\n  } else if (type === 'id') {\n    if (expr.type === 'string') {\n      return typed(expr.value, 'id', { literal: expr.literal });\n    }\n  } else if (type === 'float') {\n    if (expr.type === 'integer') {","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/aql/compiler.ts#L294-L330","documentation":"castInput needs a `date-year` value (YYYY). Accepted sources are `date`, `date-month`, `string`, `any`, params, and null; other types throw with the source type named. Strings are parsed via parseYear/parseMonth/parseDate, and date fields are truncated with SQL SUBSTR.","triggerScenarios":"Using a year function/filter with an expression typed integer, float, boolean, id, array, etc. — anything that is not date/date-month/string/any.","commonSituations":"Yearly summary queries that accidentally reference a numeric column (amount, balance) instead of `date`; programmatic query builders that pass numbers for years into the wrong slot.","solutions":["Pass a date field or a string like '2024' / '2024-01' / '2024-01-15'","Confirm the referenced field is the `date` column","Convert the year to a string before passing it into the query","Rebuild the filter using { $year: { $field: 'date' } }"],"exampleFix":"// before\nq.filter({ $year: { $field: 'amount' } })\n// after\nq.filter({ $year: { $field: 'date' } })","handlingStrategy":"validation","validationCode":"const YEAR_RE = /^(\\d{4}(-\\d{2}(-\\d{2})?)?)$/;\nif (!(typeof v === 'string' && YEAR_RE.test(v))) {\n  throw new Error('Expected YYYY (or YYYY-MM-DD) string');\n}","typeGuard":"function isYearLike(v) {\n  return typeof v === 'string' && /^\\d{4}(-\\d{2}(-\\d{2})?)?$/.test(v);\n}","tryCatchPattern":"try {\n  runQuery(q.filter({ $year: { $field: field } }));\n} catch (e) {\n  if (/Can't cast .* to date-year/.test(e.message)) {\n    throw new Error(`Field '${field}' cannot be cast to a year`, { cause: e });\n  }\n  throw e;\n}","preventionTips":["Pass date/date-month fields or year strings into $year","Don't pass raw numbers (2024) where a year string/expression is expected","Verify the field reference points to `date`"],"tags":["aql","query-compiler","type-cast","dates"],"backgroundTag":"aql-type-cast-error","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}