Skip to content

Document cron-vs-quartz parsing convention for dayOfWeek part in CronExpression #32128

Closed
@tvahrst

Description

@tvahrst

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

	}

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)status: backportedAn issue that has been backported to maintenance branchestype: documentationA documentation task

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions