Skip to content

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

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)
    }
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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