{"record":{"id":"63a7a1296fe97e90","repo":"xuxueli/xxl-job","slug":"offset-from-last-day-must-be","errorCode":null,"errorMessage":"Offset from last day must be <= {}","messagePattern":"Offset from last day must be <= (.+?)","errorType":"validation","errorClass":"ParseException","httpStatus":null,"severity":"error","filePath":"xxl-job-admin/src/main/java/com/xxl/job/admin/business/scheduler/cron/CronExpression.java","lineNumber":685,"sourceCode":"        } else if (c == 'L') {\n\n            if(type < DAY_OF_MONTH)\n                throw new ParseException(\"'L' not expected in seconds, minutes or hours fields.\", i);\n\n            i++;\n            if (type == DAY_OF_WEEK) {\n                addToSet(7, 7, 0, type);\n            }\n            if (type == DAY_OF_MONTH) {\n                int dom = LAST_DAY_OFFSET_END;\n                boolean nearestWeekday = false;\n                if (s.length() > i) {\n                    c = s.charAt(i);\n                    if (c == '-') {\n                        ValueSet vs = getValue(0, s, i + 1);\n                        int offset = vs.value;\n                        if (offset > MAX_LAST_DAY_OFFSET)\n                            throw new ParseException(\"Offset from last day must be <= \" + MAX_LAST_DAY_OFFSET, i + 1);\n                        dom -= offset;\n                        i = vs.pos;\n                    }\n                    if (s.length() > i) {\n                        c = s.charAt(i);\n                        if (c == 'W') {\n                            nearestWeekday = true;\n                            i++;\n                        }\n                    }\n                }\n                if (nearestWeekday) {\n                    nearestWeekdays.add(dom);\n                } else {\n                    daysOfMonth.add(dom);\n                }\n            }\n            return i;","sourceCodeStart":667,"sourceCodeEnd":703,"githubUrl":"https://github.com/xuxueli/xxl-job/blob/e74c784f68f81fa89cb350913ef15794865d7b12/xxl-job-admin/src/main/java/com/xxl/job/admin/business/scheduler/cron/CronExpression.java#L667-L703","documentation":"When using the 'L-N' syntax in the Day-of-Month field (meaning 'N days before the last day of the month'), the offset N must not exceed MAX_LAST_DAY_OFFSET (30). This error fires when the parsed offset exceeds 30.","triggerScenarios":"A cron expression like '0 0 0 L-31 * ?' where the offset after 'L-' is greater than 30. The parser reads the offset via getValue and compares against MAX_LAST_DAY_OFFSET at line 684.","commonSituations":"Generating offset values dynamically and exceeding the 30-day cap, or misunderstanding that months can have at most 31 days so offsets beyond 30 are meaningless.","solutions":["Reduce the offset to 30 or less (e.g., 'L-7' for a week before the last day).","If a larger window is needed, reconsider the scheduling approach — use a range or run daily with a date check in the job itself.","Clamp or validate the offset value before constructing the cron string."],"exampleFix":"// before\n\"0 0 0 L-45 * ?\"\n// after\n\"0 0 0 L-30 * ?\"","handlingStrategy":"validation","validationCode":"// Check 'L-N' offset in day-of-month (field index 3)\nString[] parts = cron.trim().split(\"\\\\s+\");\nif (parts.length > 3 && parts[3].contains(\"L-\")) {\n    String off = parts[3].substring(parts[3].indexOf(\"L-\") + 2);\n    int offset = Integer.parseInt(off);\n    if (offset > 30) {\n        throw new IllegalArgumentException(\"Last-day offset must be <= 30, got: \" + offset);\n    }\n}","typeGuard":"boolean isValidLastDayOffset(String domField) {\n    if (!domField.contains(\"L-\")) return true;\n    String num = domField.substring(domField.indexOf(\"L-\") + 2);\n    try { return Integer.parseInt(num) <= 30; } catch (NumberFormatException e) { return false; }","tryCatchPattern":"try {\n    new CronExpression(cron);\n} catch (ParseException e) {\n    if (e.getMessage().contains(\"Offset from last day\")) {\n        // clamp offset to 30 and retry\n    }\n}","preventionTips":["Clamp last-day offsets to 30 before building the expression.","Remember that months have at most 31 days, so offsets beyond 30 are meaningless.","Unit-test boundary values (L-30 should pass, L-31 should fail)."],"tags":["cron","validation","quartz","day-of-month"],"backgroundTag":null,"analyzedSha":"e74c784f68f81fa89cb350913ef15794865d7b12","analyzedAt":"2026-08-14T04:22:43.715Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}