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 }