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; i1) { 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; } }