actualbudget/actual · error

Invalid end date format

Error message

Invalid end date format

What it means

calendarSpreadsheet parses the report's 'end' month string (yyyy-MM) into a yyyy-MM-dd day string and then into a Date via date-fns parse. If that parse throws (malformed month string), the function rethrows a wrapped 'Invalid end date format' error instead of letting date-fns leak through. It exists to give report consumers a clear signal that the configured date range cannot be used to build the calendar query.

Source

Thrown at packages/desktop-client/src/components/reports/spreadsheets/calendar-spreadsheet.ts:74

        monthUtils.firstDayOfMonth(start),
        'yyyy-MM-dd',
        new Date(),
      );
    } catch (error) {
      console.error('Failed to parse start date:', error);
      throw new Error('Invalid start date format');
    }

    let endDay: Date;
    try {
      endDay = d.parse(
        monthUtils.lastDayOfMonth(end),
        'yyyy-MM-dd',
        new Date(),
      );
    } catch (error) {
      console.error('Failed to parse end date:', error);
      throw new Error('Invalid end date format');
    }

    const makeRootQuery = () =>
      q('transactions')
        .filter({
          $and: [
            { date: { $gte: d.format(startDay, 'yyyy-MM-dd') } },
            { date: { $lte: d.format(endDay, 'yyyy-MM-dd') } },
          ],
        })
        .filter({
          [conditionsOpKey]: filters,
        })
        .groupBy(['date'])
        .select(['date', { amount: { $sum: '$amount' } }]);

    let expenseData;
    try {

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Log/inspect the end value being passed to calendarSpreadsheet and correct it to a valid 'yyyy-MM' month string
  2. Validate the end date with a regex like /^\d{4}-\d{2}$/ and monthUtils.getMonth before calling calendarSpreadsheet
  3. If the value comes from saved report config, reset or re-save the report's date range in the UI
  4. Wrap the calendarSpreadsheet call in try/catch and fall back to a default range (e.g. current year)

Example fix

// before
calendarSpreadsheet(start, '2024/05', conditions, locale);
// after
const end = '2024-05'; // valid yyyy-MM
if (!/^\d{4}-\d{2}$/.test(end)) throw new Error('end must be yyyy-MM');
calendarSpreadsheet(start, end, conditions, locale);
Defensive patterns

Strategy: validation

Validate before calling

const isValidMonth = (s: string) => /^\d{4}-(0[1-9]|1[0-2])$/.test(s);
if (!isValidMonth(end)) throw new RangeError(`end must be yyyy-MM, got: ${end}`);

Type guard

function isValidMonthString(v: unknown): v is string {
  return typeof v === 'string' && /^\d{4}-(0[1-9]|1[0-2])$/.test(v);
}

Try / catch

try {
  await calendarSpreadsheet(start, end, conditions, locale)(spreadsheet, setData);
} catch (e) {
  if (e instanceof Error && e.message === 'Invalid end date format') {
    console.warn('Falling back to default range', e);
    await calendarSpreadsheet(start, defaultEnd, conditions, locale)(spreadsheet, setData);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling calendarSpreadsheet with an end date that is not a valid yyyy-MM month string (e.g. '2024-13', 'abc', '', '2024/05') so monthUtils.lastDayOfMonth or d.parse throws inside the try block.

Common situations: A saved report/dashboard has a corrupted or hand-edited start/end month in its config; a custom plugin or API integration passes an arbitrary date string instead of a month string; a locale-related refactor changes the date format passed into report builders.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/7e3f37095ed19abf. Report an issue: GitHub.