{"record":{"id":"6d06420baafa66af","repo":"actualbudget/actual","slug":"start-date-must-be-before-or-equal-to-end-date","errorCode":null,"errorMessage":"Start date must be before or equal to end date.","messagePattern":"Start date must be before or equal to end date\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/components/reports/spreadsheets/summary-spreadsheet.ts","lineNumber":70,"sourceCode":"      endDay = d.parse(\n        monthUtils.getMonth(end) ===\n          monthUtils.getMonth(monthUtils.currentDay())\n          ? monthUtils.currentDay()\n          : monthUtils.lastDayOfMonth(end),\n        'yyyy-MM-dd',\n        new Date(),\n      );\n    } catch (error) {\n      console.error('Error parsing dates:', error);\n      throw new Error('Invalid date format provided');\n    }\n\n    if (!d.isValid(startDay) || !d.isValid(endDay)) {\n      throw new Error('Invalid date values provided');\n    }\n\n    if (d.isAfter(startDay, endDay)) {\n      throw new Error('Start date must be before or equal to end date.');\n    }\n\n    const getOneDatePerMonth = (start: Date, end: Date) => {\n      const months = [];\n      let currentDate = d.startOfMonth(start);\n\n      while (!d.isSameMonth(currentDate, end)) {\n        months.push(currentDate);\n        currentDate = d.addMonths(currentDate, 1);\n      }\n      months.push(end);\n\n      return months;\n    };\n\n    const makeRootQuery = () =>\n      q('transactions')\n        .filter({","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/components/reports/spreadsheets/summary-spreadsheet.ts#L52-L88","documentation":"summarySpreadsheet enforces a chronological range: after validating both dates it checks d.isAfter(startDay, endDay) and throws 'Start date must be before or equal to end date.' when the range is inverted. This prevents nonsensical queries (negative or zero-length ranges where the query's $gte bound exceeds $lte) and division issues in per-month/per-year calculations.","triggerScenarios":"Calling summarySpreadsheet with start later than end, e.g. start='2024-06', end='2024-01'; also equal-month ranges where end resolves to a day before start's first day (edge cases with currentDay substitution).","commonSituations":"Swapped start/end arguments at a call site; a date-range picker letting users set an 'end' before 'start'; programmatic range math (subtracting months from start) that overshoots below end.","solutions":["Swap the arguments so start <= end at the call site","Validate the ordering before calling: if (new Date(start) > new Date(end)) fix or reject","Clamp/normalize the range in UI code (e.g. date picker constraints) so an inverted range can't be produced","Catch the error and show a user-facing message asking to correct the range"],"exampleFix":"// before\nsummarySpreadsheet('2024-06', '2024-01', ...); // inverted\n// after\nconst [start, end] = ['2024-06', '2024-01'].sort(); // or fix the picker constraint\nsummarySpreadsheet(start, end, ...);","handlingStrategy":"validation","validationCode":"if (new Date(start + '-01') > new Date(end + '-01')) {\n  [start, end] = [end, start]; // or reject\n}","typeGuard":"function isOrderedRange(start: string, end: string): boolean {\n  return start <= end; // yyyy-MM compares lexicographically\n}","tryCatchPattern":"try {\n  await summarySpreadsheet(start, end, conditions, op, content, locale)(spreadsheet, setData);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Start date must be')) {\n    showUserMessage('Please fix the report date range: start is after end.');\n  } else throw e;\n}","preventionTips":["Constrain date-range pickers so end can't precede start","Normalize (sort) start/end before invoking report builders","Assert ordering in tests for every report component","Keep start/end as a single {start,end} range object with an invariant check"],"tags":["date-parsing","reports","validation"],"backgroundTag":"invalid-date-range","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}