jmrozanec / cron-utils

Cron utils for parsing, validations and human readable descriptions as well as date/time interoperability.
http://cron-utils.com
Apache License 2.0
1.15k stars 262 forks source link

[potential bug] ExecutionTime.nextExecution returns Empty When time is beyond 2099 years #538

Closed Chazzzzzzz closed 2 years ago

Chazzzzzzz commented 2 years ago

Hey guys, we find an issue in latest cron-utils release: ExecutionTime.nextExecution will return Empty when time is beyond 2099 years. The following is my test code

In the test class, I use 12/31/2099 as the current time and try to find next execution time after one month. The result I get is empty. Please check and let me know if I am doing a wrong way or its a bug of the library.

class app `

import static com.cronutils.model.CronType.QUARTZ; import static com.cronutils.model.definition.CronDefinitionBuilder.instanceDefinitionFor;

import java.time.ZonedDateTime; import java.util.Optional;

import com.cronutils.model.Cron; import com.cronutils.model.time.ExecutionTime; import com.cronutils.parser.CronParser;

public class app { private static final String CRON_START = "cron("; private static final int CRON_OFFSET = CRON_START.length(); private static final CronParser CRON_PARSER = new CronParser(instanceDefinitionFor(QUARTZ));

private static Cron getCronExpression(String scheduleExpression, String scheduleName) throws Exception {
    // This method will parse cron("EXPRESSION") and return quartz cron.
    StringBuilder cronExpression = new StringBuilder();

    // Adding 0 because Quartz supports seconds fields.
    cronExpression.append("0 ");
    cronExpression.append(scheduleExpression.substring(CRON_OFFSET, scheduleExpression.length() - 1));
    try {
        Cron quartzExpression =  CRON_PARSER.parse(cronExpression.toString());
        quartzExpression.validate();
        return quartzExpression;
    }
    catch (Exception e) {
        throw e;
    }
}

public static Optional<ZonedDateTime> getNextNthExecutionTime(int n, ZonedDateTime startTime,
    String scheduleExpression, String scheduleName) throws Exception {
    Cron cronExpression = getCronExpression(scheduleExpression, scheduleName);

    ExecutionTime executionTime = ExecutionTime.forCron(cronExpression);

    Optional<ZonedDateTime> nextRun = Optional.of(startTime);
    for (int i=0; i<n; i++) {
        nextRun = executionTime.nextExecution(nextRun.get());
        if (!nextRun.isPresent()) {
            throw new RuntimeException("failed");
        }
    }

    return nextRun;
}

} `

test class `

import static org.junit.Assert.assertEquals;

import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Optional;

import org.junit.Test;

public class test_app {

@Test
public void test() throws Exception {
    ZonedDateTime currentRun = ZonedDateTime.of(2099,12,31,9,
        0,0,0, ZoneId.of("America/Los_Angeles"));
    Optional<ZonedDateTime> targetRun = app.getNextNthExecutionTime(1, currentRun,
        "cron(0 9 L 1/2 ? *)", "Sched1");
    ZonedDateTime expectedRun = ZonedDateTime.of(2100,2,29,9,
        0,0,0, ZoneId.of("America/Los_Angeles"));
    assertEquals("Next Nth execution date doesn't match", expectedRun, targetRun.get());
}

} `

jmrozanec commented 2 years ago

@Chazzzzzzz, thank you for rising this question and example. The Quartz cron definition requires us to consider only intervals up to 2099. Please see this web page documenting the constraint. We are closing this issue as the cron parser is behaving as expected.