package timeflow.data.time; import java.util.*; import java.text.*; public class TimeUnit { public static final TimeUnit YEAR = new TimeUnit("Years", Calendar.YEAR, 365 * 24 * 60 * 60 * 1000L, "yyyy", "yyyy"); public static final TimeUnit MONTH = new TimeUnit("Months", Calendar.MONTH, 30 * 24 * 60 * 60 * 1000L, "MMM", "MMM yyyy"); public static final TimeUnit WEEK = new TimeUnit("Weeks", Calendar.WEEK_OF_YEAR, 7 * 24 * 60 * 60 * 1000L, "d", "MMM d yyyy"); public static final TimeUnit DAY = new TimeUnit("Days", Calendar.DAY_OF_MONTH, 24 * 60 * 60 * 1000L, "d", "MMM d yyyy"); public static final TimeUnit DAY_OF_WEEK = new TimeUnit("Days", Calendar.DAY_OF_WEEK, 24 * 60 * 60 * 1000L, "d", "MMM d yyyy"); public static final TimeUnit HOUR = new TimeUnit("Hours", Calendar.HOUR_OF_DAY, 60 * 60 * 1000L, "kk:mm", "MMM d yyyy kk:mm"); public static final TimeUnit MINUTE = new TimeUnit("Minutes", Calendar.MINUTE, 60 * 1000L, ":mm", "MMM d yyyy kk:mm"); public static final TimeUnit SECOND = new TimeUnit("Seconds", Calendar.SECOND, 1000L, ":ss", "MMM d yyyy kk:mm:ss"); public static final TimeUnit DECADE = multipleYears(10); public static final TimeUnit CENTURY = multipleYears(100); private static final double DAY_SIZE = 24 * 60 * 60 * 1000L; private int quantity; private long roughSize; private SimpleDateFormat format, fullFormat; private String name; private int calendarCode; private TimeUnit() { } private TimeUnit(String name, int calendarCode, long roughSize, String formatPattern, String fullFormatPattern) { this.name = name; this.calendarCode = calendarCode; this.roughSize = roughSize; format = new SimpleDateFormat(formatPattern); fullFormat = new SimpleDateFormat(fullFormatPattern); quantity = 1; } public String toString() { return "[TimeUnit: " + name + "]"; } public static TimeUnit multipleYears(int numYears) { TimeUnit t = new TimeUnit(); t.name = numYears + " Years"; t.calendarCode = Calendar.YEAR; t.roughSize = YEAR.roughSize * numYears; t.format = YEAR.format; t.fullFormat = YEAR.fullFormat; t.quantity = numYears; return t; } public static TimeUnit multipleWeeks(int num) { TimeUnit t = new TimeUnit(); t.name = num + " Weeks"; t.calendarCode = Calendar.WEEK_OF_YEAR; t.roughSize = WEEK.roughSize * num; t.format = WEEK.format; t.fullFormat = WEEK.fullFormat; t.quantity = num; return t; } public TimeUnit times(int quantity) { TimeUnit t = new TimeUnit(); t.name = quantity + " " + this.name; t.calendarCode = this.calendarCode; t.roughSize = this.roughSize * quantity; t.format = this.format; t.fullFormat = this.fullFormat; t.quantity = quantity; return t; } public int numUnitsIn(TimeUnit u) { return (int) Math.round(u.getRoughSize() / (double) getRoughSize()); } public boolean isDayOrLess() { return roughSize <= 24 * 60 * 60 * 1000L; } public RoughTime roundDown(long timestamp) { return round(timestamp, false); } public RoughTime roundUp(long timestamp) { return round(timestamp, true); } private static final int[] calendarUnits = { Calendar.SECOND, Calendar.MINUTE, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_MONTH, Calendar.MONTH, Calendar.YEAR }; public RoughTime round(long timestamp, boolean up) { Calendar c = TimeUtils.cal(timestamp); if (calendarCode == Calendar.WEEK_OF_YEAR) { c.set(Calendar.DAY_OF_WEEK, c.getMinimum(Calendar.DAY_OF_WEEK)); } else { // set to minimum all fields of finer granularity. int roundingCode = calendarCode; if (calendarCode == Calendar.WEEK_OF_YEAR || calendarCode == Calendar.DAY_OF_WEEK) { roundingCode = Calendar.DAY_OF_MONTH; } for (int i = 0; i < calendarUnits.length; i++) { if (calendarUnits[i] == roundingCode) { break; } if (i == calendarUnits.length - 1) { throw new IllegalArgumentException("Unsupported Calendar Unit: " + calendarCode); } c.set(calendarUnits[i], c.getMinimum(calendarUnits[i])); } if (quantity > 1) { c.set(calendarCode, quantity * (c.get(calendarCode) / quantity)); } } // if rounding up, then add a unit at current granularity. if (up) { c.add(calendarCode, quantity); } return new RoughTime(c.getTimeInMillis(), this); } public int get(long timestamp) { Calendar c = TimeUtils.cal(timestamp); int n = c.get(calendarCode); return quantity == 1 ? n : n % quantity; } public void addTo(RoughTime r) { addTo(r, 1); } public void addTo(RoughTime r, int times) { Calendar c = TimeUtils.cal(r.getTime()); c.add(calendarCode, quantity * times); r.setTime(c.getTimeInMillis()); } // Finding the difference between two dates, in a given unit of time, // is much subtler than you'd think! And annoyingly, the Calendar class does not do // this for you, even though it actually "knows" how to do so since it // can add fields. // // The most vexing problem is dealing with daylight savings time, // which means that one day a year has 23 hours and one day has 25 hours. // We also have to handle the fact that months and years aren't constant lengths. // // Rather than write all this ourselves, in this code we // use the Calendar class to do the heavy lifting. public long difference(long x, long y) { // If this is not one of the hard cases, // just divide the timespan by the length of time unit. // Note that we're not worrying about hours and daylight savings time. if (calendarCode != Calendar.YEAR && calendarCode != Calendar.MONTH && calendarCode != Calendar.DAY_OF_MONTH && calendarCode != Calendar.DAY_OF_WEEK && calendarCode != Calendar.WEEK_OF_YEAR) { return (x - y) / roughSize; } Calendar c1 = TimeUtils.cal(x), c2 = TimeUtils.cal(y); int diff = 0; switch (calendarCode) { case Calendar.YEAR: return (c1.get(Calendar.YEAR) - c2.get(Calendar.YEAR)) / quantity; case Calendar.MONTH: diff = 12 * (c1.get(Calendar.YEAR) - c2.get(Calendar.YEAR)) + c1.get(Calendar.MONTH) - c2.get(Calendar.MONTH); return diff / quantity; case Calendar.DAY_OF_MONTH: case Calendar.DAY_OF_WEEK: case Calendar.DAY_OF_YEAR: case Calendar.WEEK_OF_MONTH: case Calendar.WEEK_OF_YEAR: // This is ugly, but believe me, it beats the alternative methods :-) // We use the Calendar class's knowledge of daylight savings time. // and also the fact that if we calculate this naively, then we aren't going // to be off by more than one in either direction. int naive = (int) Math.round((x - y) / (double) roughSize); c2.add(calendarCode, naive * quantity); if (c1.get(calendarCode) == c2.get(calendarCode)) { return naive / quantity; } c2.add(calendarCode, quantity); if (c1.get(calendarCode) == c2.get(calendarCode)) { return naive / quantity + 1; } return naive / quantity - 1; } throw new IllegalArgumentException("Unexpected calendar code: " + calendarCode); } public long approxNumInRange(long start, long end) { return 1 + (end - start) / roughSize; } public long getRoughSize() { return roughSize; } public String format(Date date) { return format.format(date); } public String formatFull(Date date) { return fullFormat.format(date); } public String formatFull(long timestamp) { return fullFormat.format(new Date(timestamp)); } public String getName() { return name; } }