jmrozanec / jmrozanec/cron-utils
nW in day of month throws DateTimeException when the month is shorter than n
- Dominant language
- Java
- Stars
- 1.2k
- Forks
- 272
- PR merge metrics
- No merged PRs in 30d
Description
`nW` (nearest weekday to the nth of the month) throws `java.time.DateTimeException` instead of skipping months that have fewer than `n` days. Affects `QUARTZ` and `SPRING53` (any definition with `supportsW()` on day of month).
### Reproduction
```java
CronParser parser = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ));
ExecutionTime et = ExecutionTime.forCron(parser.parse("0 0 0 31W * ?"));
et.nextExecution(ZonedDateTime.parse("2025-06-01T00:00:00Z"));
```
### Actual
```
java.time.DateTimeException: Invalid date 'JUNE 31'
```
Same for `SPRING53`. `30W` evaluated from a 30-day month works fine, so the failure is specifically "requested day exceeds the length of the candidate month".
### Expected
Quartz skips such months. Reference behaviour from `org.quartz.CronExpression` 2.5.0, same expression and start instant:
```
0 0 0 31W * ? -> 2025-07-31, 2025-08-29, 2025-10-31, 2025-12-31, 2026-01-30
```
June, September and November (30 days) and February are skipped; `2025-08-29` and `2026-01-30` show the weekday adjustment applied after the month is accepted.
### Cause
`OnDayOfMonthValueGenerator.generateValue`, `case W:` constructs the date before checking the month length:
```java
case W: // First work day of the week
final LocalDate doM = LocalDate.of(year, month, dayOfMonth);
```
`LocalDate.of` throws for `dayOfMonth > lengthOfMonth`. The `case L:` branch above it reads `lengthOfMonth()` first and is unaffected. A guard that reports no value for the month (so the generator moves to the next one) would line up with the reference behaviour.
Version: reproduced on `master` (`bac6e86`).
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in OnDayOfMonthValueGenerator.generateValue, focusing on case W and comparing it with the case L branch. Reproduce the Quartz example for 31W and inspect the relevant generator tests or add regression coverage. Done means shorter candidate months are skipped and the listed Quartz dates are produced for both QUARTZ and SPRING53 without a DateTimeException.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100