001    package org.hackystat.utilities.tstamp;
002    
003    import java.util.ArrayList;
004    import java.util.Calendar;
005    import java.util.Collection;
006    import java.util.Collections;
007    import java.util.GregorianCalendar;
008    import java.util.List;
009    import javax.xml.datatype.DatatypeConfigurationException;
010    import javax.xml.datatype.DatatypeFactory;
011    import javax.xml.datatype.XMLGregorianCalendar;
012    
013    import org.hackystat.utilities.time.period.Day;
014    
015    /**
016     * Utility class that facilitates Timestamp representation and processing. There
017     * are too many classes already named "Timestamp", thus the abbreviated name.
018     * @author Philip Johnson
019     */
020    public final class Tstamp {
021      
022      /** Make this class noninstantiable. */
023      private Tstamp() {
024        // Do nothing.
025      }
026    
027      private static final String factoryErrorMsg = "Bad DataTypeFactory";
028      
029      private static long MILLISECS_PER_DAY = 24 * 60 * 60 * 1000;
030      
031    
032      /**
033       * Returns true if the passed string can be parsed into an
034       * XMLGregorianCalendar object.
035       * @param lexicalRepresentation The string representation.
036       * @return True if the string is a legal XMLGregorianCalendar.
037       */
038      public static boolean isTimestamp(String lexicalRepresentation) {
039        try {
040          DatatypeFactory factory = DatatypeFactory.newInstance();
041          factory.newXMLGregorianCalendar(lexicalRepresentation);
042          return true;
043    
044        }
045        catch (Exception e) {
046          return false;
047        }
048      }
049    
050      /**
051       * Returns an XMLGregorianCalendar, given its string representation.
052       * Missing hours, minutes, second, millisecond, and timezone fields are given defaults.
053       * @param rep The string representation.
054       * @return The timestamp.
055       * @throws Exception If the string cannot be parsed into a timestamp.
056       */
057      public static XMLGregorianCalendar makeTimestamp(String rep)
058          throws Exception {
059        DatatypeFactory factory = DatatypeFactory.newInstance();
060        long mills = factory.newXMLGregorianCalendar(rep).toGregorianCalendar().getTimeInMillis();  
061        return makeTimestamp(mills);
062      }
063    
064      /**
065       * Converts a javax.sql.Timestamp into a
066       * javax.xml.datatype.XMLGregorianCalendar.
067       * @param tstamp The javax.sql.Timestamp
068       * @return A new instance of a javax.xml.datatype.XmlGregorianCalendar
069       */
070      public static XMLGregorianCalendar makeTimestamp(java.sql.Timestamp tstamp) {
071        DatatypeFactory factory = null;
072        try {
073          factory = DatatypeFactory.newInstance();
074          GregorianCalendar calendar = new GregorianCalendar();
075          calendar.setTimeInMillis(tstamp.getTime());
076          return factory.newXMLGregorianCalendar(calendar);
077        }
078        catch (DatatypeConfigurationException e) {
079          throw new RuntimeException(factoryErrorMsg, e);
080        }
081      }
082    
083      /**
084       * Converts the specified time in milliseconds into a
085       * javax.xml.datatype.XMLGregorianCalendar.
086       * @param timeInMillis the specified time in milliseconds to convert.
087       * @return A new instance of a javax.xml.datatype.XmlGregorianCalendar
088       */
089      public static XMLGregorianCalendar makeTimestamp(long timeInMillis) {
090        DatatypeFactory factory = null;
091        try {
092          factory = DatatypeFactory.newInstance();
093          GregorianCalendar calendar = new GregorianCalendar();
094          calendar.setTimeInMillis(timeInMillis);
095          return factory.newXMLGregorianCalendar(calendar);
096        }
097        catch (DatatypeConfigurationException e) {
098          throw new RuntimeException(factoryErrorMsg, e);
099        }
100      }
101      
102      /**
103       * Converts the specified Day into a javax.xml.datatype.XMLGregorianCalendar.
104       * @param day The day to be converted.
105       * @return A new instance of a javax.xml.datatype.XmlGregorianCalendar.
106       */
107      public static XMLGregorianCalendar makeTimestamp(Day day) {
108        DatatypeFactory factory = null;
109        try {
110          factory = DatatypeFactory.newInstance();
111          GregorianCalendar calendar = new GregorianCalendar();
112          calendar.setTimeInMillis(day.getDate().getTime());
113          return factory.newXMLGregorianCalendar(calendar);
114        }
115        catch (DatatypeConfigurationException e) {
116          throw new RuntimeException(factoryErrorMsg, e);
117        }
118      }
119    
120      /**
121       * Returns a new XMLGregorianCalendar corresponding to the passed tstamp
122       * incremented by the number of days.
123       * @param tstamp The base date and time.
124       * @param days The number of days to increment. This can be a negative number.
125       * @return A new XMLGregorianCalendar instance representing the inc'd time.
126       */
127      public static XMLGregorianCalendar incrementDays(XMLGregorianCalendar tstamp, int days) {
128        DatatypeFactory factory = null;
129        try {
130          factory = DatatypeFactory.newInstance();
131          GregorianCalendar calendar = new GregorianCalendar();
132          long millis = tstamp.toGregorianCalendar().getTimeInMillis();
133          millis += 1000L * 60 * 60 * 24 * days;
134          calendar.setTimeInMillis(millis);
135          return factory.newXMLGregorianCalendar(calendar);
136        }
137        catch (DatatypeConfigurationException e) {
138          throw new RuntimeException(factoryErrorMsg, e);
139        }
140      }
141    
142      /**
143       * Returns a new XMLGregorianCalendar corresponding to the passed tstamp
144       * incremented by the number of hours.
145       * @param tstamp The base date and time.
146       * @param hours The number of hours to increment. This can be a negative
147       * number.
148       * @return A new XMLGregorianCalendar instance representing the inc'd time.
149       */
150      public static XMLGregorianCalendar incrementHours(XMLGregorianCalendar tstamp, int hours) {
151        DatatypeFactory factory = null;
152        try {
153          factory = DatatypeFactory.newInstance();
154          GregorianCalendar calendar = new GregorianCalendar();
155          long millis = tstamp.toGregorianCalendar().getTimeInMillis();
156          millis += 1000L * 60 * 60 * hours;
157          calendar.setTimeInMillis(millis);
158          return factory.newXMLGregorianCalendar(calendar);
159        }
160        catch (DatatypeConfigurationException e) {
161          throw new RuntimeException(factoryErrorMsg, e);
162        }
163      }
164    
165      /**
166       * Returns a new XMLGregorianCalendar corresponding to the passed tstamp
167       * incremented by the number of minutes.
168       * @param tstamp The base date and time.
169       * @param minutes The number of minutes to increment. This can be a negative
170       * number.
171       * @return A new XMLGregorianCalendar instance representing the inc'd time.
172       */
173      public static XMLGregorianCalendar incrementMinutes(XMLGregorianCalendar tstamp, int minutes) {
174        DatatypeFactory factory = null;
175        try {
176          factory = DatatypeFactory.newInstance();
177          GregorianCalendar calendar = new GregorianCalendar();
178          long millis = tstamp.toGregorianCalendar().getTimeInMillis();
179          millis += 1000L * 60 * minutes;
180          calendar.setTimeInMillis(millis);
181          return factory.newXMLGregorianCalendar(calendar);
182        }
183        catch (DatatypeConfigurationException e) {
184          throw new RuntimeException(factoryErrorMsg, e);
185        }
186      }
187    
188      /**
189       * Returns a new XMLGregorianCalendar corresponding to the passed tstamp
190       * incremented by the number of seconds.
191       * @param tstamp The base date and time.
192       * @param seconds The number of seconds to increment. This can be a negative
193       * number.
194       * @return A new XMLGregorianCalendar instance representing the inc'd time.
195       */
196      public static XMLGregorianCalendar incrementSeconds(XMLGregorianCalendar tstamp, int seconds) {
197        DatatypeFactory factory = null;
198        try {
199          factory = DatatypeFactory.newInstance();
200          GregorianCalendar calendar = new GregorianCalendar();
201          long millis = tstamp.toGregorianCalendar().getTimeInMillis();
202          millis += 1000L * seconds;
203          calendar.setTimeInMillis(millis);
204          return factory.newXMLGregorianCalendar(calendar);
205        }
206        catch (DatatypeConfigurationException e) {
207          throw new RuntimeException(factoryErrorMsg, e);
208        }
209      }
210      
211      /**
212       * Returns a new XMLGregorianCalendar corresponding to the passed tstamp
213       * incremented by the number of milliseconds.
214       * @param tstamp The base date and time.
215       * @param milliseconds The number of milliseconds to increment. This can be a negative
216       * number.
217       * @return A new XMLGregorianCalendar instance representing the inc'd time.
218       */
219      public static XMLGregorianCalendar incrementMilliseconds(XMLGregorianCalendar tstamp, 
220          long milliseconds) {
221        DatatypeFactory factory = null;
222        try {
223          factory = DatatypeFactory.newInstance();
224          GregorianCalendar calendar = new GregorianCalendar();
225          long millis = tstamp.toGregorianCalendar().getTimeInMillis();
226          millis += milliseconds;
227          calendar.setTimeInMillis(millis);
228          return factory.newXMLGregorianCalendar(calendar);
229        }
230        catch (DatatypeConfigurationException e) {
231          throw new RuntimeException(factoryErrorMsg, e);
232        }
233      }
234    
235      /**
236       * Returns a new java.sql.Timestamp created from a
237       * javax.xml.datatype.XMLGregorianCalendar.
238       * @param calendar The XML timestamp.
239       * @return The SQL timestamp.
240       */
241      public static java.sql.Timestamp makeTimestamp(XMLGregorianCalendar calendar) {
242        return new java.sql.Timestamp(calendar.toGregorianCalendar().getTimeInMillis());
243      }
244    
245      /**
246       * Returns an XMLGregorianCalendar corresponding to the current time.
247       * @return The timestamp.
248       */
249      public static XMLGregorianCalendar makeTimestamp() {
250        try {
251          DatatypeFactory factory = DatatypeFactory.newInstance();
252          return factory.newXMLGregorianCalendar(new GregorianCalendar());
253        }
254        catch (Exception e) {
255          throw new RuntimeException(factoryErrorMsg, e);
256        }
257      }
258    
259      /**
260       * Returns the start time for the Default project, which is 2000-01-01. 
261       * @return A timestamp representing 2000-01-01.
262       */
263      public static XMLGregorianCalendar getDefaultProjectStartTime() {
264        XMLGregorianCalendar tstamp = Tstamp.makeTimestamp();
265        tstamp.setYear(2000);
266        tstamp.setMonth(1);
267        tstamp.setDay(1);
268        return tstamp;
269      }
270    
271      /**
272       * Returns the end time for the Default project, which is five years after today.
273       * Note that this is updated every time the server starts up. 
274       * @return The timestamp for the Default project end time. 
275       */
276      public static XMLGregorianCalendar getDefaultProjectEndTime() {
277        return Tstamp.incrementDays(Tstamp.makeTimestamp(), (365 * 5));
278      }
279    
280      
281      /**
282       * In the early days of Hackystat, default project start times were 1000-01-01.
283       * This was stupid.  The following hack exists to correct projects containing this old 
284       * value.  This code and its callers can be removed when the disease is eradicated.
285       * @param startTime The startTime in question.
286       * @return True if it's before 1950.
287       */
288      public static boolean isBogusStartTime(XMLGregorianCalendar startTime) {
289        try {
290          XMLGregorianCalendar bogusTime = Tstamp.makeTimestamp("1950-01-01");
291          return Tstamp.lessThan(startTime, bogusTime);
292        }
293        catch (Exception e) {
294          return true;
295        }
296      }
297    
298      /**
299       * Returns true if tstamp is equal to or between start and end.
300       * @param start The start time.
301       * @param tstamp The timestamp to test.
302       * @param end The end time.
303       * @return True if tstamp is between start and end.
304       */
305      public static boolean inBetween(XMLGregorianCalendar start, XMLGregorianCalendar tstamp,
306          XMLGregorianCalendar end) {
307        long startMillis = start.toGregorianCalendar().getTimeInMillis();
308        long endMillis = end.toGregorianCalendar().getTimeInMillis();
309        long tstampMillis = tstamp.toGregorianCalendar().getTimeInMillis();
310        return ((tstampMillis >= startMillis) && (tstampMillis <= endMillis));
311      }
312    
313      /**
314       * Returns true if time1 > time2.
315       * @param time1 The first time.
316       * @param time2 The second time.
317       * @return True if time1 > time2
318       */
319      public static boolean greaterThan(XMLGregorianCalendar time1, XMLGregorianCalendar time2) {
320        long time1Millis = time1.toGregorianCalendar().getTimeInMillis();
321        long time2Millis = time2.toGregorianCalendar().getTimeInMillis();
322        return (time1Millis > time2Millis);
323      }
324      
325      /**
326       * Returns the number of days between time1 and time2.
327       * Returns a negative number if day1 is after day2.
328       * Takes into account daylight savings time issues. 
329       * @param day1 The first day.
330       * @param day2 The second day. 
331       * @return The number of days between the two days. 
332       */
333      public static int daysBetween(XMLGregorianCalendar day1, XMLGregorianCalendar day2) {
334        return (int) (getUnixDay(day2) - getUnixDay(day1));
335      }
336      
337      /**
338       * Returns a long representing the number of days since the Unix epoch to the passed day.
339       * @param day The day of interest.
340       * @return The number of days since the epoch.
341       */
342      private static long getUnixDay(XMLGregorianCalendar day) {
343        GregorianCalendar greg = day.toGregorianCalendar();
344        long offset = greg.get(Calendar.ZONE_OFFSET) + greg.get(Calendar.DST_OFFSET);
345        long daysSinceEpoch = (long)Math.floor((double)(greg.getTime().getTime() + offset ) / 
346            ((double)MILLISECS_PER_DAY) );
347        return daysSinceEpoch;
348    }
349     
350    
351      /**
352       * Returns true if timeString1 > timeString2. Throws an unchecked
353       * IllegalArgument exception if the strings can't be converted to timestamps.
354       * @param timeString1 The first time.
355       * @param timeString2 The second time.
356       * @return True if time1 > time2
357       */
358      public static boolean greaterThan(String timeString1, String timeString2) {
359        try {
360          DatatypeFactory factory = DatatypeFactory.newInstance();
361          XMLGregorianCalendar time1 = factory.newXMLGregorianCalendar(timeString1);
362          XMLGregorianCalendar time2 = factory.newXMLGregorianCalendar(timeString2);
363          return greaterThan(time1, time2);
364        }
365        catch (Exception e) {
366          throw new IllegalArgumentException("Illegal timestring", e);
367        }
368      }
369    
370      /**
371       * Returns true if time1 < time2.
372       * @param time1 The first time.
373       * @param time2 The second time.
374       * @return True if time1 < time2
375       */
376      public static boolean lessThan(XMLGregorianCalendar time1, XMLGregorianCalendar time2) {
377        long time1Millis = time1.toGregorianCalendar().getTimeInMillis();
378        long time2Millis = time2.toGregorianCalendar().getTimeInMillis();
379        return (time1Millis < time2Millis);
380      }
381    
382      /**
383       * Returns true if time1 equals time2. 
384       * @param time1 The first time.
385       * @param time2 The second time.
386       * @return True if time1 equals time2
387       */
388      public static boolean equal(XMLGregorianCalendar time1, XMLGregorianCalendar time2) {
389        long millis1 = time1.toGregorianCalendar().getTimeInMillis();
390        long millis2 = time2.toGregorianCalendar().getTimeInMillis();
391        return (millis1 == millis2);
392      }
393    
394      /**
395       * Returns differences between time1 and time2 in milliseconds.
396       * 
397       * @param time1 Start.
398       * @param time2 End.
399       * @return Difference between two times in milliseconds.
400       */
401      public static long diff(XMLGregorianCalendar time1, XMLGregorianCalendar time2) {
402        long millis1 = time1.toGregorianCalendar().getTimeInMillis();
403        long millis2 = time2.toGregorianCalendar().getTimeInMillis();
404        return millis2 - millis1;
405      }
406      
407      /**
408       * Returns true if the passed timestamp indicates some time today or some time 
409       * in the future. 
410       * 
411       * @param timestamp The timestamp of interest.
412       * @return True if it's today or some day in the future. 
413       */
414      public static boolean isTodayOrLater(XMLGregorianCalendar timestamp) {
415        XMLGregorianCalendar today = Tstamp.makeTimestamp();
416        boolean isToday = (today.getYear() == timestamp.getYear()) && 
417                          (today.getMonth() == timestamp.getMonth()) && 
418                          (today.getDay() == timestamp.getDay());
419        boolean afterToday = today.toGregorianCalendar().getTimeInMillis() <
420                           timestamp.toGregorianCalendar().getTimeInMillis();
421        return (isToday || afterToday);
422      }
423      
424      /**
425       * Returns true if the passed timestamp indicates some time yesterday or some time 
426       * in the future. This is useful for the Commit/Churn DPDs, where the sensor typically
427       * sends data not from the current day, but from the day before. 
428       * 
429       * @param timestamp The timestamp of interest.
430       * @return True if it's today or some day in the future. 
431       */
432      public static boolean isYesterdayOrLater(XMLGregorianCalendar timestamp) {
433        XMLGregorianCalendar yesterday = Tstamp.incrementDays(Tstamp.makeTimestamp(), -1);
434    
435        boolean isYesterday = (yesterday.getYear() == timestamp.getYear()) && 
436                              (yesterday.getMonth() == timestamp.getMonth()) && 
437                              (yesterday.getDay() == timestamp.getDay());
438        boolean afterYesterday = yesterday.toGregorianCalendar().getTimeInMillis() <
439                                 timestamp.toGregorianCalendar().getTimeInMillis();
440        return (isYesterday || afterYesterday);
441      }
442      
443      /**
444       * Returns a newly created sorted list of tstamps from the passed collection.
445       * @param tstamps The timestamps to be sorted. 
446       * @return A new list of tstamps, now in sorted order. 
447       */
448      public static List<XMLGregorianCalendar> sort(Collection<XMLGregorianCalendar> tstamps) {
449        List<XMLGregorianCalendar> tstampList = new ArrayList<XMLGregorianCalendar>(tstamps);
450        Collections.sort(tstampList, new TstampComparator());
451        return tstampList;
452      }
453    }