Issue55UnexpectedExecutionTimes.java

/*
 * Copyright 2015 jmrozanec
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.cronutils;

import com.cronutils.model.Cron;
import com.cronutils.model.definition.CronConstraint;
import com.cronutils.model.definition.CronDefinition;
import com.cronutils.model.definition.CronDefinitionBuilder;
import com.cronutils.model.field.CronFieldName;
import com.cronutils.model.field.expression.QuestionMark;
import com.cronutils.model.time.ExecutionTime;
import com.cronutils.parser.CronParser;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class Issue55UnexpectedExecutionTimes {

    private static final Logger LOGGER = LoggerFactory.getLogger(Issue55UnexpectedExecutionTimes.class);
    private CronDefinition cronDefinition;

    /**
     * Setup.
     */
    @BeforeEach
    public void setUp() {
        cronDefinition = CronDefinitionBuilder.defineCron()
                .withMinutes().and()
                .withHours().and()
                .withDayOfMonth()
                .supportsHash().supportsL().supportsW().supportsQuestionMark().and()
                .withMonth().and()
                .withDayOfWeek()//Monday=1
                .withIntMapping(7, 0) //we support non-standard non-zero-based numbers!
                .supportsHash().supportsL().supportsW().supportsQuestionMark().and()
                .withYear().optional().and()
                .withCronValidation(
                        //both a day-of-week AND a day-of-month parameter should fail for this case; otherwise returned values are correct
                        new CronConstraint("Both, a day-of-week AND a day-of-month parameter, are not supported.") {

                            private static final long serialVersionUID = -5934767434702909825L;

                            @Override
                            public boolean validate(final Cron cron) {
                                if (!(cron.retrieve(CronFieldName.DAY_OF_MONTH).getExpression() instanceof QuestionMark)) {
                                    return cron.retrieve(CronFieldName.DAY_OF_WEEK).getExpression() instanceof QuestionMark;
                                } else {
                                    return !(cron.retrieve(CronFieldName.DAY_OF_WEEK).getExpression() instanceof QuestionMark);
                                }
                            }
                        })
                .instance();
    }

    /**
     * Test.
     */
    @Test
    public void testOnceEveryThreeDaysNoInstantsWithinTwoDays() {
        LOGGER.debug("TEST1 - expecting 0 instants");
        final ZonedDateTime startTime = ZonedDateTime.of(0, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC);
        final ZonedDateTime endTime = startTime.plusDays(2);
        final CronParser parser = new CronParser(cronDefinition);
        final Cron cron = parser.parse("0 0 */3 * ?");
        final ExecutionTime executionTime = ExecutionTime.forCron(cron);
        final List<Instant> instants = getInstants(executionTime, startTime, endTime);
        LOGGER.debug("instants.size() == {}", instants.size());
        LOGGER.debug("instants: {}", instants);
        assertEquals(0, instants.size());
    }

    /**
     * Test.
     */
    @Test
    public void testOnceAMonthTwelveInstantsInYear() {
        LOGGER.debug("TEST2 - expecting 12 instants");
        final ZonedDateTime startTime = ZonedDateTime.of(0, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC);
        final ZonedDateTime endTime = startTime.plusYears(1);
        final CronParser parser = new CronParser(cronDefinition);
        final Cron cron = parser.parse("0 12 L * ?");
        final ExecutionTime executionTime = ExecutionTime.forCron(cron);
        final List<Instant> instants = getInstants(executionTime, startTime, endTime);
        LOGGER.debug("instants.size() == {}", instants.size());
        LOGGER.debug("instants: {}", instants);
        assertEquals(12, instants.size());
    }

    private List<Instant> getInstants(final ExecutionTime executionTime, final ZonedDateTime startTime, final ZonedDateTime endTime) {
        final List<Instant> instantList = new ArrayList<>();
        final Optional<ZonedDateTime> startTimeExecution = executionTime.nextExecution(startTime);
        if (startTimeExecution.isPresent()) {
            ZonedDateTime next = startTimeExecution.get();
            while (next.isBefore(endTime)) {
                final Optional<ZonedDateTime> nextExecution = executionTime.nextExecution(next);
                instantList.add(next.toInstant());
                if (nextExecution.isPresent()) {
                    next = nextExecution.get();
                } else {
                    throw new NullPointerException("next execution is not present");
                }
            }
            return instantList;
        } else {
            throw new NullPointerException("starttime execution was not present");
        }
    }

}