From 9b1737acf001792cf58ba1019ad29bf6cbdbbecc Mon Sep 17 00:00:00 2001 From: Ziver Koc Date: Tue, 28 Feb 2017 18:11:13 +0100 Subject: [PATCH] Impl better cron algo and added mor TCs --- src/zutil/CronTimer.java | 197 +++++++++++++++++++++++----------- test/zutil/CronTimerTest.java | 73 ++++++++++++- 2 files changed, 205 insertions(+), 65 deletions(-) diff --git a/src/zutil/CronTimer.java b/src/zutil/CronTimer.java index 1bb8d79..a08f580 100755 --- a/src/zutil/CronTimer.java +++ b/src/zutil/CronTimer.java @@ -7,8 +7,37 @@ import java.util.*; /** * This is a utility class that will generate timestamps from a Cron formatted String. + *
+ * A cron string consists of 5 to 6 sections separated by a space: + *
    + *
  1. Minute in Hour
  2. + *
  3. Hour in a Day
  4. + *
  5. Day of Month
  6. + *
  7. Month
  8. + *
  9. Day of Week
  10. + *
  11. Year
  12. + *
+ * Each section is defined by a number or by special characters: + * + *
+ * Examples (from Cron Format Specification): + * + *
+ * Note that this class will only calculate the next cron up to 50 years in the future ant not more. * - * @see Cron Format Specification + * @see Cron Format Specification + * @see Cron calculator + * @see Stackoverflow implementation reference */ public class CronTimer implements Iterator, Iterable{ @@ -84,10 +113,15 @@ public class CronTimer implements Iterator, Iterable{ if (rangeArr.length == 2) { int rangeFrom = Integer.parseInt(rangeArr[0]); int rangeTo = Integer.parseInt(rangeArr[1]); + if (from > rangeFrom || rangeTo > to) + throw new IllegalArgumentException("Invalid range "+rangeFrom+"-"+rangeTo+" must be between: "+from+"-"+to); for (int i = rangeFrom; i <= rangeTo; ++i) list.add(i); } else { - list.add(Integer.parseInt(str)); + int value = Integer.parseInt(str); + if (from > value || value > to) + throw new IllegalArgumentException("Valid values are between "+from+"-"+to+" but got: "+value); + list.add(value); } } } @@ -125,73 +159,116 @@ public class CronTimer implements Iterator, Iterable{ public Long next(long fromTimestamp) { Calendar cal = getCalendar(fromTimestamp); - int minute = cal.get(Calendar.MINUTE); - int index = Arrays.binarySearch(minutes, minute); - if (index < 0){ // not found in array - if (Math.abs(index) > minutes.length) { - cal.set(Calendar.MINUTE, minutes[0]); - cal.add(Calendar.HOUR_OF_DAY, 1); - } else - cal.set(Calendar.MINUTE, minutes[Math.abs(index+1)]); - } + while (true) { + int index; - int hour = cal.get(Calendar.HOUR_OF_DAY); - index = Arrays.binarySearch(hours, hour); - if (index < 0){ // not found in array - if (Math.abs(index) > hours.length) { - cal.set(Calendar.HOUR_OF_DAY, hours[0]); - cal.add(Calendar.DAY_OF_MONTH, 1); - } else - cal.set(Calendar.HOUR_OF_DAY, hours[Math.abs(index+1)]); - } + int year = cal.get(Calendar.YEAR); + index = Arrays.binarySearch(years, year); + if (index < 0) { // index not found in array + if (Math.abs(index) > years.length) + return -1L; // We have reach the limit no more years left + else + cal.set(Calendar.YEAR, years[Math.abs(index + 1)]); + continue; + } - int day = cal.get(Calendar.DAY_OF_MONTH); - index = Arrays.binarySearch(dayOfMonths, day); - if (index < 0){ // index not found in array - // check if month have that many days in it - if (Math.abs(index) > dayOfMonths.length || - dayOfMonths[Math.abs(index+1)] > cal.getActualMaximum(Calendar.DAY_OF_MONTH)) { - cal.set(Calendar.DAY_OF_MONTH, dayOfMonths[0]); - cal.add(Calendar.MONTH, 1); - } else - cal.set(Calendar.DAY_OF_MONTH, dayOfMonths[Math.abs(index+1)]); - } + int month = cal.get(Calendar.MONTH); // month ids are between 0-11 :( + index = Arrays.binarySearch(months, month + 1); + if (index < 0) { // index not found in array + if (Math.abs(index) > months.length) { + cal.set(Calendar.MONTH, months[0] - 1); + cal.add(Calendar.YEAR, 1); + } else + cal.set(Calendar.MONTH, months[Math.abs(index + 1)] - 1); + continue; + } - int month = cal.get(Calendar.MONTH); - index = Arrays.binarySearch(months, month+1); - if (index < 0){ // index not found in array - if (Math.abs(index) > months.length) { - cal.set(Calendar.MONTH, months[0]-1); - cal.add(Calendar.YEAR, 1); - } else - cal.set(Calendar.MONTH, months[Math.abs(index+1)]-1); - } -/* - int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); - index = Arrays.binarySearch(dayOfWeeks, dayOfWeek); - if (index < 0){ // index not found in array - if (Math.abs(index) > dayOfWeeks.length) { - cal.set(Calendar.DAY_OF_WEEK, dayOfWeeks[0]); - cal.add(Calendar.WEEK_OF_YEAR, 1); - } else - cal.set(Calendar.DAY_OF_WEEK, dayOfWeeks[Math.abs(index+1)]); - } -*/ - int year = cal.get(Calendar.YEAR); - index = Arrays.binarySearch(years, year); - if (index < 0){ // index not found in array - if (Math.abs(index) > years.length) - return -1L; - else - cal.set(Calendar.YEAR, years[Math.abs(index+1)]); - } + int day = cal.get(Calendar.DAY_OF_MONTH); + index = Arrays.binarySearch(dayOfMonths, day); + if (index < 0) { // index not found in array + if (Math.abs(index) > dayOfMonths.length || + // check if month have that many days in it + dayOfMonths[Math.abs(index + 1)] > cal.getActualMaximum(Calendar.DAY_OF_MONTH)) { + cal.set(Calendar.DAY_OF_MONTH, dayOfMonths[0]); + cal.add(Calendar.MONTH, 1); + } else + cal.set(Calendar.DAY_OF_MONTH, dayOfMonths[Math.abs(index + 1)]); + continue; + } + // Calendar DAY_OF_WEEK is weird so we need to convert it to a logical number + int dayOfWeek = getDayOfWeekID(cal.get(Calendar.DAY_OF_WEEK)); + index = Arrays.binarySearch(dayOfWeeks, dayOfWeek); + if (index < 0) { // index not found in array + if (Math.abs(index) > dayOfWeeks.length) { + cal.set(Calendar.DAY_OF_WEEK, getDayOfWeekEnum(dayOfWeeks[0])); + cal.add(Calendar.WEEK_OF_YEAR, 1); + } else + cal.set(Calendar.DAY_OF_WEEK, getDayOfWeekEnum(dayOfWeeks[Math.abs(index + 1)])); + continue; + } + + int hour = cal.get(Calendar.HOUR_OF_DAY); + index = Arrays.binarySearch(hours, hour); + if (index < 0) { // not found in array + if (Math.abs(index) > hours.length) { + cal.set(Calendar.HOUR_OF_DAY, hours[0]); + cal.add(Calendar.DAY_OF_MONTH, 1); + } else + cal.set(Calendar.HOUR_OF_DAY, hours[Math.abs(index + 1)]); + continue; + } + + int minute = cal.get(Calendar.MINUTE); + index = Arrays.binarySearch(minutes, minute); + if (index < 0) { // not found in array + if (Math.abs(index) > minutes.length) { + cal.set(Calendar.MINUTE, minutes[0]); + cal.add(Calendar.HOUR_OF_DAY, 1); + } else + cal.set(Calendar.MINUTE, minutes[Math.abs(index + 1)]); + continue; + } + + // If we reach the end that means that we got match for all parameters + break; + } return cal.getTimeInMillis(); } protected Calendar getCalendar(long timestamp){ - Calendar cal = Calendar.getInstance(); + Calendar cal = Calendar.getInstance( + TimeZone.getTimeZone("UTC"), + new Locale("sv","SE")); cal.setTimeInMillis(timestamp); return cal; } + + /** + * Converts Calendar DAY_OF_WEEK enum to id starting from 1 (Monday) to 7 (Sunday) + */ + private int getDayOfWeekID(int calDayOfWeek){ + switch (calDayOfWeek){ + case Calendar.MONDAY: return 1; + case Calendar.TUESDAY: return 2; + case Calendar.WEDNESDAY: return 3; + case Calendar.THURSDAY: return 4; + case Calendar.FRIDAY: return 5; + case Calendar.SATURDAY: return 6; + case Calendar.SUNDAY: return 7; + } + return -1; + } + private int getDayOfWeekEnum(int dayId){ + switch (dayId){ + case 1: return Calendar.MONDAY; + case 2: return Calendar.TUESDAY; + case 3: return Calendar.WEDNESDAY; + case 4: return Calendar.THURSDAY; + case 5: return Calendar.FRIDAY; + case 6: return Calendar.SATURDAY; + case 7: return Calendar.SUNDAY; + } + return -1; + } } diff --git a/test/zutil/CronTimerTest.java b/test/zutil/CronTimerTest.java index 21497d6..184f00b 100755 --- a/test/zutil/CronTimerTest.java +++ b/test/zutil/CronTimerTest.java @@ -34,20 +34,83 @@ public class CronTimerTest { assertEquals(Arrays.asList(0,1,2,3,4,5,6,7,8,9,10), CronTimer.getRange("*", 0,10)); assertEquals(Arrays.asList(5,6,7), CronTimer.getRange("*", 5,7)); assertEquals(Arrays.asList(0,10,20,30,40,50,60), CronTimer.getRange("*/10", 0,60)); + + // Illegal numbers + try{ + CronTimer.getRange("50", 1,12); + fail("Did not receive Exception"); + } catch (IllegalArgumentException e){e.printStackTrace();} // We expect exception + try{ + CronTimer.getRange("0", 1,12); + fail("Did not receive Exception"); + } catch (IllegalArgumentException e){e.printStackTrace();} // We expect exception + try{ + CronTimer.getRange("1-50", 1,12); + fail("Did not receive Exception"); + } catch (IllegalArgumentException e){e.printStackTrace();} // We expect exception + try{ + CronTimer.getRange("0-5", 3,12); + fail("Did not receive Exception"); + } catch (IllegalArgumentException e){e.printStackTrace();} // We expect exception } @Test public void specificTime() { - CronTimer cron = new CronTimer("59 23 31 12 5 2003"); + CronTimer cron = new CronTimer("59 23 31 12 3 2003"); assertEquals(-1, (long) cron.next()); - assertEquals(1072911540000L, (long) cron.next(1072911540000L)); - assertEquals(1072911540000L, (long) cron.next(978307140000L)); + assertEquals(1072915140000L, (long) cron.next(1072915140000L)); + assertEquals(1072915140000L, (long) cron.next(978307140000L)); } + @Test public void minuteWildcard(){ CronTimer cron = new CronTimer("00 * * * * *"); - assertEquals(1041411600000L, (long)cron.next(1041411600000L)); - assertEquals(1041415200000L, (long)cron.next(1041411660000L)); + assertEquals(1041379200000L, (long)cron.next(1041379200000L)); + assertEquals(1041382800000L, (long)cron.next(1041379260000L)); + } + + @Test + public void hourWildcard(){ + CronTimer cron = new CronTimer("* 0 * * * *"); + assertEquals(1041379200000L, (long)cron.next(1041379200000L)); + assertEquals(1041379260000L, (long)cron.next(1041379260000L)); // minute change + assertEquals(1041382790000L, (long)cron.next(1041382790000L)); // minute border + assertEquals(1041465600000L, (long)cron.next(1041382800000L)); // hour change + } + + @Test + public void dayWildcard(){ + CronTimer cron = new CronTimer("* * 1 * * *"); + assertEquals(1041379200000L, (long)cron.next(1041379200000L)); + assertEquals(1041379260000L, (long)cron.next(1041379260000L)); // minute change + assertEquals(1044057600000L, (long)cron.next(1041465600000L)); // day change + } + + @Test + public void monthWildcard(){ + CronTimer cron = new CronTimer("* * * 1 * *"); + assertEquals(1041379200000L, (long)cron.next(1041379200000L)); + assertEquals(1041382800000L, (long)cron.next(1041382800000L)); // hour change + assertEquals(1041469200000L, (long)cron.next(1041469200000L)); // day change + assertEquals(1072915200000L, (long)cron.next(1044057600000L)); // month change + } + + @Test + public void weekDayWildcard(){ + CronTimer cron = new CronTimer("* * * * 3 *"); + assertEquals(1041379200000L, (long)cron.next(1041379200000L)); + assertEquals(1041984000000L, (long)cron.next(1041465600000L)); // day change + } + + @Test + public void yearWildcard(){ + CronTimer cron = new CronTimer("* * * * * 2003"); + assertEquals(1041379200000L, (long)cron.next(1041379200000L)); + assertEquals(1041379260000L, (long)cron.next(1041379260000L)); // min change + assertEquals(1041382860000L, (long)cron.next(1041382860000L)); // hour change + assertEquals(1041469260000L, (long)cron.next(1041469260000L)); // day change + assertEquals(1044147660000L, (long)cron.next(1044147660000L)); // month change + assertEquals(-1, (long)cron.next(1075683660000L)); // year change } } \ No newline at end of file