Repository: jmrozanec/cron-utils (java)

Issue:
Incorrect next execution time for spring cron expression with day of week
version 9.0.1
With the cron expression like 0 0 9 * * MON 
Where it is assumed to be fired on 9 o'clock of monday ,but executionTime.nextExecution(now) always return 9 o'clock of the next day,whether the next day is monday or not.

CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(SPRING);
CronParser parser = new CronParser(cronDefinition);
ExecutionTime executionTime = ExecutionTime.forCron(parser.parse("0 0 9 * * MON"));
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime nextExecution = executionTime.nextExecution(now).get();


Pull request:
When generating day candidates, take the intersection of days generated from DoW and DoM instead of union
Fixes #394 
All possible days of the month was being returned.

Patch:
diff --git a/src/main/java/com/cronutils/model/time/SingleExecutionTime.java b/src/main/java/com/cronutils/model/time/SingleExecutionTime.java
index 4b1fa75..2d2eaed 100755
--- a/src/main/java/com/cronutils/model/time/SingleExecutionTime.java
+++ b/src/main/java/com/cronutils/model/time/SingleExecutionTime.java
@@ -691,10 +691,11 @@ public class SingleExecutionTime implements ExecutionTime {
                     .stream().distinct().sorted()
                     .collect(Collectors.toList());
         } else {
-            return Stream.concat(
-                    createDayOfWeekValueGeneratorInstance(daysOfWeekCronField, year, month, mondayDoWValue).generateCandidates(1, lengthOfMonth).stream(),
-                    createDayOfMonthValueGeneratorInstance(daysOfMonthCronField, year, month).generateCandidates(1, lengthOfMonth).stream()
-            ).distinct().sorted().collect(Collectors.toList());
+            Set<Integer> candidates = new HashSet<>(createDayOfMonthValueGeneratorInstance(daysOfMonthCronField, year, month).generateCandidates(1, lengthOfMonth));
+            Set<Integer> daysOfWeek = new HashSet<>(createDayOfWeekValueGeneratorInstance(daysOfWeekCronField, year, month, mondayDoWValue).generateCandidates(1, lengthOfMonth));
+            // Only the intersection of valid days from the days of week and valid days of month should be returned.
+            candidates.retainAll(daysOfWeek);
+            return candidates.stream().sorted().collect(Collectors.toList());
         }
     }
 


Test patch:
diff --git a/src/test/java/com/cronutils/Issue394Test.java b/src/test/java/com/cronutils/Issue394Test.java
new file mode 100644
index 0000000..25f2102
--- /dev/null
+++ b/src/test/java/com/cronutils/Issue394Test.java
@@ -0,0 +1,40 @@
+package com.cronutils;
+
+import com.cronutils.model.definition.CronDefinition;
+import com.cronutils.model.definition.CronDefinitionBuilder;
+import com.cronutils.model.time.ExecutionTime;
+import com.cronutils.parser.CronParser;
+import org.junit.Test;
+
+import java.time.DayOfWeek;
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import static com.cronutils.model.CronType.SPRING;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class Issue394Test {
+
+    @Test
+    public void testEveryMondayAt0900hours() {
+        String cron = "0 0 9 * * MON";
+
+        CronDefinition cronDefinition = CronDefinitionBuilder.instanceDefinitionFor(SPRING);
+        CronParser parser = new CronParser(cronDefinition);
+        ExecutionTime executionTime = ExecutionTime.forCron(parser.parse(cron));
+        ZonedDateTime now = ZonedDateTime.now();
+        Optional<ZonedDateTime> nextExecution = executionTime.nextExecution(now);
+        assertTrue(nextExecution.isPresent());
+        // Should be the next Monday at 0900 hours
+        assertEquals(DayOfWeek.MONDAY, nextExecution.get().getDayOfWeek());
+        assertEquals(9, nextExecution.get().getHour());
+        // The next execution after that should also be a Monday at 0900 hours, the following week
+        ZonedDateTime nextExpectedExecution = nextExecution.get().plusWeeks(1);
+        nextExecution = executionTime.nextExecution(nextExecution.get());
+        assertTrue(nextExecution.isPresent());
+        assertEquals(DayOfWeek.MONDAY, nextExecution.get().getDayOfWeek());
+        assertEquals(9, nextExecution.get().getHour());
+        assertEquals(nextExpectedExecution, nextExecution.get());
+    }
+}