//revised from prefuse
import java.lang.reflect.Constructor;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* Library routines for dealing with times and time spans. All time values
* are given as long values, indicating the number of milliseconds since
* the epoch (January 1, 1970). This is the same time format returned
* by the {@link java.lang.System#currentTimeMillis()} method.
*
* @author jeffrey heer
*/
public class TimeLib {
/** Represents a millenium, 1000 years. */
public static final int MILLENIUM = -1000;
/** Represents a century, 100 years */
public static final int CENTURY = -100;
/** Represents a decade, 10 years */
public static final int DECADE = -10;
private static final double SECOND_MILLIS = 1000;
private static final double MINUTE_MILLIS = SECOND_MILLIS*60;
private static final double HOUR_MILLIS = MINUTE_MILLIS*60;
private static final double DAY_MILLIS = HOUR_MILLIS * 24.0015;
private static final double WEEK_MILLIS = DAY_MILLIS * 7;
private static final double MONTH_MILLIS = DAY_MILLIS * 30.43675;
private static final double YEAR_MILLIS = WEEK_MILLIS * 52.2;
private static final double DECADE_MILLIS = YEAR_MILLIS * 10;
private static final double CENTURY_MILLIS = DECADE_MILLIS * 10;
private static final double MILLENIUM_MILLIS = CENTURY_MILLIS * 10;
private static final int[] CALENDAR_FIELDS = {
Calendar.YEAR, Calendar.MONTH, Calendar.DATE, Calendar.HOUR_OF_DAY,
Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND
};
private TimeLib() {
// prevent instantiation
}
/**
* Get the number of time units between the two given timestamps.
* @param t0 the first timestamp (as a long)
* @param t1 the second timestamp (as a long)
* @param field the time unit to use, one of the {@link java.util.Calendar}
* fields, or one of the extended fields provided by this class
* (MILLENIUM, CENTURY, or DECADE).
* @return the number of time units between the two timestamps
*/
public static int getUnitsBetween(long t0, long t1, int field) {
boolean negative = false;
if ( t1 < t0 ) {
long tmp = t1; t1 = t0; t0 = tmp; // swap
negative = true;
}
GregorianCalendar gc1 = new GregorianCalendar();
GregorianCalendar gc2 = new GregorianCalendar();
gc1.setTimeInMillis(t0);
gc2.setTimeInMillis(t1);
// add 2 units less than the estimate to 1st date,
// then serially add units till we exceed 2nd date
int est = estimateUnitsBetween(t0, t1, field);
boolean multiYear = isMultiYear(field);
if ( multiYear ) {
gc1.add(Calendar.YEAR, -field*(est-2));
est = -field*est;
} else {
gc1.add(field, est-2);
}
int f = multiYear ? Calendar.YEAR : field;
int inc = multiYear ? -field : 1;
for( int i=est-inc; ; i+=inc ) {
gc1.add(f, inc);
if( gc1.after(gc2) ) {
return negative ? inc-i : i-inc;
}
}
}
/**
* Based on code posted at
* http://forum.java.sun.com/thread.jspa?threadID=488676&messageID=2292012
*/
private static int estimateUnitsBetween(long t0, long t1, int field) {
long d = t1-t0;
switch (field) {
case Calendar.MILLISECOND:
return (int)d; // this could be very inaccurate. TODO: use long instead of int?
case Calendar.SECOND:
return (int)(d / SECOND_MILLIS + .5);
case Calendar.MINUTE:
return (int)(d / MINUTE_MILLIS + .5);
case Calendar.HOUR_OF_DAY:
case Calendar.HOUR:
return (int)(d / HOUR_MILLIS + .5);
case Calendar.DAY_OF_WEEK_IN_MONTH :
case Calendar.DAY_OF_MONTH :
// case Calendar.DATE : // codes to same int as DAY_OF_MONTH
return (int) (d / DAY_MILLIS + .5);
case Calendar.WEEK_OF_YEAR :
return (int) (d / WEEK_MILLIS + .5);
case Calendar.MONTH :
return (int) (d / MONTH_MILLIS + .5);
case Calendar.YEAR :
return (int) (d / YEAR_MILLIS + .5);
case DECADE:
return (int) (d / DECADE_MILLIS + .5);
case CENTURY:
return (int) (d / CENTURY_MILLIS + .5);
case MILLENIUM:
return (int) (d / MILLENIUM_MILLIS + .5);
default:
return 0;
}
}
/**
* Increment a calendar by a given number of time units.
* @param c the calendar to increment
* @param field the time unit to increment, one of the
* {@link java.util.Calendar} fields, or one of the extended fields
* provided by this class (MILLENIUM, CENTURY, or DECADE).
* @param val the number of time units to increment by
*/
public static void increment(Calendar c, int field, int val) {
if ( isMultiYear(field) ) {
c.add(Calendar.YEAR, -field*val);
} else {
c.add(field, val);
}
}
/**
* Get the value of the given time field for a Calendar. Just like the
* {@link java.util.Calendar#get(int)} method, but include support for
* the extended fields provided by this class (MILLENIUM, CENTURY, or
* DECADE).
* @param c the Calendar
* @param field the time field
* @return the value of the time field for the given calendar
*/
public static int get(Calendar c, int field) {
if ( isMultiYear(field) ) {
int y = c.get(Calendar.YEAR);
return -field * (y/-field);
} else {
return c.get(field);
}
}
// ------------------------------------------------------------------------
// Date Access
/**
* Get the timestamp for the given year, month, and, day.
* @param c a Calendar to use to help compute the result. The state of the
* Calendar will be overwritten.
* @param year the year to look up
* @param month the month to look up (months start at 0==January)
* @param day the day to look up
* @return the timestamp for the given date
*/
public static long getDate(Calendar c, int year, int month, int day) {
c.clear(Calendar.MILLISECOND);
c.set(year, month, day, 0, 0, 0);
return c.getTimeInMillis();
}
/**
* Get a timestamp for the given hour, minute, and second. The date will
* be assumed to be January 1, 1970.
* @param c a Calendar to use to help compute the result. The state of the
* Calendar will be overwritten.
* @param hour the hour, on a 24 hour clock
* @param minute the minute value
* @param second the seconds value
* @return the timestamp for the given date
*/
public static long getTime(Calendar c, int hour, int minute, int second) {
c.clear(Calendar.MILLISECOND);
c.set(1970, 0, 1, hour, minute, second);
return c.getTimeInMillis();
}
/**
* Get a new Date instance of the specified subclass and given long value.
* @param type the concrete subclass of the Date instance, must be an
* instance of subclass of java.util.Date
* @param d the date/time value as a long
* @return the new Date instance, or null if the class type is not valid
*/
public static Date getDate(Class type, long d) {
try {
Constructor c = type.getConstructor(new Class[] {long.class});
return (Date)c.newInstance(new Object[] {new Long(d)});
} catch ( Exception e ) {
e.printStackTrace();
return null;
}
}
// ------------------------------------------------------------------------
// Date Normalization
/**
* Get the timestamp resulting from clearing (setting to zero) all time
* values less than or equal to that of the given field. For example,
* clearing to {@link Calendar#HOUR} will floor the time to nearest
* hour which occurred before or at the given time (e.g., 1:32
* --> 1:30).
* @param t the reference time
* @param c a Calendar instance used to help compute the value, the
* state of the Calendar will be overwritten.
* @param field the time field to clear to, one of the
* {@link java.util.Calendar} fields, or one of the extended fields
* provided by this class (MILLENIUM, CENTURY, or DECADE).
* @return the cleared time
*/
public static long getClearedTime(long t, Calendar c, int field) {
c.setTimeInMillis(t);
TimeLib.clearTo(c, field);
return c.getTimeInMillis();
}
/**
* Clear the given calendar, setting to zero all time
* values less than or equal to that of the given field. For example,
* clearing to {@link Calendar#HOUR} will floor the time to nearest
* hour which occurred before or at the given time (e.g., 1:32
* --> 1:30).
* @param c the Calendar to clear
* @param field the time field to clear to, one of the
* {@link java.util.Calendar} fields, or one of the extended fields
* provided by this class (MILLENIUM, CENTURY, or DECADE).
* @return the original Calendar reference, now set to the cleared time
*/
public static Calendar clearTo(Calendar c, int field) {
int i = CALENDAR_FIELDS.length-1;
for ( ; i>=1 && field != CALENDAR_FIELDS[i]; i-- ) {
int val = (CALENDAR_FIELDS[i]==Calendar.DATE?1:0);
c.set(CALENDAR_FIELDS[i],val);
}
if ( isMultiYear(field) ) {
int y = c.get(Calendar.YEAR);
y = -field * (y/-field);
c.set(Calendar.YEAR, y);
}
return c;
}
/**
* Indicates if a field value indicates a timespan greater than one
* year. These multi-year spans are the extended fields introduced by
* this class (MILLENIUM, CENTURY, and DECADE).
* @param field the time field
* @return true if the field is multi-year, false otherwise
*/
public static boolean isMultiYear(int field) {
return ( field == DECADE || field == CENTURY || field == MILLENIUM );
}
} // end of class TimeLib