Skip to content

Bug: ExecutionTime incorrectly skips month intervals e.g. "0 0 0 15 10/3 ? *" #692

@lindsaymf

Description

@lindsaymf

I'm using this in my Android Kotlin app and I noticed this bug.

Based on the javadoc comment describing the Everyfield expression, I expected this to work. I tried with a custom built cron using Every as described, but I got the same result.

Library version used: 9.2.1

Expectation: should return next execution date correctly. Should return execution dates for date range correctly.

Actual: only returns execution dates for the starting month each year.

import com.cronutils.model.CronType
import com.cronutils.model.definition.CronDefinitionBuilder
import com.cronutils.model.time.ExecutionTime
import com.cronutils.parser.CronParser
import org.junit.Assert.assertEquals
import org.junit.Test
import java.time.LocalDate
import java.time.LocalTime
import java.time.ZoneId
import java.time.ZonedDateTime
import kotlin.jvm.optionals.getOrNull

class MonthlyCronTest {

    private val cronParser = CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ))

    private fun LocalDate.toZoned(): ZonedDateTime =
        ZonedDateTime.of(this, LocalTime.MIDNIGHT, ZoneId.systemDefault())

    @Test
    fun next_execution_for_month_interval() {
        // Cron expression for "every 3 months starting in October"
        val cron = cronParser.parse("0 0 0 * 10/3 ? *")
        val executionTime = ExecutionTime.forCron(cron)

        // Reference date: February 1, 2026
        val referenceDate = LocalDate.of(2026, 2, 1).toZoned()

        // Expected result: April 1, 2026
        val expected = LocalDate.of(2026, 4, 1)

        // Actual result: October 1, 2026
        val actual = executionTime.nextExecution(referenceDate).getOrNull()?.toLocalDate()

        // This assertion fails
        assertEquals(expected, actual)
    }

    @Test
    fun next_execution_for_month_interval_with_specific_day_of_month() {
        // Cron expression for "15th of the month, every 3 months starting in October"
        val cron = cronParser.parse("0 0 0 15 10/3 ? *")
        val executionTime = ExecutionTime.forCron(cron)

        // Reference date: January 1, 2026
        val referenceDate = LocalDate.of(2026, 1, 1).toZoned()

        // Expected result: January 15, 2026
        val expected = LocalDate.of(2026, 1, 15)

        // Actual result: October 15, 2026
        val actual = executionTime.nextExecution(referenceDate).getOrNull()?.toLocalDate()

        // This assertion fails
        assertEquals(expected, actual)
    }

    @Test
    fun execution_dates_for_month_interval() {
        // Cron expression for "15th of the month, every 3 months starting in October"
        val cron = cronParser.parse("0 0 0 15 10/3 ? *")
        val executionTime = ExecutionTime.forCron(cron)

        // Date range of Oct 1, 2025 to Oct 31, 2026
        val startDate = LocalDate.of(2025, 10, 1).toZoned()
        val endDate = LocalDate.of(2026, 10, 31).toZoned()

        // Expected result:
        // - Oct 15, 2025
        // - Jan 15, 2026
        // - Apr 15, 2026
        // - Jul 15, 2026
        // - Oct 15, 2026
        val expected = listOf(
            LocalDate.of(2025, 10, 15).toZoned(),
            LocalDate.of(2026, 1, 15).toZoned(),
            LocalDate.of(2026, 4, 15).toZoned(),
            LocalDate.of(2026, 7, 15).toZoned(),
            LocalDate.of(2026, 10, 15).toZoned()
        )

        // Actual result:
        // - Oct 15, 2025
        // - Oct 15, 2026
        val actual = executionTime.getExecutionDates(startDate, endDate)

        // This assertion fails
        assertEquals(expected, actual)
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions