package org.wattdepot.common.util.tstamp; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.GregorianCalendar; import java.util.List; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar; /** * Utility class that facilitates Timestamp representation and processing. There * are too many classes already named "Timestamp", thus the abbreviated name. * * @author Philip Johnson */ public final class Tstamp { /** Make this class noninstantiable. */ private Tstamp() { // Do nothing. } private static final String factoryErrorMsg = "Bad DataTypeFactory"; private static long MILLISECS_PER_DAY = 24 * 60 * 60 * 1000; /** * Returns true if the passed string can be parsed into an * XMLGregorianCalendar object. * * @param lexicalRepresentation The string representation. * @return True if the string is a legal XMLGregorianCalendar. */ public static boolean isTimestamp(String lexicalRepresentation) { try { DatatypeFactory factory = DatatypeFactory.newInstance(); factory.newXMLGregorianCalendar(lexicalRepresentation); return true; } catch (Exception e) { return false; } } /** * Returns an XMLGregorianCalendar, given its string representation. Missing * hours, minutes, second, millisecond, and timezone fields are given * defaults. * * @param rep The string representation. * @return The timestamp. * @throws Exception If the string cannot be parsed into a timestamp. */ public static XMLGregorianCalendar makeTimestamp(String rep) throws Exception { DatatypeFactory factory = DatatypeFactory.newInstance(); long mills = factory.newXMLGregorianCalendar(rep).toGregorianCalendar().getTimeInMillis(); return makeTimestamp(mills); } /** * Converts a javax.sql.Timestamp into a * javax.xml.datatype.XMLGregorianCalendar. * * @param tstamp The javax.sql.Timestamp * @return A new instance of a javax.xml.datatype.XmlGregorianCalendar */ public static XMLGregorianCalendar makeTimestamp(java.sql.Timestamp tstamp) { DatatypeFactory factory = null; try { factory = DatatypeFactory.newInstance(); GregorianCalendar calendar = new GregorianCalendar(); calendar.setTimeInMillis(tstamp.getTime()); return factory.newXMLGregorianCalendar(calendar); } catch (DatatypeConfigurationException e) { throw new RuntimeException(factoryErrorMsg, e); } } /** * Converts the specified time in milliseconds into a * javax.xml.datatype.XMLGregorianCalendar. * * @param timeInMillis the specified time in milliseconds to convert. * @return A new instance of a javax.xml.datatype.XmlGregorianCalendar */ public static XMLGregorianCalendar makeTimestamp(long timeInMillis) { DatatypeFactory factory = null; try { factory = DatatypeFactory.newInstance(); GregorianCalendar calendar = new GregorianCalendar(); calendar.setTimeInMillis(timeInMillis); return factory.newXMLGregorianCalendar(calendar); } catch (DatatypeConfigurationException e) { throw new RuntimeException(factoryErrorMsg, e); } } // /** // * Converts the specified Day into a // javax.xml.datatype.XMLGregorianCalendar. // * @param day The day to be converted. // * @return A new instance of a javax.xml.datatype.XmlGregorianCalendar. // */ // public static XMLGregorianCalendar makeTimestamp(Day day) { // DatatypeFactory factory = null; // try { // factory = DatatypeFactory.newInstance(); // GregorianCalendar calendar = new GregorianCalendar(); // calendar.setTimeInMillis(day.getDate().getTime()); // return factory.newXMLGregorianCalendar(calendar); // } // catch (DatatypeConfigurationException e) { // throw new RuntimeException(factoryErrorMsg, e); // } // } /** * Returns a new XMLGregorianCalendar corresponding to the passed tstamp * incremented by the number of days. * * @param tstamp The base date and time. * @param days The number of days to increment. This can be a negative number. * @return A new XMLGregorianCalendar instance representing the inc'd time. */ public static XMLGregorianCalendar incrementDays(XMLGregorianCalendar tstamp, int days) { DatatypeFactory factory = null; try { factory = DatatypeFactory.newInstance(); GregorianCalendar calendar = new GregorianCalendar(); long millis = tstamp.toGregorianCalendar().getTimeInMillis(); millis += 1000L * 60 * 60 * 24 * days; calendar.setTimeInMillis(millis); return factory.newXMLGregorianCalendar(calendar); } catch (DatatypeConfigurationException e) { throw new RuntimeException(factoryErrorMsg, e); } } /** * Returns a new XMLGregorianCalendar corresponding to the passed tstamp * incremented by the number of hours. * * @param tstamp The base date and time. * @param hours The number of hours to increment. This can be a negative * number. * @return A new XMLGregorianCalendar instance representing the inc'd time. */ public static XMLGregorianCalendar incrementHours(XMLGregorianCalendar tstamp, int hours) { DatatypeFactory factory = null; try { factory = DatatypeFactory.newInstance(); GregorianCalendar calendar = new GregorianCalendar(); long millis = tstamp.toGregorianCalendar().getTimeInMillis(); millis += 1000L * 60 * 60 * hours; calendar.setTimeInMillis(millis); return factory.newXMLGregorianCalendar(calendar); } catch (DatatypeConfigurationException e) { throw new RuntimeException(factoryErrorMsg, e); } } /** * Returns a new XMLGregorianCalendar corresponding to the passed tstamp * incremented by the number of minutes. * * @param tstamp The base date and time. * @param minutes The number of minutes to increment. This can be a negative * number. * @return A new XMLGregorianCalendar instance representing the inc'd time. */ public static XMLGregorianCalendar incrementMinutes(XMLGregorianCalendar tstamp, int minutes) { DatatypeFactory factory = null; try { factory = DatatypeFactory.newInstance(); GregorianCalendar calendar = new GregorianCalendar(); long millis = tstamp.toGregorianCalendar().getTimeInMillis(); millis += 1000L * 60 * minutes; calendar.setTimeInMillis(millis); return factory.newXMLGregorianCalendar(calendar); } catch (DatatypeConfigurationException e) { throw new RuntimeException(factoryErrorMsg, e); } } /** * Returns a new XMLGregorianCalendar corresponding to the passed tstamp * incremented by the number of seconds. * * @param tstamp The base date and time. * @param seconds The number of seconds to increment. This can be a negative * number. * @return A new XMLGregorianCalendar instance representing the inc'd time. */ public static XMLGregorianCalendar incrementSeconds(XMLGregorianCalendar tstamp, int seconds) { DatatypeFactory factory = null; try { factory = DatatypeFactory.newInstance(); GregorianCalendar calendar = new GregorianCalendar(); long millis = tstamp.toGregorianCalendar().getTimeInMillis(); millis += 1000L * seconds; calendar.setTimeInMillis(millis); return factory.newXMLGregorianCalendar(calendar); } catch (DatatypeConfigurationException e) { throw new RuntimeException(factoryErrorMsg, e); } } /** * Returns a new XMLGregorianCalendar corresponding to the passed tstamp * incremented by the number of milliseconds. * * @param tstamp The base date and time. * @param milliseconds The number of milliseconds to increment. This can be a * negative number. * @return A new XMLGregorianCalendar instance representing the inc'd time. */ public static XMLGregorianCalendar incrementMilliseconds(XMLGregorianCalendar tstamp, long milliseconds) { DatatypeFactory factory = null; try { factory = DatatypeFactory.newInstance(); GregorianCalendar calendar = new GregorianCalendar(); long millis = tstamp.toGregorianCalendar().getTimeInMillis(); millis += milliseconds; calendar.setTimeInMillis(millis); return factory.newXMLGregorianCalendar(calendar); } catch (DatatypeConfigurationException e) { throw new RuntimeException(factoryErrorMsg, e); } } /** * Returns a new java.sql.Timestamp created from a * javax.xml.datatype.XMLGregorianCalendar. * * @param calendar The XML timestamp. * @return The SQL timestamp. */ public static java.sql.Timestamp makeTimestamp(XMLGregorianCalendar calendar) { return new java.sql.Timestamp(calendar.toGregorianCalendar().getTimeInMillis()); } /** * Returns an XMLGregorianCalendar corresponding to the current time. * * @return The timestamp. */ public static XMLGregorianCalendar makeTimestamp() { try { DatatypeFactory factory = DatatypeFactory.newInstance(); return factory.newXMLGregorianCalendar(new GregorianCalendar()); } catch (Exception e) { throw new RuntimeException(factoryErrorMsg, e); } } /** * Returns true if tstamp is equal to or between start and end. * * @param start The start time. * @param tstamp The timestamp to test. * @param end The end time. * @return True if tstamp is between start and end. */ public static boolean inBetween(XMLGregorianCalendar start, XMLGregorianCalendar tstamp, XMLGregorianCalendar end) { long startMillis = start.toGregorianCalendar().getTimeInMillis(); long endMillis = end.toGregorianCalendar().getTimeInMillis(); long tstampMillis = tstamp.toGregorianCalendar().getTimeInMillis(); return tstampMillis >= startMillis && tstampMillis <= endMillis; } /** * Returns true if time1 > time2. * * @param time1 The first time. * @param time2 The second time. * @return True if time1 > time2 */ public static boolean greaterThan(XMLGregorianCalendar time1, XMLGregorianCalendar time2) { long time1Millis = time1.toGregorianCalendar().getTimeInMillis(); long time2Millis = time2.toGregorianCalendar().getTimeInMillis(); return time1Millis > time2Millis; } /** * Returns the number of days between time1 and time2. Returns a negative * number if day1 is after day2. Takes into account daylight savings time * issues. * * @param day1 The first day. * @param day2 The second day. * @return The number of days between the two days. */ public static int daysBetween(XMLGregorianCalendar day1, XMLGregorianCalendar day2) { return (int) (getUnixDay(day2) - getUnixDay(day1)); } /** * Returns a long representing the number of days since the Unix epoch to the * passed day. * * @param day The day of interest. * @return The number of days since the epoch. */ private static long getUnixDay(XMLGregorianCalendar day) { GregorianCalendar greg = day.toGregorianCalendar(); long offset = greg.get(Calendar.ZONE_OFFSET) + greg.get(Calendar.DST_OFFSET); long daysSinceEpoch = (long) Math.floor((double) (greg.getTime().getTime() + offset) / ((double) MILLISECS_PER_DAY)); return daysSinceEpoch; } /** * Returns true if timeString1 > timeString2. Throws an unchecked * IllegalArgument exception if the strings can't be converted to timestamps. * * @param timeString1 The first time. * @param timeString2 The second time. * @return True if time1 > time2 */ public static boolean greaterThan(String timeString1, String timeString2) { try { DatatypeFactory factory = DatatypeFactory.newInstance(); XMLGregorianCalendar time1 = factory.newXMLGregorianCalendar(timeString1); XMLGregorianCalendar time2 = factory.newXMLGregorianCalendar(timeString2); return greaterThan(time1, time2); } catch (Exception e) { throw new IllegalArgumentException("Illegal timestring", e); } } /** * Returns true if time1 < time2. * * @param time1 The first time. * @param time2 The second time. * @return True if time1 < time2 */ public static boolean lessThan(XMLGregorianCalendar time1, XMLGregorianCalendar time2) { long time1Millis = time1.toGregorianCalendar().getTimeInMillis(); long time2Millis = time2.toGregorianCalendar().getTimeInMillis(); return time1Millis < time2Millis; } /** * Returns true if time1 equals time2. * * @param time1 The first time. * @param time2 The second time. * @return True if time1 equals time2 */ public static boolean equal(XMLGregorianCalendar time1, XMLGregorianCalendar time2) { long millis1 = time1.toGregorianCalendar().getTimeInMillis(); long millis2 = time2.toGregorianCalendar().getTimeInMillis(); return millis1 == millis2; } /** * Returns differences between time1 and time2 in milliseconds. * * @param time1 Start. * @param time2 End. * @return Difference between two times in milliseconds. */ public static long diff(XMLGregorianCalendar time1, XMLGregorianCalendar time2) { long millis1 = time1.toGregorianCalendar().getTimeInMillis(); long millis2 = time2.toGregorianCalendar().getTimeInMillis(); return millis2 - millis1; } /** * Returns true if the passed timestamp indicates some time today or some time * in the future. * * @param timestamp The timestamp of interest. * @return True if it's today or some day in the future. */ public static boolean isTodayOrLater(XMLGregorianCalendar timestamp) { XMLGregorianCalendar today = Tstamp.makeTimestamp(); boolean isToday = today.getYear() == timestamp.getYear() && today.getMonth() == timestamp.getMonth() && today.getDay() == timestamp.getDay(); boolean afterToday = today.toGregorianCalendar().getTimeInMillis() < timestamp .toGregorianCalendar().getTimeInMillis(); return isToday || afterToday; } /** * Returns true if the passed timestamp indicates some time yesterday or some * time in the future. This is useful for the Commit/Churn DPDs, where the * sensor typically sends data not from the current day, but from the day * before. * * @param timestamp The timestamp of interest. * @return True if it's today or some day in the future. */ public static boolean isYesterdayOrLater(XMLGregorianCalendar timestamp) { XMLGregorianCalendar yesterday = Tstamp.incrementDays(Tstamp.makeTimestamp(), -1); boolean isYesterday = yesterday.getYear() == timestamp.getYear() && yesterday.getMonth() == timestamp.getMonth() && yesterday.getDay() == timestamp.getDay(); boolean afterYesterday = yesterday.toGregorianCalendar().getTimeInMillis() < timestamp .toGregorianCalendar().getTimeInMillis(); return isYesterday || afterYesterday; } /** * Returns a newly created sorted list of tstamps from the passed collection. * * @param tstamps The timestamps to be sorted. * @return A new list of tstamps, now in sorted order. */ public static List<XMLGregorianCalendar> sort(Collection<XMLGregorianCalendar> tstamps) { List<XMLGregorianCalendar> tstampList = new ArrayList<XMLGregorianCalendar>(tstamps); Collections.sort(tstampList, new TstampComparator()); return tstampList; } /** * Helper function that prepares a List timestamps, between the start time and * end time, at the given sampling interval. * * @param startTime The start of the range requested. * @param endTime The start of the range requested. * @param intervalMinutes The sampling interval requested in minutes. * @return The List of XMLGregorianCalendars. */ public static List<XMLGregorianCalendar> getTimestampList(XMLGregorianCalendar startTime, XMLGregorianCalendar endTime, int intervalMinutes) { long intervalMilliseconds; long rangeLength = Tstamp.diff(startTime, endTime); long minutesToMilliseconds = 60L * 1000L; if (intervalMinutes < 0) { return null; } if (rangeLength <= 0) { // either startTime == endTime, or startTime > endTime return null; } else if (intervalMinutes == 0) { // use default interval intervalMilliseconds = rangeLength / 10; } else if ((intervalMinutes * minutesToMilliseconds) > rangeLength) { // TODO BOGUS, should throw an exception so callers can distinguish // between problems return null; } else { // got a good interval intervalMilliseconds = intervalMinutes * minutesToMilliseconds; } // DEBUG // System.out.format("%nstartTime=%s, endTime=%s, interval=%d min%n", // startTime, endTime, // intervalMilliseconds / minutesToMilliseconds); // Build list of timestamps, starting with startTime, separated by // intervalMilliseconds List<XMLGregorianCalendar> timestampList = new ArrayList<XMLGregorianCalendar>(); XMLGregorianCalendar timestamp = startTime; while (Tstamp.lessThan(timestamp, endTime)) { timestampList.add(timestamp); // System.out.format("timestamp=%s%n", timestamp); // DEBUG timestamp = Tstamp.incrementMilliseconds(timestamp, intervalMilliseconds); } // add endTime to cover the last runt interval which is <= // intervalMilliseconds timestampList.add(endTime); // System.out.format("timestamp=%s%n", endTime); // DEBUG return timestampList; } /** * Produces a list of n + 1 XMLGregorianCalendar instances that span the given * time interval by dividing the interval by n. * * @param n The number of timestamps to generate. * @param startTime The start of the interval. * @param endTime The end of the interval. * @return A List<XMLGregorianCalendar> of size n. */ public static List<XMLGregorianCalendar> getNTimestampList(int n, XMLGregorianCalendar startTime, XMLGregorianCalendar endTime) { long rangeLength = Tstamp.diff(startTime, endTime); long sampleIntervalMilliseconds = rangeLength / n; // Build list of timestamps, starting with startTime, separated by // intervalMilliseconds List<XMLGregorianCalendar> timestampList = new ArrayList<XMLGregorianCalendar>(); XMLGregorianCalendar timestamp = startTime; while (Tstamp.lessThan(timestamp, endTime) || Tstamp.equal(timestamp, endTime)) { timestampList.add(timestamp); // System.out.format("timestamp=%s%n", timestamp); // DEBUG timestamp = Tstamp.incrementMilliseconds(timestamp, sampleIntervalMilliseconds); } // add endTime to cover the last runt interval which is <= // intervalMilliseconds timestampList.add(endTime); // System.out.format("timestamp=%s%n", endTime); // DEBUG return timestampList; } }