package er.extensions.foundation; import java.sql.Timestamp; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.GregorianCalendar; import com.webobjects.foundation.NSComparator; import com.webobjects.foundation.NSTimeZone; import com.webobjects.foundation.NSTimestamp; import com.webobjects.foundation.NSTimestampFormatter; /** * Collection of {@link com.webobjects.foundation.NSTimestamp NSTimestamp} utilities. */ public class ERXTimestampUtilities { /** caches date formatter the first time it is used */ private static NSTimestampFormatter _gregorianDateFormatterForJavaDate; /** * Calculates a timestamp given a string. Currently supports * the strings: now, today, yesterday, tomorrow, distantPast * and distantFuture. * @param defaultValue string given above * @return timestamp equivalent to the string */ public static NSTimestamp timestampForString(String defaultValue) { NSTimestamp value = null; if (defaultValue.equals("now")) { value = new NSTimestamp(); } else if (defaultValue.equals("today")) { value = ERXTimestampUtilities.today(); } else if (defaultValue.equals("yesterday")) { value = ERXTimestampUtilities.yesterday(); } else if (defaultValue.equals("tomorrow")) { value = ERXTimestampUtilities.tomorrow(); } else if (defaultValue.equals("distantPast")) { value = NSTimestamp.DistantPast; } else if (defaultValue.equals("distantFuture")) { value = NSTimestamp.DistantFuture; } return value; } /** * Static inner class used for calculations for a given * timestamp. Only used internally. */ static class ERXTimestamp { /** the number of days elapsed from 4713 BC to 1 C.E. */ private static final long _YEAR_1 = 1721426; /** holds the calendar instance */ private GregorianCalendar _calendar; /** holds the timestamp */ private NSTimestamp ts; /** * Public constructor. * @param value timestamp to be wrapped */ ERXTimestamp(NSTimestamp value) { ts = value; _calendar = new GregorianCalendar(); _calendar.setTime(ts); } /** * Returns the day of the common era. * Please note that even if leap years are handled but * result is not consistent with <code>NSTimestamp<code>'s. * The formula used is detailed here: * http://www.tondering.dk/claus/cal/node3.html#SECTION003151000000000000000 * @return day of the common era */ public long dayOfCommonEra() { int a = (14- (monthOfYear()+1) )/12; int y = yearOfCommonEra() + 4800 - a; long m = monthOfYear()+1 + 12*a - 3; long julianDays = dayOfMonth() + (153*m + 2)/5 + 365 * y + y/4 - y/100 + y/400 - 32045; return julianDays - _YEAR_1; } /** * Returns the day of the week as returned by * a GregorianCalendar. * @return day of the week as an int. */ public int dayOfWeek() { return _calendar.get(Calendar.DAY_OF_WEEK); } /** * Returns the day of the month as returned by * a GregorianCalendar. * @return day of the month as an int. */ public int dayOfMonth() { return _calendar.get(Calendar.DATE); } /** * Returns the day of the year as returned by * a GregorianCalendar. * @return day of the year as an int. */ public int dayOfYear() { return _calendar.get(Calendar.DAY_OF_YEAR); } /** * Returns the hour of the day as returned by * a GregorianCalendar. * @return hour of the day as an int. */ public int hourOfDay() { return _calendar.get(Calendar.HOUR_OF_DAY); } /** * Returns the minute of the hour as returned by * a GregorianCalendar. * @return minute of the hour as an int. */ public int minuteOfHour() { return _calendar.get(Calendar.MINUTE); } /** * Returns the seconds of the minute as returned by * a GregorianCalendar. * @return seconds of the minute as an int. */ public int secondOfMinute() { return _calendar.get(Calendar.SECOND); } /** * Returns the month of the year as returned by * a GregorianCalendar. * @return month of the year as an int. */ public int monthOfYear() { return _calendar.get(Calendar.MONTH); } /** * Returns the year of the common era as returned by * a GregorianCalendar. * @return year of the common era as an int. */ public int yearOfCommonEra() { return _calendar.get(Calendar.YEAR); } } /** * Package level access for returning an * instance of the inner class ERXTimestamp for * the current time. * @return instance of ERXTimestamp for the current * time. */ static ERXTimestamp getInstance() { return getInstance(new NSTimestamp()); } /** * Package level access for returning an * instance of the inner class ERXTimestamp for * the given time. * @param ts a timestamp * @return instance of ERXTimestamp for the given * time. */ static ERXTimestamp getInstance(NSTimestamp ts) { return new ERXTimestamp(ts); } /** * Timestamp representing today (12:00 AM). Implementation * wise this method subtracts the current hours, minutes and * seconds from the current time. * @return timestamp for today. */ public static NSTimestamp today() { ERXTimestamp now = getInstance(); return now.ts.timestampByAddingGregorianUnits(0, 0, 0, -now.hourOfDay(), -now.minuteOfHour(), -now.secondOfMinute()); } /** * Timestamp representing tomorrow (12:00 AM). Implementation * wise this method subtracts the current hours, minutes and * seconds from the current time and then adds one day. * @return timestamp for tomorrow. */ public static NSTimestamp tomorrow() { ERXTimestamp now = getInstance(); return now.ts.timestampByAddingGregorianUnits(0, 0, 1, -now.hourOfDay(), -now.minuteOfHour(), -now.secondOfMinute()); } /** * Timestamp representing yesterday (12:00 AM). Implementation * wise this method subtracts the current hours, minutes and * seconds from the current time and then subtracts one day. * @return timestamp for yesterday. */ public static NSTimestamp yesterday() { ERXTimestamp now = getInstance(); return now.ts.timestampByAddingGregorianUnits(0, 0, -1, -now.hourOfDay(), -now.minuteOfHour(), -now.secondOfMinute()); } /** * Adds the time (hours, minutes and seconds) from * the second timestamp to the first timestamp. * @param ts timestamp to have the time added too. * @param t1 timestamp to add the time from * @return the first timestamp with the time of the * second added to it. */ public static NSTimestamp timestampByAddingTime(NSTimestamp ts, NSTimestamp t1) { ERXTimestamp time = getInstance(t1); return ts.timestampByAddingGregorianUnits(0, 0, 0, time.hourOfDay(), time.minuteOfHour(), time.secondOfMinute()); } /************** Start Of UnixTimeAdditions ***************/ /** holds a static reference to the epoch */ static NSTimestamp _epoch = new NSTimestamp(1970, 1, 1, 0, 0, 0, null); /** * Utility method used to return the epoch, * Jan 1st, 1970 * @return the epoch as an NSTimestamp */ public static NSTimestamp epoch() { return _epoch; } /** * Converts an offset from the epoch into a * timestamp. * @param helpedNSNumber number offset from the epoch * @return timestamp representation of the offset from * the epoch. */ public static NSTimestamp unixDate(Number helpedNSNumber) { return ERXTimestampUtilities.epoch().timestampByAddingGregorianUnits(0, 0, 0, 0, 0, (int)helpedNSNumber.longValue()+60*60); } /** * Converts a timestamp into the equivalent unix * offset from the epoch. * @param ts timestamp to be converted * @return timestamp represented as an integer offset * from the epoch. */ public static Integer unixTimestamp(NSTimestamp ts) { long seconds = 0; seconds = ts.getTime() - epoch().getTime(); return Integer.valueOf((int)((seconds-60*60)/1000L)); } /** * Returns the SimpleDateFormat pattern given an NSTimestampFormatter pattern. Note that these are not * 100% compatible -- SimpleDateFormat properly implements DST and TimeZones whereas NSTimestampFormatter * is ... kind of whacked, so you may notice your dates are off by a DST amount. * * @param timestampFormatterPattern the NSTimestampFormatter pattern * @return a SimpleDateFormat pattern */ public static String simpleDateFormatForNSTimestampFormat(String timestampFormatterPattern) { StringBuilder dateFormat = new StringBuilder(timestampFormatterPattern.length()); int length = timestampFormatterPattern.length(); for (int i = 0; i < length; i ++) { char ch = timestampFormatterPattern.charAt(i); if (ch == '%') { char nextCh = timestampFormatterPattern.charAt(++ i); switch (nextCh) { case '%': dateFormat.append('%'); break; case 'a': dateFormat.append("EEE"); break; case 'A': dateFormat.append("EEEEE"); break; case 'b': dateFormat.append("MMM"); break; case 'B': dateFormat.append("MMMMM"); break; case 'c': dateFormat.append(((SimpleDateFormat)SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)).toPattern()); break; case 'd': dateFormat.append("dd"); break; case 'e': dateFormat.append('d'); break; case 'F': dateFormat.append("SSS"); break; case 'H': dateFormat.append("HH"); break; case 'I': dateFormat.append("hh"); break; case 'j': dateFormat.append("DDD"); break; case 'm': dateFormat.append("MM"); break; case 'M': dateFormat.append("mm"); break; case 'p': dateFormat.append("aa"); break; case 'S': dateFormat.append("ss"); break; case 'w': dateFormat.append("EEE"); // ??? break; case 'x': dateFormat.append(((SimpleDateFormat)SimpleDateFormat.getDateInstance(DateFormat.SHORT)).toPattern()); break; case 'X': dateFormat.append(((SimpleDateFormat)SimpleDateFormat.getTimeInstance(DateFormat.SHORT)).toPattern()); break; case 'y': dateFormat.append("yy"); break; case 'Y': dateFormat.append("yyyy"); break; case 'z': dateFormat.append('Z'); break; case 'Z': dateFormat.append('z'); break; default: dateFormat.append('%'); // (this is what NSTimstampFormatter did) } } else { dateFormat.append(ch); } } return dateFormat.toString(); } public static GregorianCalendar calendarForTimestamp(NSTimestamp t) { GregorianCalendar calendar = (GregorianCalendar) Calendar.getInstance(); calendar.setTime(t); return calendar; } public static long offsetForDateInCommonEra(NSTimestamp t, int mode) { GregorianCalendar calendar = calendarForTimestamp(t); switch(mode) { case Calendar.YEAR: return calendar.get(Calendar.YEAR); case Calendar.MONTH: return calendar.get(Calendar.YEAR) * 12 + calendar.get(Calendar.MONTH); case Calendar.WEEK_OF_YEAR: return calendar.get(Calendar.YEAR) * 52 + calendar.get(Calendar.WEEK_OF_YEAR); case Calendar.DAY_OF_MONTH: case Calendar.DAY_OF_YEAR: return calendar.get(Calendar.YEAR) * 365 + calendar.get(Calendar.DAY_OF_YEAR); case Calendar.HOUR_OF_DAY: case Calendar.HOUR: return (calendar.get(Calendar.YEAR) * 365 + calendar.get(Calendar.DAY_OF_YEAR)) * 24 + calendar.get(Calendar.HOUR_OF_DAY); default: return 0; } } public static long differenceByDay(NSTimestamp t1, NSTimestamp t2) { return compareDatesInCommonEra(t1, t2, Calendar.DAY_OF_YEAR); } public static long differenceByWeek(NSTimestamp t1, NSTimestamp t2) { return compareDatesInCommonEra(t1, t2, Calendar.WEEK_OF_YEAR); } public static long differenceByMonth(NSTimestamp t1, NSTimestamp t2) { return compareDatesInCommonEra(t1, t2, Calendar.MONTH); } public static long differenceByYear(NSTimestamp t1, NSTimestamp t2) { return compareDatesInCommonEra(t1, t2, Calendar.YEAR); } public static NSTimestamp firstDateInSameWeek(NSTimestamp value) { return new NSTimestamp(yearOfCommonEra(value), monthOfYear(value), -dayOfWeek(value) + 1, 0, 0, 0, NSTimeZone.defaultTimeZone()); } public static NSTimestamp firstDateInSameMonth(NSTimestamp value) { return new NSTimestamp(yearOfCommonEra(value), monthOfYear(value), -dayOfMonth(value) + 1, 0, 0, 0, NSTimeZone.defaultTimeZone()); } public static NSTimestamp firstDateInNextMonth(NSTimestamp value) { return firstDateInSameMonth(value).timestampByAddingGregorianUnits(0, 1, 0, 0, 0, 0); } public static long compareDatesInCommonEra(NSTimestamp t1, NSTimestamp t2, int mode) { return offsetForDateInCommonEra(t2, mode) - offsetForDateInCommonEra(t1, mode); } public static int dayOfCommonEra(NSTimestamp t) { return yearOfCommonEra(t) * 365 + dayOfYear(t); } public static int monthOfCommonEra(NSTimestamp t) { return yearOfCommonEra(t) * 12 + monthOfYear(t); } public static int weekOfCommonEra(NSTimestamp t) { return yearOfCommonEra(t) * 12 + weekOfYear(t); } public static boolean isWeekDay(NSTimestamp t) { int day = dayOfWeek(t); return !((day == Calendar.SATURDAY) || (day == Calendar.SUNDAY)); } public static int dayOfWeek(NSTimestamp t) { return calendarForTimestamp(t).get(Calendar.DAY_OF_WEEK); } public static int dayOfMonth(NSTimestamp t) { return calendarForTimestamp(t).get(Calendar.DAY_OF_MONTH); } public static int weekOfYear(NSTimestamp t) { return calendarForTimestamp(t).get(Calendar.WEEK_OF_YEAR); } public static int weekOfMonth(NSTimestamp t) { return calendarForTimestamp(t).get(Calendar.WEEK_OF_MONTH); } public static int dayOfYear(NSTimestamp t) { return calendarForTimestamp(t).get(Calendar.DAY_OF_YEAR); } public static int hourOfDay(NSTimestamp t) { return calendarForTimestamp(t).get(Calendar.HOUR_OF_DAY); } public static int minuteOfHour(NSTimestamp t) { return calendarForTimestamp(t).get(Calendar.MINUTE); } public static int secondOfMinute(NSTimestamp t) { return calendarForTimestamp(t).get(Calendar.SECOND); } public static int monthOfYear(NSTimestamp t) { return calendarForTimestamp(t).get(Calendar.MONTH); } public static int yearOfCommonEra(NSTimestamp t) { return calendarForTimestamp(t).get(Calendar.YEAR); } /** * Utility method to return a standard timestamp * formatter for the default string representation * of java dates. * @return timestamp formatter for java dates. */ public static NSTimestampFormatter gregorianDateFormatterForJavaDate() { if (_gregorianDateFormatterForJavaDate == null) _gregorianDateFormatterForJavaDate = new NSTimestampFormatter("%a %b %d %H:%M:%S %Z %Y"); return _gregorianDateFormatterForJavaDate; } }