Repository: jmrozanec/cron-utils
Pull request #119 (2016-08-31 22:15:47)

Issue 117 possible fix
I don't fully understand the code, but with this fix, all tests are now passing.  I suspect maybe there was confusion of ZonedDateTime being 1 indexed vs 0..i don't know..


Patch:
diff --git a/src/main/java/com/cronutils/model/time/ExecutionTime.java b/src/main/java/com/cronutils/model/time/ExecutionTime.java
index 75ad621..1efe74d 100644
--- a/src/main/java/com/cronutils/model/time/ExecutionTime.java
+++ b/src/main/java/com/cronutils/model/time/ExecutionTime.java
@@ -167,7 +167,7 @@ public class ExecutionTime {
             days = generateDays(cronDefinition, ZonedDateTime.of(LocalDateTime.of(date.getYear(), nextMonths, 1, 0, 0), date.getZone()));
             return initDateTime(date.getYear(), nextMonths, days.getValues().get(0), lowestHour, lowestMinute, lowestSecond, date.getZone());
         }
-        days = generateDays(cronDefinition, date);
+        days = generateDays(cronDefinition, date);       
         if(!days.getValues().contains(date.getDayOfMonth())) {
             nearestValue = days.getNextValue(date.getDayOfMonth(), 0);
             if(nearestValue.getShifts()>0){
@@ -320,10 +320,9 @@ public class ExecutionTime {
         if(questionMarkSupported){
             return new TimeNode(
                     generateDayCandidatesQuestionMarkSupported(
-                            date.getYear(), date.getMonthValue(),
-                            ((DayOfWeekFieldDefinition)
-                                    cronDefinition.getFieldDefinition(DAY_OF_WEEK)
-                            ).getMondayDoWValue()
+                            date.getYear(), 
+                            date.getMonthValue(),
+                            ((DayOfWeekFieldDefinition)cronDefinition.getFieldDefinition(DAY_OF_WEEK)).getMondayDoWValue()
                     )
             );
         }else{
@@ -414,10 +413,12 @@ public class ExecutionTime {
         if (daysOfMonthCronField.getExpression() instanceof Always && daysOfWeekCronField.getExpression() instanceof Always) {
             candidates.addAll(createDayOfMonthValueGeneratorInstance(daysOfMonthCronField, year, month)
                     .generateCandidates(1, lengthOfMonth));
-        } else if (daysOfMonthCronField.getExpression() instanceof QuestionMark) {
-            // the day of week calculator must get a -1 value to indicate its generating the first value of the month
+        } else if (daysOfMonthCronField.getExpression() instanceof QuestionMark) {            
+        	// ZoneDateTime is 1-End of Month, candidate gen is 'exclusive'...0- Length+1 is the proper range? 
             candidates.addAll(createDayOfWeekValueGeneratorInstance(daysOfWeekCronField, year, month, mondayDoWValue)
-                    .generateCandidates(-1, lengthOfMonth));
+                    .generateCandidates(0, lengthOfMonth+1));
+            
+            
         } else if (daysOfWeekCronField.getExpression() instanceof QuestionMark) {
             candidates.addAll(createDayOfMonthValueGeneratorInstance(daysOfMonthCronField, year, month)
                     .generateCandidates(1, lengthOfMonth));


Test patch:
diff --git a/src/test/java/com/cronutils/model/time/ExecutionTimeQuartzIntegrationTest.java b/src/test/java/com/cronutils/model/time/ExecutionTimeQuartzIntegrationTest.java
index c568011..29b0127 100644
--- a/src/test/java/com/cronutils/model/time/ExecutionTimeQuartzIntegrationTest.java
+++ b/src/test/java/com/cronutils/model/time/ExecutionTimeQuartzIntegrationTest.java
@@ -383,7 +383,27 @@ public class ExecutionTimeQuartzIntegrationTest {
         assertEquals(getMinimumInterval("0 0 */3 * * ?"), Duration.ofSeconds(10800));
         assertEquals(getMinimumInterval("0 0 0 * * ?"), Duration.ofSeconds(86400));
     }
+    
+    @Test
+    public void noSpecificDayOfMonth() {
+        Cron cron = parser.parse("0 * * ? * *");
+        ExecutionTime executionTime = ExecutionTime.forCron(cron);
+        ZonedDateTime now = ZonedDateTime.of(2016, 8, 30, 23, 59, 0,0,ZoneId.of("UTC"));
+        ZonedDateTime nextRun = executionTime.nextExecution(now);
 
+        assertEquals(ZonedDateTime.of(2016, 8, 31, 0, 0, 0,0, ZoneId.of("UTC")), nextRun);
+    }
+    
+    @Test
+    public void noSpecificDayOfMonthEvaluatedOnLastDay() {
+    	Cron cron = parser.parse("0 * * ? * *");
+        ExecutionTime executionTime = ExecutionTime.forCron(cron);
+        ZonedDateTime now = ZonedDateTime.of(2016, 8, 31, 10, 10, 0,0,ZoneId.of("UTC"));
+        ZonedDateTime nextRun = executionTime.nextExecution(now);
+
+        assertEquals(ZonedDateTime.of(2016, 8, 31, 10, 11, 0, 0, ZoneId.of("UTC")), nextRun);
+    }
+    
     private Duration getMinimumInterval(String quartzPattern) {
         ExecutionTime et = ExecutionTime.forCron(parser.parse(quartzPattern));
         ZonedDateTime coolDay = ZonedDateTime.of(2016, 1, 1, 0, 0, 0, 0, UTC);