louislam/uptime-kuma · error · Error

Invalid end date

Error message

Invalid end date

What it means

Sibling of the start-date check, applied to obj.dateRange[1] (the end of the maintenance window). `new Date(obj.dateRange[1])` must produce a valid date with year <= 9999. The two checks are independent, so a payload with a good start but bad end will set start_date on the bean first and then throw here — callers should treat jsonToBean as atomic and not persist a half-filled bean.

Source

Thrown at server/model/maintenance.js:172

        bean.interval_day = obj.intervalDay;
        bean.timezone = obj.timezoneOption;
        bean.active = obj.active;

        if (obj.dateRange[0]) {
            const parsedDate = new Date(obj.dateRange[0]);
            if (isNaN(parsedDate.getTime()) || parsedDate.getFullYear() > 9999) {
                throw new Error("Invalid start date");
            }

            bean.start_date = obj.dateRange[0];
        } else {
            bean.start_date = null;
        }

        if (obj.dateRange[1]) {
            const parsedDate = new Date(obj.dateRange[1]);
            if (isNaN(parsedDate.getTime()) || parsedDate.getFullYear() > 9999) {
                throw new Error("Invalid end date");
            }

            bean.end_date = obj.dateRange[1];
        } else {
            bean.end_date = null;
        }

        if (bean.strategy === "cron") {
            bean.duration = obj.durationMinutes * 60;
            bean.cron = obj.cron;
            this.validateCron(bean.cron);
        }

        if (bean.strategy.startsWith("recurring-")) {
            bean.start_time = parseTimeFromTimeObject(obj.timeRange[0]);
            bean.end_time = parseTimeFromTimeObject(obj.timeRange[1]);
            bean.weekdays = JSON.stringify(obj.weekdays);
            bean.days_of_month = JSON.stringify(obj.daysOfMonth);

View on GitHub (pinned to 6b5ea01557)

Solutions

  1. Send dateRange[1] as a valid ISO 8601 string later than dateRange[0].
  2. Validate both endpoints client-side before POST/PUT to /api/maintenance/*.
  3. If only a single-point window is needed, send the same valid timestamp for both range slots.
  4. Reject empty/whitespace strings before they reach new Date().

Example fix

// before
if (obj.dateRange[1]) {
    const parsedDate = new Date(obj.dateRange[1]);
    if (isNaN(parsedDate.getTime()) || parsedDate.getFullYear() > 9999) {
        throw new Error("Invalid end date");
    }
    bean.end_date = obj.dateRange[1];
}

// after — also enforce ordering and report the bad input
if (obj.dateRange[1]) {
    const end = new Date(obj.dateRange[1]);
    const start = bean.start_date ? new Date(bean.start_date) : null;
    if (isNaN(end.getTime()) || end.getFullYear() > 9999) {
        throw new Error(`Invalid end date: ${JSON.stringify(obj.dateRange[1])}`);
    }
    if (start && end < start) {
        throw new Error("End date must not be earlier than start date");
    }
    bean.end_date = end.toISOString();
}
Defensive patterns

Strategy: validation

Validate before calling

function isValidMaintenanceDate(v) {
    if (!v) return true;
    const d = new Date(v);
    return !isNaN(d.getTime()) && d.getFullYear() <= 9999;
}
function assertValidRange(dateRange) {
    if (!isValidMaintenanceDate(dateRange?.[0])) throw new Error("Invalid start date");
    if (!isValidMaintenanceDate(dateRange?.[1])) throw new Error("Invalid end date");
    if (dateRange?.[0] && dateRange?.[1] && new Date(dateRange[1]) < new Date(dateRange[0])) {
        throw new Error("End date must not be earlier than start date");
    }
}

Type guard

function isValidRange(dateRange) {
    const [s, e] = dateRange || [];
    const ok = (v) => !v || (!isNaN(new Date(v).getTime()) && new Date(v).getFullYear() <= 9999);
    return ok(s) && ok(e) && (!s || !e || new Date(e) >= new Date(s));
}

Try / catch

try {
    await Maintenance.jsonToBean(bean, obj);
} catch (e) {
    if (/Invalid (start|end) date/.test(e.message)) {
        // highlight the offending date field in the UI
    }
    throw e;
}

Prevention

When it happens

Trigger: Maintenance payload where dateRange[1] is malformed, empty-but-truthy (e.g. a whitespace string), or out of year range. Also triggered when the frontend sends an end date earlier than the start — though note this code does NOT validate ordering, only validity; ordering must be checked elsewhere.

Common situations: Frontend bug that omits or mangles the end date; API caller sending only a start date and a placeholder end; locale issues; date pickers that allow manual typing of bad values.

Related errors


AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12). Data as JSON: /api/errors/995d7bdea754fd1f. Report an issue: GitHub.