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