001 package org.hackystat.utilities.time.period; 002 003 import java.util.Date; 004 import java.util.Locale; 005 import java.text.SimpleDateFormat; 006 import java.util.Calendar; 007 008 import javax.xml.datatype.XMLGregorianCalendar; 009 010 011 /** 012 * Provides a "cousin" of Date that represents only year, month, and day information. Many Hackystat 013 * facilities need date information only at the precision of year/month/day. This abstraction 014 * represents a single 24 hour time period. 015 * <p> 016 * All Day constructors are private or package private. Clients of this class must use the 017 * static getInstance() methods to obtain Day instances. 018 * <p> 019 * The Calendar is forced to Locale.US to ensure constant week boundaries. 020 * <p> 021 * Day instances are immutable. 022 * 023 * @author Philip M. Johnson 024 */ 025 public final class Day implements TimePeriod { 026 027 /** Used to represent the day. */ 028 private Calendar cal = Calendar.getInstance(Locale.US); 029 /** The hashcode used for comparisons. */ 030 private int hashCode = 0; 031 /** The date format for the toString method. */ 032 private static SimpleDateFormat toStringFormat = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); 033 /** Simplified date format. */ 034 private static SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US); 035 /** The formats for the getting the day, month, and year fields as strings. */ 036 private static SimpleDateFormat monthStringFormat = new SimpleDateFormat("MM", Locale.US); 037 /** The formats for the getting the day, month, and year fields as strings. */ 038 private static SimpleDateFormat mediumMonthStringFormat = new SimpleDateFormat("MMM", Locale.US); 039 /** Year string formatter. */ 040 private static SimpleDateFormat yearStringFormat = new SimpleDateFormat("yyyy", Locale.US); 041 /** Day string formatter. */ 042 private static SimpleDateFormat dayStringFormat = new SimpleDateFormat("dd", Locale.US); 043 /** The number of milliseconds in a day. */ 044 private static long millisInDay = 1000 * 60 * 60 * 24; 045 046 /** 047 * Returns a Day instance corresponding to today. 048 * @return A Day instance corresponding to today. 049 */ 050 public static Day getInstance() { 051 return new Day(); 052 } 053 054 /** 055 * Returns a Day instance corresponding to the day associated with timestamp. 056 * 057 * @param timestamp A UTC time from which a Day instance will be returned. 058 * @return A Day instance corresponding to timestamp. 059 */ 060 public static Day getInstance(long timestamp) { 061 return new Day(timestamp); 062 } 063 064 /** 065 * Returns a Day instance corresponding to the passed Date. 066 * 067 * @param date A date from which a Day instance will be returned. 068 * @return The Day instance corresponding to Date. 069 */ 070 public static Day getInstance(Date date) { 071 return new Day(date.getTime()); 072 } 073 074 /** 075 * Returns a Day instance corresponding to the passed year, month, and day. 076 * Historically used for testing, although getInstance(dayString) should be used instead 077 * for clarity's sake unless numeric values are being manipulated directly. 078 * 079 * @param year Year, such as 2003. 080 * @param month Month, such as 0. Note that January is 0! 081 * @param day Day, such as 1. The first day is 1. 082 * @return The Day instance associated with this date. 083 */ 084 public static Day getInstance(int year, int month, int day) { 085 Calendar cal = Calendar.getInstance(Locale.US); 086 cal.set(Calendar.YEAR, year); 087 cal.set(Calendar.MONTH, month); 088 cal.set(Calendar.DAY_OF_MONTH, day); 089 return new Day(cal.getTime().getTime()); 090 } 091 092 /** 093 * Returns a Day instance corresponding to the passed date in dd-MMM-yyyy format. 094 * This creates a temporary SimpleDateFormat, so it shouldn't be used when efficiency is 095 * important, but it's a nice method for test cases where dates are being supplied manually. 096 * 097 * @param dayString A string in dd-MMM-yyyy US Locale format, such as "01-Jan-2004". 098 * @return The Day instance associated with this date. 099 * @throws Exception If problems occur parsing dayString. 100 */ 101 public static Day getInstance(String dayString) throws Exception { 102 Date date = new SimpleDateFormat("dd-MMM-yyyy", Locale.US).parse(dayString); 103 return new Day(date.getTime()); 104 } 105 106 /** 107 * Returns a Day instance associated with this passed XMLGregorianCalendar. 108 * @param xmlDay The date. 109 * @return The corresponding day. 110 */ 111 public static Day getInstance(XMLGregorianCalendar xmlDay) { 112 long millis = xmlDay.toGregorianCalendar().getTimeInMillis(); 113 return new Day(millis); 114 } 115 116 /** 117 * Create a new Day, initializing it to today. 118 */ 119 private Day() { 120 this(new Date()); 121 } 122 123 /** 124 * Creates a new Day instance, initializing it to the passed long UTC time. 125 * 126 * @param timestamp A UTC value indicating a time. 127 */ 128 private Day(long timestamp) { 129 this(new Date(timestamp)); 130 } 131 132 /** 133 * Creates a new Day instance, initializing it from the passed Date. 134 * The internal date is always set to 00:00:00.000 midnight at the 135 * beginning of the passed day. 136 * We do this upfront to make comparisons and interval calculations fast, since we're 137 * caching these Day instances there should be relatively few of them around. 138 * 139 * @param date A date. 140 */ 141 private Day(Date date) { 142 this.cal = Calendar.getInstance(Locale.US); 143 this.cal.setTime(date); 144 this.cal.set(Calendar.HOUR_OF_DAY, 00); 145 this.cal.set(Calendar.MINUTE, 0); 146 this.cal.set(Calendar.SECOND, 0); 147 this.cal.set(Calendar.MILLISECOND, 0); 148 //this.date = cal.getTime(); 149 } 150 151 /** 152 * Returns a Day instance corresponding to a day plus or minus increment days in the future 153 * or past. 154 * 155 * @param increment A positive or negative integer. 156 * @return A Day corresponding to the increment from this day. 157 */ 158 public Day inc(int increment) { 159 Calendar newCal = (Calendar)this.cal.clone(); 160 newCal.add(Calendar.DAY_OF_YEAR, increment); 161 return new Day(newCal.getTime()); 162 } 163 164 /** 165 * Returns a freshly created Date object that corresponds to the current day. 166 * Note that this Date object may not correspond to the same time of day as was 167 * used to create this Day instance! 168 * @return A Date object corresponding to this day. 169 */ 170 public Date getDate() { 171 return new Date(this.cal.getTimeInMillis()); 172 } 173 174 /** 175 * Returns the "first" day in the TimePeriod. For Days, that's just this day. 176 * @return The current day. 177 */ 178 public Day getFirstDay() { 179 return this; 180 } 181 182 /** 183 * Returns the number of days (positive or negative) between day1 and day2. 184 * @param day1 A Day instance. 185 * @param day2 A Day instance. 186 * @return The interval in days between day1 and day2. 187 */ 188 public static int daysBetween(Day day1, Day day2) { 189 return Math.round((float)(day2.cal.getTimeInMillis() - day1.cal.getTimeInMillis()) 190 / millisInDay); 191 } 192 193 /** 194 * Compares two Day objects. 195 * 196 * @param o A Day instance. 197 * @return The standard compareTo values. 198 */ 199 public int compareTo(Object o) { 200 return this.cal.compareTo(((Day)o).cal); 201 } 202 203 /** 204 * Returns true if this Day preceeds the passed Day, false if the two Days are equal or this 205 * day comes after the passed Day. 206 * @param day The day to be compared. 207 * @return True if the passed day comes after this Day. 208 */ 209 public boolean isBefore(Day day) { 210 return (this.cal.before(day.cal)); 211 } 212 213 /** 214 * Two Day instances are equal() iff they have equal year, month, and day fields. 215 * 216 * @param obj Any object. 217 * @return True if obj is a Day and it's equal to this Day. 218 */ 219 @Override 220 public boolean equals(Object obj) { 221 if (!(obj instanceof Day)) { 222 return false; 223 } 224 Calendar thisCal = this.cal; 225 Calendar otherCal = ((Day)obj).cal; 226 return 227 ((thisCal.get(Calendar.YEAR) == otherCal.get(Calendar.YEAR)) && 228 (thisCal.get(Calendar.DAY_OF_YEAR) == otherCal.get(Calendar.DAY_OF_YEAR))); 229 } 230 231 232 /** 233 * Compute the hashcode following recommendations in "Effective Java". 234 * 235 * @return The hashcode. 236 */ 237 @Override 238 public int hashCode() { 239 if (this.hashCode == 0) { 240 this.hashCode = 17; 241 this.hashCode = 37 * this.hashCode + this.cal.get(Calendar.YEAR); 242 this.hashCode = 37 * this.hashCode + this.cal.get(Calendar.MONTH); 243 this.hashCode = 37 * this.hashCode + this.cal.get(Calendar.DAY_OF_MONTH); 244 } 245 return this.hashCode; 246 } 247 248 /** 249 * Returns this Day instance in YYYY-MM-DD format. 250 * 251 * @return The day as a string. 252 */ 253 @Override 254 public String toString() { 255 synchronized (Day.toStringFormat) { 256 return Day.toStringFormat.format(new Date(this.cal.getTimeInMillis())); 257 } 258 } 259 260 /** 261 * Gets the simple date string in 2004-03-25 format. 262 * 263 * @return Simply date format. 264 */ 265 public String getSimpleDayString() { 266 synchronized (Day.simpleFormat) { 267 return Day.simpleFormat.format(new Date(this.cal.getTimeInMillis())); 268 } 269 } 270 271 272 /** 273 * Returns a two character string representing this Day's day (00-31). 274 * 275 * @return The day as a string. 276 */ 277 public String getDayString() { 278 synchronized (Day.dayStringFormat) { 279 return Day.dayStringFormat.format(new Date(this.cal.getTimeInMillis())); 280 } 281 } 282 283 /** 284 * Returns a two character string representing this Day's month (01-12). 285 * 286 * @return The month as a string. 287 */ 288 public String getMonthString() { 289 synchronized (Day.monthStringFormat) { 290 return Day.monthStringFormat.format(new Date(this.cal.getTimeInMillis())); 291 } 292 } 293 294 295 /** 296 * Returns a three character string representing this Day's month, e.g. Jan, Feb. 297 * 298 * @return The month as a string. 299 */ 300 public String getMediumMonthString() { 301 synchronized (Day.mediumMonthStringFormat) { 302 return Day.mediumMonthStringFormat.format(new Date(this.cal.getTimeInMillis())); 303 } 304 } 305 306 /** 307 * Returns a four character string representing this Day's year (2000, etc.). 308 * 309 * @return The year as a string. 310 */ 311 public String getYearString() { 312 synchronized (Day.yearStringFormat) { 313 return Day.yearStringFormat.format(new Date(this.cal.getTimeInMillis())); 314 } 315 } 316 317 /** 318 * Gets the first tick of the day. 319 * 320 * @return The first tick of the day in millis. 321 */ 322 public long getFirstTickOfTheDay() { 323 return this.cal.getTimeInMillis(); 324 } 325 326 /** 327 * Gets the last tick of the day. 328 * 329 * @return The last tick of the day in millis. 330 */ 331 public long getLastTickOfTheDay() { 332 Calendar newCal = Calendar.getInstance(Locale.US); 333 newCal.setTimeInMillis(this.cal.getTimeInMillis()); 334 newCal.set(Calendar.HOUR_OF_DAY, newCal.getActualMaximum(Calendar.HOUR_OF_DAY)); 335 newCal.set(Calendar.MINUTE, newCal.getActualMaximum(Calendar.MINUTE)); 336 newCal.set(Calendar.SECOND, newCal.getActualMaximum(Calendar.SECOND)); 337 newCal.set(Calendar.MILLISECOND, newCal.getActualMaximum(Calendar.MILLISECOND)); 338 return newCal.getTime().getTime(); 339 } 340 } 341