001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * -------- 028 * Day.java 029 * -------- 030 * (C) Copyright 2001-2008, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes 036 * ------- 037 * 11-Oct-2001 : Version 1 (DG); 038 * 15-Nov-2001 : Updated Javadoc comments (DG); 039 * 04-Dec-2001 : Added static method to parse a string into a Day object (DG); 040 * 19-Dec-2001 : Added new constructor as suggested by Paul English (DG); 041 * 29-Jan-2002 : Changed getDay() method to getSerialDate() (DG); 042 * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to 043 * evaluate with reference to a particular time zone (DG); 044 * 19-Mar-2002 : Changed the API for the TimePeriod classes (DG); 045 * 29-May-2002 : Fixed bug in equals method (DG); 046 * 24-Jun-2002 : Removed unnecessary imports (DG); 047 * 10-Sep-2002 : Added getSerialIndex() method (DG); 048 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG); 049 * 10-Jan-2003 : Changed base class and method names (DG); 050 * 13-Mar-2003 : Moved to com.jrefinery.data.time package, and implemented 051 * Serializable (DG); 052 * 21-Oct-2003 : Added hashCode() method (DG); 053 * 30-Sep-2004 : Replaced getTime().getTime() with getTimeInMillis() (DG); 054 * 04-Nov-2004 : Reverted change of 30-Sep-2004, because it won't work for 055 * JDK 1.3 (DG); 056 * ------------- JFREECHART 1.0.x --------------------------------------------- 057 * 05-Oct-2006 : Updated API docs (DG); 058 * 06-Oct-2006 : Refactored to cache first and last millisecond values (DG); 059 * 16-Sep-2008 : Deprecated DEFAULT_TIME_ZONE (DG); 060 * 061 */ 062 063 package org.jfree.data.time; 064 065 import java.io.Serializable; 066 import java.text.DateFormat; 067 import java.text.ParseException; 068 import java.text.SimpleDateFormat; 069 import java.util.Calendar; 070 import java.util.Date; 071 import java.util.TimeZone; 072 073 import org.jfree.date.SerialDate; 074 075 /** 076 * Represents a single day in the range 1-Jan-1900 to 31-Dec-9999. This class 077 * is immutable, which is a requirement for all {@link RegularTimePeriod} 078 * subclasses. 079 */ 080 public class Day extends RegularTimePeriod implements Serializable { 081 082 /** For serialization. */ 083 private static final long serialVersionUID = -7082667380758962755L; 084 085 /** A standard date formatter. */ 086 protected static final DateFormat DATE_FORMAT 087 = new SimpleDateFormat("yyyy-MM-dd"); 088 089 /** A date formatter for the default locale. */ 090 protected static final DateFormat 091 DATE_FORMAT_SHORT = DateFormat.getDateInstance(DateFormat.SHORT); 092 093 /** A date formatter for the default locale. */ 094 protected static final DateFormat 095 DATE_FORMAT_MEDIUM = DateFormat.getDateInstance(DateFormat.MEDIUM); 096 097 /** A date formatter for the default locale. */ 098 protected static final DateFormat 099 DATE_FORMAT_LONG = DateFormat.getDateInstance(DateFormat.LONG); 100 101 /** The day (uses SerialDate for convenience). */ 102 private SerialDate serialDate; 103 104 /** The first millisecond. */ 105 private long firstMillisecond; 106 107 /** The last millisecond. */ 108 private long lastMillisecond; 109 110 /** 111 * Creates a new instance, derived from the system date/time (and assuming 112 * the default timezone). 113 */ 114 public Day() { 115 this(new Date()); 116 } 117 118 /** 119 * Constructs a new one day time period. 120 * 121 * @param day the day-of-the-month. 122 * @param month the month (1 to 12). 123 * @param year the year (1900 <= year <= 9999). 124 */ 125 public Day(int day, int month, int year) { 126 this.serialDate = SerialDate.createInstance(day, month, year); 127 peg(Calendar.getInstance()); 128 } 129 130 /** 131 * Constructs a new one day time period. 132 * 133 * @param serialDate the day (<code>null</code> not permitted). 134 */ 135 public Day(SerialDate serialDate) { 136 if (serialDate == null) { 137 throw new IllegalArgumentException("Null 'serialDate' argument."); 138 } 139 this.serialDate = serialDate; 140 peg(Calendar.getInstance()); 141 } 142 143 /** 144 * Constructs a new instance, based on a particular date/time and the 145 * default time zone. 146 * 147 * @param time the time (<code>null</code> not permitted). 148 * 149 * @see #Day(Date, TimeZone) 150 */ 151 public Day(Date time) { 152 // defer argument checking... 153 this(time, TimeZone.getDefault()); 154 } 155 156 /** 157 * Constructs a new instance, based on a particular date/time and time zone. 158 * 159 * @param time the date/time. 160 * @param zone the time zone. 161 */ 162 public Day(Date time, TimeZone zone) { 163 // FIXME: need a Locale as well as a TimeZone 164 if (time == null) { 165 throw new IllegalArgumentException("Null 'time' argument."); 166 } 167 if (zone == null) { 168 throw new IllegalArgumentException("Null 'zone' argument."); 169 } 170 Calendar calendar = Calendar.getInstance(zone); 171 calendar.setTime(time); 172 int d = calendar.get(Calendar.DAY_OF_MONTH); 173 int m = calendar.get(Calendar.MONTH) + 1; 174 int y = calendar.get(Calendar.YEAR); 175 this.serialDate = SerialDate.createInstance(d, m, y); 176 peg(calendar); 177 } 178 179 /** 180 * Returns the day as a {@link SerialDate}. Note: the reference that is 181 * returned should be an instance of an immutable {@link SerialDate} 182 * (otherwise the caller could use the reference to alter the state of 183 * this <code>Day</code> instance, and <code>Day</code> is supposed 184 * to be immutable). 185 * 186 * @return The day as a {@link SerialDate}. 187 */ 188 public SerialDate getSerialDate() { 189 return this.serialDate; 190 } 191 192 /** 193 * Returns the year. 194 * 195 * @return The year. 196 */ 197 public int getYear() { 198 return this.serialDate.getYYYY(); 199 } 200 201 /** 202 * Returns the month. 203 * 204 * @return The month. 205 */ 206 public int getMonth() { 207 return this.serialDate.getMonth(); 208 } 209 210 /** 211 * Returns the day of the month. 212 * 213 * @return The day of the month. 214 */ 215 public int getDayOfMonth() { 216 return this.serialDate.getDayOfMonth(); 217 } 218 219 /** 220 * Returns the first millisecond of the day. This will be determined 221 * relative to the time zone specified in the constructor, or in the 222 * calendar instance passed in the most recent call to the 223 * {@link #peg(Calendar)} method. 224 * 225 * @return The first millisecond of the day. 226 * 227 * @see #getLastMillisecond() 228 */ 229 public long getFirstMillisecond() { 230 return this.firstMillisecond; 231 } 232 233 /** 234 * Returns the last millisecond of the day. This will be 235 * determined relative to the time zone specified in the constructor, or 236 * in the calendar instance passed in the most recent call to the 237 * {@link #peg(Calendar)} method. 238 * 239 * @return The last millisecond of the day. 240 * 241 * @see #getFirstMillisecond() 242 */ 243 public long getLastMillisecond() { 244 return this.lastMillisecond; 245 } 246 247 /** 248 * Recalculates the start date/time and end date/time for this time period 249 * relative to the supplied calendar (which incorporates a time zone). 250 * 251 * @param calendar the calendar (<code>null</code> not permitted). 252 * 253 * @since 1.0.3 254 */ 255 public void peg(Calendar calendar) { 256 this.firstMillisecond = getFirstMillisecond(calendar); 257 this.lastMillisecond = getLastMillisecond(calendar); 258 } 259 260 /** 261 * Returns the day preceding this one. 262 * 263 * @return The day preceding this one. 264 */ 265 public RegularTimePeriod previous() { 266 267 Day result; 268 int serial = this.serialDate.toSerial(); 269 if (serial > SerialDate.SERIAL_LOWER_BOUND) { 270 SerialDate yesterday = SerialDate.createInstance(serial - 1); 271 return new Day(yesterday); 272 } 273 else { 274 result = null; 275 } 276 return result; 277 278 } 279 280 /** 281 * Returns the day following this one, or <code>null</code> if some limit 282 * has been reached. 283 * 284 * @return The day following this one, or <code>null</code> if some limit 285 * has been reached. 286 */ 287 public RegularTimePeriod next() { 288 289 Day result; 290 int serial = this.serialDate.toSerial(); 291 if (serial < SerialDate.SERIAL_UPPER_BOUND) { 292 SerialDate tomorrow = SerialDate.createInstance(serial + 1); 293 return new Day(tomorrow); 294 } 295 else { 296 result = null; 297 } 298 return result; 299 300 } 301 302 /** 303 * Returns a serial index number for the day. 304 * 305 * @return The serial index number. 306 */ 307 public long getSerialIndex() { 308 return this.serialDate.toSerial(); 309 } 310 311 /** 312 * Returns the first millisecond of the day, evaluated using the supplied 313 * calendar (which determines the time zone). 314 * 315 * @param calendar calendar to use (<code>null</code> not permitted). 316 * 317 * @return The start of the day as milliseconds since 01-01-1970. 318 * 319 * @throws NullPointerException if <code>calendar</code> is 320 * <code>null</code>. 321 */ 322 public long getFirstMillisecond(Calendar calendar) { 323 int year = this.serialDate.getYYYY(); 324 int month = this.serialDate.getMonth(); 325 int day = this.serialDate.getDayOfMonth(); 326 calendar.clear(); 327 calendar.set(year, month - 1, day, 0, 0, 0); 328 calendar.set(Calendar.MILLISECOND, 0); 329 //return calendar.getTimeInMillis(); // this won't work for JDK 1.3 330 return calendar.getTime().getTime(); 331 } 332 333 /** 334 * Returns the last millisecond of the day, evaluated using the supplied 335 * calendar (which determines the time zone). 336 * 337 * @param calendar calendar to use (<code>null</code> not permitted). 338 * 339 * @return The end of the day as milliseconds since 01-01-1970. 340 * 341 * @throws NullPointerException if <code>calendar</code> is 342 * <code>null</code>. 343 */ 344 public long getLastMillisecond(Calendar calendar) { 345 int year = this.serialDate.getYYYY(); 346 int month = this.serialDate.getMonth(); 347 int day = this.serialDate.getDayOfMonth(); 348 calendar.clear(); 349 calendar.set(year, month - 1, day, 23, 59, 59); 350 calendar.set(Calendar.MILLISECOND, 999); 351 //return calendar.getTimeInMillis(); // this won't work for JDK 1.3 352 return calendar.getTime().getTime(); 353 } 354 355 /** 356 * Tests the equality of this Day object to an arbitrary object. Returns 357 * true if the target is a Day instance or a SerialDate instance 358 * representing the same day as this object. In all other cases, 359 * returns false. 360 * 361 * @param obj the object (<code>null</code> permitted). 362 * 363 * @return A flag indicating whether or not an object is equal to this day. 364 */ 365 public boolean equals(Object obj) { 366 367 if (obj == this) { 368 return true; 369 } 370 if (!(obj instanceof Day)) { 371 return false; 372 } 373 Day that = (Day) obj; 374 if (!this.serialDate.equals(that.getSerialDate())) { 375 return false; 376 } 377 return true; 378 379 } 380 381 /** 382 * Returns a hash code for this object instance. The approach described by 383 * Joshua Bloch in "Effective Java" has been used here: 384 * <p> 385 * <code>http://developer.java.sun.com/developer/Books/effectivejava 386 * /Chapter3.pdf</code> 387 * 388 * @return A hash code. 389 */ 390 public int hashCode() { 391 return this.serialDate.hashCode(); 392 } 393 394 /** 395 * Returns an integer indicating the order of this Day object relative to 396 * the specified object: 397 * 398 * negative == before, zero == same, positive == after. 399 * 400 * @param o1 the object to compare. 401 * 402 * @return negative == before, zero == same, positive == after. 403 */ 404 public int compareTo(Object o1) { 405 406 int result; 407 408 // CASE 1 : Comparing to another Day object 409 // ---------------------------------------- 410 if (o1 instanceof Day) { 411 Day d = (Day) o1; 412 result = -d.getSerialDate().compare(this.serialDate); 413 } 414 415 // CASE 2 : Comparing to another TimePeriod object 416 // ----------------------------------------------- 417 else if (o1 instanceof RegularTimePeriod) { 418 // more difficult case - evaluate later... 419 result = 0; 420 } 421 422 // CASE 3 : Comparing to a non-TimePeriod object 423 // --------------------------------------------- 424 else { 425 // consider time periods to be ordered after general objects 426 result = 1; 427 } 428 429 return result; 430 431 } 432 433 /** 434 * Returns a string representing the day. 435 * 436 * @return A string representing the day. 437 */ 438 public String toString() { 439 return this.serialDate.toString(); 440 } 441 442 /** 443 * Parses the string argument as a day. 444 * <P> 445 * This method is required to recognise YYYY-MM-DD as a valid format. 446 * Anything else, for now, is a bonus. 447 * 448 * @param s the date string to parse. 449 * 450 * @return <code>null</code> if the string does not contain any parseable 451 * string, the day otherwise. 452 */ 453 public static Day parseDay(String s) { 454 455 try { 456 return new Day (Day.DATE_FORMAT.parse(s)); 457 } 458 catch (ParseException e1) { 459 try { 460 return new Day (Day.DATE_FORMAT_SHORT.parse(s)); 461 } 462 catch (ParseException e2) { 463 // ignore 464 } 465 } 466 return null; 467 468 } 469 470 }