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