jmrozanec / jmrozanec/cron-utils
Bug: ExecutionTime incorrectly skips month intervals e.g. "0 0 0 15 10/3 ? *"
- Dominant language
- Java
- Stars
- 1.2k
- Forks
- 272
- PR merge metrics
- No merged PRs in 30d
Description
I'm using this in my Android Kotlin app and I noticed this bug.
Based on the javadoc comment describing the [Every](https://github.com/jmrozanec/cron-utils/blob/6b7b06abf21ea9c8196672770731c42828f96cd9/src/main/java/com/cronutils/model/field/expression/Every.java#L24)field 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)
}
}
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.