LastExecutionWithDifferentMonthLengthsTest.java
package com.cronutils.model.time;
import com.cronutils.model.CronType;
import com.cronutils.model.definition.CronDefinitionBuilder;
import com.cronutils.parser.CronParser;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
import static java.time.format.DateTimeFormatter.ISO_INSTANT;
public class LastExecutionWithDifferentMonthLengthsTest {
@Test
void shouldFindCorrectLastExecTimeWhenDaysOfMonthsAreDifferent() {
final var parser = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ));
// last run is in a month with 31 days
final var execTime = ExecutionTime.forCron(parser.parse("0 30 23 * * ? 2022"));
final var lastExec = execTime.lastExecution(
Instant.parse("2023-11-24T12:00:00Z").atZone(ZoneId.of("UTC")));
assertTrue(lastExec.isPresent(), "Should find a last execution");
assertEquals(
"2022-12-31T23:30:00Z",
ISO_INSTANT.format(lastExec.get().toInstant()),
"Last execution should be the last possible time in 2022"
);
final var nextExec = execTime.nextExecution(lastExec.get());
assertTrue(nextExec.isEmpty(), "Should not find next execution as cron ended in 2022");
}
@Test
void shouldHandleLastDayOfMonthTransitions() {
final var parser = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ));
// Run at 23:30 on the last day of every month in 2022
final var execTime = ExecutionTime.forCron(parser.parse("0 30 23 L * ? 2022"));
// Check from a month with 30 days (November)
final var novemberCheck = execTime.lastExecution(
Instant.parse("2023-11-24T12:00:00Z").atZone(ZoneId.of("UTC")));
assertTrue(novemberCheck.isPresent(), "Should find a last execution from November");
assertEquals(
"2022-12-31T23:30:00Z",
ISO_INSTANT.format(novemberCheck.get().toInstant()),
"Last execution should be December 31, 2022"
);
// Check from a month with 31 days (October)
final var octoberCheck = execTime.lastExecution(
Instant.parse("2023-10-24T12:00:00Z").atZone(ZoneId.of("UTC")));
assertTrue(octoberCheck.isPresent(), "Should find a last execution from October");
assertEquals(
"2022-12-31T23:30:00Z",
ISO_INSTANT.format(octoberCheck.get().toInstant()),
"Last execution should be December 31, 2022"
);
}
@Test
void shouldHandleSpecificDayAcrossMonths() {
final var parser = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ));
// Run at 23:30 on the 31st of every month in 2022
final var execTime = ExecutionTime.forCron(parser.parse("0 30 23 31 * ? 2022"));
// Test cases for different months
Stream.of(
Arguments.of("2023-11-24T12:00:00Z", "2022-12-31T23:30:00Z"),
Arguments.of("2023-10-24T12:00:00Z", "2022-12-31T23:30:00Z"),
Arguments.of("2023-09-24T12:00:00Z", "2022-12-31T23:30:00Z")
).forEach(args -> {
final var checkTime = Instant.parse((String) args.get()[0]).atZone(ZoneId.of("UTC"));
final var expectedLastExec = (String) args.get()[1];
final var lastExec = execTime.lastExecution(checkTime);
assertTrue(lastExec.isPresent(), "Should find last execution when checking from " + checkTime);
assertEquals(
expectedLastExec,
ISO_INSTANT.format(lastExec.get().toInstant()),
"Last execution should be correct when checking from " + checkTime
);
final var nextExec = execTime.nextExecution(lastExec.get());
assertTrue(nextExec.isEmpty(), "Should not find next execution after " + lastExec.get());
});
}
}