Cron expressions with a quartz specific dayOfWeek part like 0 30 17 ? * 2#3 are not parsed correctly by Spring's CronExpression:

2#3 means 'third Monday every month' but CronExpression parses this as "third tuesday in month".

It looks like the parsing logic in QuartzCronField#parseDayOfWeek is not correct:

    private static DayOfWeek parseDayOfWeek(String value) {
        int dayOfWeek = Integer.parseInt(value);
        if (dayOfWeek == 0) {
            dayOfWeek = 7; // cron is 0 based; java.time 1 based
        }
        try {
            return DayOfWeek.of(dayOfWeek);
        }...
    }

This methods transforms the input-value (cron/quartz dayOfWeek) into a java.time dayOfWeek index, then returning the corresponding java.time.DayOfWeek

The problem is, that cron, quartz and java.time have different dayOfTime indices. cron and quartz use same indices for su - fr, but Saturday differs. java.time is 1 based, beginning with Monday:

    cron quartz    java.time
sa    0    7         6
su    1    1         7
mo    2    2         1
tu    3    3         2
...

So the input-value has to be decremented by 1 and the overflow handled, like this:

        // Offset cron/quartz => java.time:
        dayOfWeek -= 1;

        if (dayOfWeek < 1) {
            dayOfWeek += 7;
        }

Tests:

    @Test
    public void testDayOfWeek_7() {
        // every third saturday in month.
        QuartzCronField quartzCronField = QuartzCronField.parseDaysOfWeek("7#3");
        LocalDateTime next = quartzCronField.nextOrSame(LocalDateTime.parse("2024-01-01T17:00:00"));

        assertThat(next.getDayOfMonth()).isEqualTo(20);  // expected: 2024-10-20, saturday

    }
    @Test
    public void testDayOfWeek_0(){
        // 0 is not a valid dayOfWeek for quartz, but for cron. To be tolerant for cron dayOfWeek, we handle 0 as saturday.
        QuartzCronField quartzCronField = QuartzCronField.parseDaysOfWeek("0#3");
        LocalDateTime next = quartzCronField.nextOrSame(LocalDateTime.parse("2024-01-01T17:00:00"));

        assertThat(next.getDayOfMonth()).isEqualTo(20);  // expected: 2024-10-20, saturday
    }

    @Test
    public void testDayOfWeek_1(){
        // every third sunday in month.
        QuartzCronField quartzCronField = QuartzCronField.parseDaysOfWeek("1#3");
        LocalDateTime next = quartzCronField.nextOrSame(LocalDateTime.parse("2024-01-01T17:00:00"));

        assertThat(next.getDayOfMonth()).isEqualTo(21);  // expected: 2024-10-21, sunday
    }

    @Test
    public void testDayOfWeek_2(){
        // every third monday in month.
        QuartzCronField quartzCronField = QuartzCronField.parseDaysOfWeek("2#3");
        LocalDateTime next = quartzCronField.nextOrSame(LocalDateTime.parse("2024-01-01T17:00:00"));

        assertThat(next.getDayOfMonth()).isEqualTo(15);  // expected: 2024-10-15, monday

    }

Comment From: jhoeller

On review, the inconsistency seems to be between Cron and Quartz: https://github.com/quartz-scheduler/quartz/issues/524 https://stackoverflow.com/questions/18919151/crontab-day-of-the-week-syntax

We currently parse the day of the week according to common Cron rules which Quartz seems to disagree with. Since we have been doing this for a number of years already, it'll be hard to change this, even if this is arguably Quartz-specific syntax to begin with... so we might have to turn this into a documentation issue.