{"record":{"id":"995d7bdea754fd1f","repo":"louislam/uptime-kuma","slug":"invalid-end-date","errorCode":null,"errorMessage":"Invalid end date","messagePattern":"Invalid end date","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/model/maintenance.js","lineNumber":172,"sourceCode":"        bean.interval_day = obj.intervalDay;\n        bean.timezone = obj.timezoneOption;\n        bean.active = obj.active;\n\n        if (obj.dateRange[0]) {\n            const parsedDate = new Date(obj.dateRange[0]);\n            if (isNaN(parsedDate.getTime()) || parsedDate.getFullYear() > 9999) {\n                throw new Error(\"Invalid start date\");\n            }\n\n            bean.start_date = obj.dateRange[0];\n        } else {\n            bean.start_date = null;\n        }\n\n        if (obj.dateRange[1]) {\n            const parsedDate = new Date(obj.dateRange[1]);\n            if (isNaN(parsedDate.getTime()) || parsedDate.getFullYear() > 9999) {\n                throw new Error(\"Invalid end date\");\n            }\n\n            bean.end_date = obj.dateRange[1];\n        } else {\n            bean.end_date = null;\n        }\n\n        if (bean.strategy === \"cron\") {\n            bean.duration = obj.durationMinutes * 60;\n            bean.cron = obj.cron;\n            this.validateCron(bean.cron);\n        }\n\n        if (bean.strategy.startsWith(\"recurring-\")) {\n            bean.start_time = parseTimeFromTimeObject(obj.timeRange[0]);\n            bean.end_time = parseTimeFromTimeObject(obj.timeRange[1]);\n            bean.weekdays = JSON.stringify(obj.weekdays);\n            bean.days_of_month = JSON.stringify(obj.daysOfMonth);","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/louislam/uptime-kuma/blob/6b5ea0155793e666666745fb8d6fef1e829543a2/server/model/maintenance.js#L154-L190","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Send dateRange[1] as a valid ISO 8601 string later than dateRange[0].","Validate both endpoints client-side before POST/PUT to /api/maintenance/*.","If only a single-point window is needed, send the same valid timestamp for both range slots.","Reject empty/whitespace strings before they reach new Date()."],"exampleFix":"// before\nif (obj.dateRange[1]) {\n    const parsedDate = new Date(obj.dateRange[1]);\n    if (isNaN(parsedDate.getTime()) || parsedDate.getFullYear() > 9999) {\n        throw new Error(\"Invalid end date\");\n    }\n    bean.end_date = obj.dateRange[1];\n}\n\n// after — also enforce ordering and report the bad input\nif (obj.dateRange[1]) {\n    const end = new Date(obj.dateRange[1]);\n    const start = bean.start_date ? new Date(bean.start_date) : null;\n    if (isNaN(end.getTime()) || end.getFullYear() > 9999) {\n        throw new Error(`Invalid end date: ${JSON.stringify(obj.dateRange[1])}`);\n    }\n    if (start && end < start) {\n        throw new Error(\"End date must not be earlier than start date\");\n    }\n    bean.end_date = end.toISOString();\n}","handlingStrategy":"validation","validationCode":"function isValidMaintenanceDate(v) {\n    if (!v) return true;\n    const d = new Date(v);\n    return !isNaN(d.getTime()) && d.getFullYear() <= 9999;\n}\nfunction assertValidRange(dateRange) {\n    if (!isValidMaintenanceDate(dateRange?.[0])) throw new Error(\"Invalid start date\");\n    if (!isValidMaintenanceDate(dateRange?.[1])) throw new Error(\"Invalid end date\");\n    if (dateRange?.[0] && dateRange?.[1] && new Date(dateRange[1]) < new Date(dateRange[0])) {\n        throw new Error(\"End date must not be earlier than start date\");\n    }\n}","typeGuard":"function isValidRange(dateRange) {\n    const [s, e] = dateRange || [];\n    const ok = (v) => !v || (!isNaN(new Date(v).getTime()) && new Date(v).getFullYear() <= 9999);\n    return ok(s) && ok(e) && (!s || !e || new Date(e) >= new Date(s));\n}","tryCatchPattern":"try {\n    await Maintenance.jsonToBean(bean, obj);\n} catch (e) {\n    if (/Invalid (start|end) date/.test(e.message)) {\n        // highlight the offending date field in the UI\n    }\n    throw e;\n}","preventionTips":["Validate the whole dateRange client-side, including ordering, before submit.","Use a single date-time picker component that emits ISO strings.","Do not let raw text inputs reach the maintenance API."],"tags":["maintenance","validation","date"],"backgroundTag":null,"analyzedSha":"6b5ea0155793e666666745fb8d6fef1e829543a2","analyzedAt":"2026-08-12T23:42:12.959Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}