Repository: jmrozanec/cron-utils
Pull request #294 (2017-12-02 02:42:28)

Issue 293
When going backwards in time to find last execution time, and crossing a month boundary, continue at the last moment at the end of the prior month, rather than using the highest day of the month found in the current month.


Patch:
diff --git a/src/main/java/com/cronutils/model/time/ExecutionTime.java b/src/main/java/com/cronutils/model/time/ExecutionTime.java
index 69265a5..8b40587 100755
--- a/src/main/java/com/cronutils/model/time/ExecutionTime.java
+++ b/src/main/java/com/cronutils/model/time/ExecutionTime.java
@@ -399,8 +399,11 @@ public class ExecutionTime {
             ).minusYears(nearestValue.getShifts());
             return new ExecutionTimeResult(newDate, false);
         }
-        return new ExecutionTimeResult(date.withMonth(previousMonths).withDayOfMonth(highestDay)
-                .with(LocalTime.of(highestHour, highestMinute, highestSecond)), false);
+        else {
+            newDate = ZonedDateTime.of(date.getYear(), date.getMonthValue(), 1, 0, 0, 0, 0, date.getZone()).minusNanos(1);
+
+            return new ExecutionTimeResult(newDate, false);
+        }
     }
 
     private ExecutionTimeResult getPreviousPotentialDayOfMonth(final ZonedDateTime date, final TimeNode days, final int highestHour, final int highestMinute,


Test patch:
diff --git a/src/test/java/com/cronutils/Issue293Test.java b/src/test/java/com/cronutils/Issue293Test.java
new file mode 100644
index 0000000..404e267
--- /dev/null
+++ b/src/test/java/com/cronutils/Issue293Test.java
@@ -0,0 +1,64 @@
+package com.cronutils;
+
+import java.time.DayOfWeek;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import com.cronutils.model.Cron;
+import com.cronutils.model.CronType;
+import com.cronutils.model.definition.CronDefinition;
+import com.cronutils.model.definition.CronDefinitionBuilder;
+import com.cronutils.model.time.ExecutionTime;
+import com.cronutils.parser.CronParser;
+
+import static org.junit.Assert.assertEquals;
+
+// https://github.com/jmrozanec/cron-utils/issues/293
+@RunWith(Parameterized.class)
+public class Issue293Test {
+    private static final ZoneId ZONE = ZoneId.systemDefault();
+
+    private final String cronText;
+
+    /**
+     * Each test is a cron spec excluding the reference month (December)
+     * @return unix cron
+     */
+    @Parameterized.Parameters(name = "{0}")
+    public static String[] data() {
+        return new String[] {
+            "15 18 * 1-11 *",       // DateTimeException - Invalid date - Nov 31
+            "15 18 * 1-11 0-5",     // DateTimeException - Invalid date - Nov 31
+            "15 18 * 1-11 4-5",     // Actual is 11/24
+            "15 18 * 1-11 1-5"      // Actual is 11/29
+        };
+    }
+
+    public Issue293Test(String cronText) {
+        this.cronText = cronText;
+    }
+
+    @Test
+    public void test() {
+        CronDefinition def = CronDefinitionBuilder.instanceDefinitionFor(CronType.UNIX);
+        CronParser parser = new CronParser(def);
+
+        Cron cron = parser.parse(cronText);
+        ExecutionTime et = ExecutionTime.forCron(cron);
+
+        ZonedDateTime vs = ZonedDateTime.of(2017, 12, 1, 9, 30, 0, 0, ZONE);
+        assertEquals(DayOfWeek.FRIDAY, vs.getDayOfWeek());
+
+        // Last match prior to our reference time
+        ZonedDateTime expected = ZonedDateTime.of(2017, 11, 30, 18, 15, 0, 0, ZONE);
+        assertEquals(DayOfWeek.THURSDAY, expected.getDayOfWeek());
+
+        ZonedDateTime actual = et.lastExecution(vs).get();
+
+        assertEquals(expected, actual);
+    }
+}