Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/main/java/com/cronutils/model/Cron.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,13 @@ public interface Cron extends Serializable {
* @return boolean - true if equivalent; false otherwise.
*/
boolean equivalent(final Cron cron);

/**
* Checks if this cron expression overlaps with another cron expression.
*
* @param cron - The other cron expression to compare with, never null.
* @return true if the cron expressions overlap, false otherwise.
*/
boolean overlap(final Cron cron);
}

23 changes: 22 additions & 1 deletion src/main/java/com/cronutils/model/SingleCron.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import com.cronutils.model.field.CronFieldName;
import com.cronutils.model.field.expression.visitor.ValidationFieldExpressionVisitor;
import com.cronutils.utils.Preconditions;

import java.time.ZonedDateTime;
import com.cronutils.model.time.ExecutionTime;
import java.util.*;

public class SingleCron implements Cron {
Expand Down Expand Up @@ -109,4 +110,24 @@ public boolean equivalent(final CronMapper cronMapper, final Cron cron) {
public boolean equivalent(final Cron cron) {
return asString().equals(cron.asString());
}

@Override
public boolean overlap(final Cron cron) {
Preconditions.checkNotNull(cron, "Cron must not be null");

ExecutionTime thisExecutionTime = ExecutionTime.forCron(this);
ExecutionTime otherExecutionTime = ExecutionTime.forCron(cron);

ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime oneYearFromNow = now.plusYears(1);

List<ZonedDateTime> thisExecutionDates = thisExecutionTime.getExecutionDates(now, oneYearFromNow);

for (ZonedDateTime executionDate : thisExecutionDates) {
if (otherExecutionTime.isMatch(executionDate)) {
return true;
}
}
return false;
}
}
129 changes: 129 additions & 0 deletions src/test/java/com/cronutils/model/SingleCronTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.cronutils.model;

import com.cronutils.model.definition.CronDefinition;
import com.cronutils.model.definition.CronDefinitionBuilder;
import com.cronutils.parser.CronParser;
import com.cronutils.model.CronType;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Disabled; // Will use if a test needs specific time mocking not available

import java.time.ZonedDateTime;
import java.time.ZoneId; // For ZonedDateTime construction

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;

public class SingleCronTest {

private CronParser cronParser;

@BeforeEach
public void setUp() {
// Using QUARTZ definition for 7 fields (includes year)
CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ);
cronParser = new CronParser(cronDefinition);
}

@Test
public void testOverlapFromIssueDescription() {
// cron1 = "0 0 10 1/1 ? *" (every day at 10am) - This is not a QUARTZ expression (6 fields)
// cron2 = "0 0 10 ? * MON *" (every Monday at 10am) - QUARTZ
// Let's make cron1 QUARTZ compatible: "0 0 10 1/1 * ? *"
Cron cron1 = cronParser.parse("0 0 10 1/1 * ? *"); // Every day at 10 AM
Cron cron2 = cronParser.parse("0 0 10 ? * MON *"); // Every Monday at 10 AM
assertTrue(cron1.overlap(cron2), "Cron1 (every day at 10am) should overlap with Cron2 (every Monday at 10am)");
assertTrue(cron2.overlap(cron1), "Overlap should be symmetric");
}

@Test
public void testNonOverlappingExpressions() {
Cron cron1 = cronParser.parse("0 0 10 ? * MON *"); // Every Monday at 10 AM
Cron cron2 = cronParser.parse("0 0 12 ? * TUE *"); // Every Tuesday at 12 PM
assertFalse(cron1.overlap(cron2), "Cron1 (Monday 10am) should not overlap with Cron2 (Tuesday 12pm)");
assertFalse(cron2.overlap(cron1), "Overlap should be symmetric");
}

@Test
public void testAlwaysOverlappingExpressions() {
Cron cron1 = cronParser.parse("* * * * * ? *"); // Every second
Cron cron2 = cronParser.parse("0 * * * * ? *"); // Every minute at second 0
assertTrue(cron1.overlap(cron2), "Cron1 (every second) should overlap with Cron2 (every minute at second 0)");
assertTrue(cron2.overlap(cron1), "Overlap should be symmetric");
}

@Test
public void testOverlapOnSpecificDate() {
// Ensure test year has Dec 25th as a Friday. 2026 is such a year.
// cron1: Dec 25th, 2026 at 12 PM
Cron cron1 = cronParser.parse("0 0 12 25 12 ? 2026");
// cron2: Every Friday in December 2026 at 12 PM
Cron cron2 = cronParser.parse("0 0 12 ? 12 FRI 2026");
assertTrue(cron1.overlap(cron2), "Cron1 (Dec 25th 2026 12pm) should overlap with Cron2 (Fridays in Dec 2026 at 12pm) as Dec 25, 2026 is a Friday.");
assertTrue(cron2.overlap(cron1), "Overlap should be symmetric");

// Test with a year where Dec 25 is NOT a Friday. 2025 is such a year (Thursday).
Cron cron3 = cronParser.parse("0 0 12 25 12 ? 2025"); // Dec 25th, 2025 at 12 PM
Cron cron4 = cronParser.parse("0 0 12 ? 12 FRI 2025"); // Every Friday in December 2025 at 12 PM
assertFalse(cron3.overlap(cron4), "Cron3 (Dec 25th 2025 12pm) should NOT overlap with Cron4 (Fridays in Dec 2025 at 12pm) as Dec 25, 2025 is a Thursday.");
assertFalse(cron4.overlap(cron3), "Overlap should be symmetric");
}

@Test
public void testSlightlyDifferentExpressionsThatShouldOverlap() {
// Every 5 minutes vs Every 10 minutes
Cron cron1 = cronParser.parse("0 0/5 * * * ? *");
Cron cron2 = cronParser.parse("0 0/10 * * * ? *");
assertTrue(cron1.overlap(cron2), "Every 5 minutes should overlap with every 10 minutes.");
assertTrue(cron2.overlap(cron1), "Overlap should be symmetric");
}

@Test
public void testNonOverlappingSameDayDifferentHours() {
Cron cron1 = cronParser.parse("0 0 10 ? * MON *"); // Monday 10:00 AM
Cron cron2 = cronParser.parse("0 0 11 ? * MON *"); // Monday 11:00 AM
assertFalse(cron1.overlap(cron2), "Monday 10am should not overlap with Monday 11am.");
assertFalse(cron2.overlap(cron1), "Overlap should be symmetric");
}

@Test
public void testOverlappingHourlyAndDaily() {
// Every hour vs Every day at a specific hour
Cron cron1 = cronParser.parse("0 0 * * * ? *"); // Every hour at minute 0
Cron cron2 = cronParser.parse("0 0 12 * * ? *"); // Every day at 12:00 PM
assertTrue(cron1.overlap(cron2), "Every hour should overlap with every day at 12 PM.");
assertTrue(cron2.overlap(cron1), "Overlap should be symmetric");
}

// It might be good to have a test case where the year is not specified in one
// and specified in another, if the cron definition allows optional years.
// For QUARTZ, year is a field. If not specified, it might default or be an error.
// The CronParser with QUARTZ definition expects 7 fields usually.
// Let's try a case where one has a wildcard year and another a specific year.
@Test
public void testOverlapWithWildcardYearAndSpecificYear() {
// cron1: Every Jan 1st 10 AM, any year
Cron cron1 = cronParser.parse("0 0 10 1 1 ? *");
// cron2: Jan 1st, 2027 at 10 AM
Cron cron2 = cronParser.parse("0 0 10 1 1 ? 2027");
assertTrue(cron1.overlap(cron2), "Jan 1st any year should overlap with Jan 1st 2027.");
assertTrue(cron2.overlap(cron1), "Overlap should be symmetric");

// cron3: Every Jan 1st 10 AM, any year
Cron cron3 = cronParser.parse("0 0 10 1 1 ? *");
// cron4: Feb 1st, 2027 at 10 AM
Cron cron4 = cronParser.parse("0 0 10 1 2 ? 2027");
assertFalse(cron3.overlap(cron4), "Jan 1st any year should NOT overlap with Feb 1st 2027.");
assertFalse(cron4.overlap(cron3), "Overlap should be symmetric");
}

// Test for expressions that will never overlap because of year constraints
@Test
public void testNonOverlapDueToYear() {
Cron cron1 = cronParser.parse("0 0 12 1 1 ? 2025"); // Jan 1st, 2025 12:00 PM
Cron cron2 = cronParser.parse("0 0 12 1 1 ? 2026"); // Jan 1st, 2026 12:00 PM
assertFalse(cron1.overlap(cron2), "Expressions for different years should not overlap.");
assertFalse(cron2.overlap(cron1), "Overlap should be symmetric");
}
}
Loading