diff --git a/src/zutil/ArrayUtil.java b/src/zutil/ArrayUtil.java index 458f8d1..c5eab2c 100755 --- a/src/zutil/ArrayUtil.java +++ b/src/zutil/ArrayUtil.java @@ -1,10 +1,25 @@ package zutil; +import java.util.List; + /** * A utility class containing Array specific utility methods */ public class ArrayUtil { + /** + * Converts a List with Integer objects to a primary type int array + */ + public static int[] toIntArray(List list){ + if (list == null) + return null; + int[] arr = new int[list.size()]; + int i = 0; + for (Integer v : list) + arr[i++] = v; + return arr; + } + /** * Searches for a given object inside of an array. * The method uses reference comparison or {@link #equals(Object)} to check for equality. diff --git a/src/zutil/CronTimer.java b/src/zutil/CronTimer.java index dcc6dbd..1bb8d79 100755 --- a/src/zutil/CronTimer.java +++ b/src/zutil/CronTimer.java @@ -1,17 +1,23 @@ package zutil; -import com.mysql.fabric.xmlrpc.base.Array; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; -import java.util.ArrayList; -import java.util.List; +import java.util.*; /** * This is a utility class that will generate timestamps from a Cron formatted String. * * @see Cron Format Specification */ -public class CronTimer { +public class CronTimer implements Iterator, Iterable{ + + private int[] minutes; + private int[] hours; + private int[] dayOfMonths; + private int[] months; + private int[] dayOfWeeks; + private int[] years; /** @@ -38,11 +44,154 @@ public class CronTimer { } private void init(String minute, String hour, String dayOfMonth, String monthOfYear, String dayOfWeek, String year){ - + minutes = ArrayUtil.toIntArray(getRange(minute, 0, 59)); + hours = ArrayUtil.toIntArray(getRange(hour, 0, 23)); + dayOfMonths = ArrayUtil.toIntArray(getRange(dayOfMonth, 1, 31)); + months = ArrayUtil.toIntArray(getRange(monthOfYear, 1, 12)); + dayOfWeeks = ArrayUtil.toIntArray(getRange(dayOfWeek, 1, 7)); + years = ArrayUtil.toIntArray(getRange(year, + 1970, + Calendar.getInstance().get(Calendar.YEAR)+30)); } - protected static List getRange(String number){ - ArrayList list = new ArrayList<>(); - + protected static List getRange(String str, int from, int to){ + if (str == null || str.isEmpty()) + return Collections.emptyList(); + + List list = new LinkedList<>(); + + String[] commaArr = str.split(","); + if (commaArr.length > 1){ + for (String section : commaArr) + list.addAll(getRange(section, from, to)); + } + else { + String[] divisionArr = str.split("/", 2); + if (divisionArr.length == 2) { + float divider = Integer.parseInt(divisionArr[1]); + Iterator it = getRange(divisionArr[0], from, to).iterator(); + while (it.hasNext()) { + Integer i = it.next(); + if (i%divider == 0) + list.add(i); + } + } + else { + String[] rangeArr; + if (str.equals("*")) + rangeArr = new String[]{""+from, ""+to}; + else + rangeArr = str.split("-", 2); + if (rangeArr.length == 2) { + int rangeFrom = Integer.parseInt(rangeArr[0]); + int rangeTo = Integer.parseInt(rangeArr[1]); + for (int i = rangeFrom; i <= rangeTo; ++i) + list.add(i); + } else { + list.add(Integer.parseInt(str)); + } + } + } return list; } + + + + @Override + public boolean hasNext() { + return true; + } + @Override + public Iterator iterator() { + return this; + } + @Override + public void remove() { + throw new NotImplementedException(); + } + + /** + * @return the next timestamp that triggers this cron timer from now, + * -1 if there is no more future trigger points. + */ + @Override + public Long next() { + return next(System.currentTimeMillis()); + } + /** + * @param fromTimestamp the timestamp offset to check the trigger from. Should be in MS + * @return the next timestamp that triggers this cron timer from the given timestamp, + * -1 if there is no more future trigger points. + */ + 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)]); + } + + 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 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); + 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)]); + } + + return cal.getTimeInMillis(); + } + + protected Calendar getCalendar(long timestamp){ + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(timestamp); + return cal; + } } diff --git a/test/zutil/CronTimerTest.java b/test/zutil/CronTimerTest.java index e0ad97b..21497d6 100755 --- a/test/zutil/CronTimerTest.java +++ b/test/zutil/CronTimerTest.java @@ -15,15 +15,39 @@ public class CronTimerTest { @Test public void getRange() throws Exception { // invalid numbers - assertEquals(Collections.EMPTY_LIST, CronTimer.getRange("")); - assertEquals(Collections.EMPTY_LIST, CronTimer.getRange(null)); + assertEquals(Collections.EMPTY_LIST, CronTimer.getRange("", 0,60)); + assertEquals(Collections.EMPTY_LIST, CronTimer.getRange(null, 0,60)); + + // individual numbers + assertEquals(Arrays.asList(55), CronTimer.getRange("55", 0,60)); + assertEquals(Arrays.asList(5,10,15), CronTimer.getRange("5,10,15", 0, 60)); // ranges - //assertEquals(Arrays.asList(0,1), CronTimer.getRange("0-1")); + assertEquals(Arrays.asList(0,1), CronTimer.getRange("0-1", 0,60)); + assertEquals(Arrays.asList(5,6,7,8,9,10), CronTimer.getRange("5-10", 0,60)); // intervals + assertEquals(Arrays.asList(), CronTimer.getRange("15/10", 0,60)); + assertEquals(Arrays.asList(0,10,20,30,40,50,60), CronTimer.getRange("0-60/10", 0,60)); - // range and interval + // wildcards + 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)); + } + + @Test + public void specificTime() { + CronTimer cron = new CronTimer("59 23 31 12 5 2003"); + assertEquals(-1, (long) cron.next()); + assertEquals(1072911540000L, (long) cron.next(1072911540000L)); + assertEquals(1072911540000L, (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)); } } \ No newline at end of file