{"record":{"id":"9b703f9b87a1b1b3","repo":"jeecgboot/JeecgBoot","slug":"offset-from-last-day-must-be-max-last-day-offs","errorCode":null,"errorMessage":"Offset from last day must be <= {MAX_LAST_DAY_OFFSET}","messagePattern":"Offset from last day must be <= (.+?)","errorType":"exception","errorClass":"ParseException","httpStatus":null,"severity":"error","filePath":"jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/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/jeecgboot/JeecgBoot/blob/96fb33f5ec68516da0b0147da06b2eb0419e063a/jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-xxljob/src/main/java/com/xxl/job/admin/business/scheduler/cron/CronExpression.java#L667-L703","documentation":"For DAY_OF_MONTH the 'L-N' syntax means 'N days before the last day of month' (e.g. L-3 = third-to-last day). The parser caps the offset at MAX_LAST_DAY_OFFSET (=30); an offset greater than 30 is rejected because no month has more than 31 days so a larger backward offset is meaningless. The thrown message interpolates the constant so it reads '<= 30'.","triggerScenarios":"A day-of-month like 'L-31', 'L-40', or a computed 'L-' + largeNumber. Also negative or garbage that parseInt turns into a large value. Index i+1 points at the digit start.","commonSituations":"Programmatic offset that is unbounded; confusion about whether the offset is 0- or 1-based; trying to express 'end of quarter' with a fixed large offset.","solutions":["Keep the L-offset between 0 and 30 (L-0 or just L = last day).","If you need an earlier date, use an explicit day number (e.g. '15') instead of a large L-offset.","Clamp computed offsets to the 0-30 range before assembling the cron string."],"exampleFix":"// before - offset beyond the 30-day cap\nString cron = \"0 0 0 L-40 * ?\";  // Offset from last day must be <= 30\n\n// after - valid offset, or explicit day\nString cron = \"0 0 0 L-5 * ?\";\n// or a concrete day number\nString cron = \"0 0 0 1 * ?\";","handlingStrategy":"validation","validationCode":"private static final int MAX_LAST_DAY_OFFSET = 30;\n\n// Validate an 'L-N' day-of-month token before assembling the cron string.\npublic static String validateLastDayOffset(String domField) {\n    if (domField == null || !domField.startsWith(\"L-\")) return null;\n    try {\n        int offset = Integer.parseInt(domField.substring(2));\n        if (offset < 0 || offset > MAX_LAST_DAY_OFFSET) {\n            return \"L-offset must be 0..\" + MAX_LAST_DAY_OFFSET + \", got \" + offset;\n        }\n    } catch (NumberFormatException ex) {\n        return \"L-offset must be an integer: \" + domField;\n    }\n    return null;\n}","typeGuard":"public static boolean isValidLastDayOffsetToken(String token) {\n    if (token == null) return false;\n    if (token.equals(\"L\") || token.matches(\"LW?\") || token.matches(\"LW\")) return true;\n    java.util.regex.Matcher m = java.util.regex.Pattern.compile(\"^L-(\\\\d+)W?$\").matcher(token);\n    if (!m.matches()) return false;\n    int off = Integer.parseInt(m.group(1));\n    return off >= 0 && off <= 30;\n}","tryCatchPattern":"try {\n    new CronExpression(expr);\n} catch (ParseException e) {\n    if (e.getMessage().startsWith(\"Offset from last day\")) {\n        return \"The L-offset must be <= 30. Use an explicit day number for earlier dates. \" + e.getMessage();\n    }\n    throw e;\n}","preventionTips":["Clamp L-offsets to 0-30 when generating 'L-N' tokens.","Prefer explicit day numbers for dates more than 30 days before month end.","Unit-test generated L-offset expressions against the parser."],"tags":["cron","xxl-job","last-modifier","day-of-month","validation","parse-error"],"backgroundTag":null,"analyzedSha":"96fb33f5ec68516da0b0147da06b2eb0419e063a","analyzedAt":"2026-08-14T00:04:16.786Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}