001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert 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-present, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): -; 034 * 035 */ 036 037package org.jfree.data.time; 038 039import java.io.Serializable; 040import java.text.DateFormat; 041import java.text.ParseException; 042import java.text.SimpleDateFormat; 043import java.util.*; 044 045import org.jfree.chart.date.SerialDate; 046import org.jfree.chart.util.Args; 047 048/** 049 * Represents a single day in the range 1-Jan-1900 to 31-Dec-9999. This class 050 * is immutable, which is a requirement for all {@link RegularTimePeriod} 051 * subclasses. 052 */ 053public class Day extends RegularTimePeriod implements Serializable { 054 055 /** For serialization. */ 056 private static final long serialVersionUID = -7082667380758962755L; 057 058 /** A standard date formatter. */ 059 protected static final DateFormat DATE_FORMAT 060 = new SimpleDateFormat("yyyy-MM-dd"); 061 062 /** A date formatter for the default locale. */ 063 protected static final DateFormat DATE_FORMAT_SHORT 064 = DateFormat.getDateInstance(DateFormat.SHORT); 065 066 /** A date formatter for the default locale. */ 067 protected static final DateFormat DATE_FORMAT_MEDIUM 068 = DateFormat.getDateInstance(DateFormat.MEDIUM); 069 070 /** A date formatter for the default locale. */ 071 protected static final DateFormat DATE_FORMAT_LONG 072 = DateFormat.getDateInstance(DateFormat.LONG); 073 074 /** The day (uses SerialDate for convenience). */ 075 private SerialDate serialDate; 076 077 /** The first millisecond. */ 078 private long firstMillisecond; 079 080 /** The last millisecond. */ 081 private long lastMillisecond; 082 083 /** 084 * Creates a new instance, derived from the system date/time. 085 * The time zone and locale are determined by the calendar 086 * returned by {@link RegularTimePeriod#getCalendarInstance()}. 087 */ 088 public Day() { 089 this(new Date()); 090 } 091 092 /** 093 * Constructs a new one day time period. 094 * The time zone and locale are determined by the calendar 095 * returned by {@link RegularTimePeriod#getCalendarInstance()}. 096 * 097 * @param day the day-of-the-month. 098 * @param month the month (1 to 12). 099 * @param year the year (1900 <= year <= 9999). 100 */ 101 public Day(int day, int month, int year) { 102 this.serialDate = SerialDate.createInstance(day, month, year); 103 peg(getCalendarInstance()); 104 } 105 106 /** 107 * Constructs a new one day time period. 108 * The time zone and locale are determined by the calendar 109 * returned by {@link RegularTimePeriod#getCalendarInstance()}. 110 * 111 * @param serialDate the day ({@code null} not permitted). 112 */ 113 public Day(SerialDate serialDate) { 114 Args.nullNotPermitted(serialDate, "serialDate"); 115 this.serialDate = serialDate; 116 peg(getCalendarInstance()); 117 } 118 119 /** 120 * Constructs a new instance, based on a particular date/time. 121 * The time zone and locale are determined by the calendar 122 * returned by {@link RegularTimePeriod#getCalendarInstance()}. 123 * 124 * @param time the time ({@code null} not permitted). 125 * 126 * @see #Day(Date, TimeZone, Locale) 127 */ 128 public Day(Date time) { 129 // defer argument checking... 130 this(time, getCalendarInstance()); 131 } 132 133 /** 134 * Constructs a new instance, based on a particular date/time and time zone. 135 * 136 * @param time the date/time ({@code null} not permitted). 137 * @param zone the time zone ({@code null} not permitted). 138 * @param locale the locale ({@code null} not permitted). 139 */ 140 public Day(Date time, TimeZone zone, Locale locale) { 141 Args.nullNotPermitted(time, "time"); 142 Args.nullNotPermitted(zone, "zone"); 143 Args.nullNotPermitted(locale, "locale"); 144 Calendar calendar = Calendar.getInstance(zone, locale); 145 calendar.setTime(time); 146 initUsing(calendar); 147 peg(calendar); 148 } 149 150 /** 151 * Constructs a new instance, based on a particular date/time. 152 * The time zone and locale are determined by the {@code calendar} 153 * parameter. 154 * 155 * @param time the date/time ({@code null} not permitted). 156 * @param calendar the calendar to use for calculations ({@code null} not permitted). 157 */ 158 public Day(Date time, Calendar calendar) { 159 Args.nullNotPermitted(time, "time"); 160 Args.nullNotPermitted(calendar, "calendar"); 161 calendar.setTime(time); 162 initUsing(calendar); 163 peg(calendar); 164 } 165 166 private void initUsing(Calendar calendar) { 167 int d = calendar.get(Calendar.DAY_OF_MONTH); 168 int m = calendar.get(Calendar.MONTH) + 1; 169 int y = calendar.get(Calendar.YEAR); 170 this.serialDate = SerialDate.createInstance(d, m, y); 171 } 172 173 /** 174 * Returns the day as a {@link SerialDate}. Note: the reference that is 175 * returned should be an instance of an immutable {@link SerialDate} 176 * (otherwise the caller could use the reference to alter the state of 177 * this {@code Day} instance, and {@code Day} is supposed 178 * to be immutable). 179 * 180 * @return The day as a {@link SerialDate}. 181 */ 182 public SerialDate getSerialDate() { 183 return this.serialDate; 184 } 185 186 /** 187 * Returns the year. 188 * 189 * @return The year. 190 */ 191 public int getYear() { 192 return this.serialDate.getYYYY(); 193 } 194 195 /** 196 * Returns the month. 197 * 198 * @return The month. 199 */ 200 public int getMonth() { 201 return this.serialDate.getMonth(); 202 } 203 204 /** 205 * Returns the day of the month. 206 * 207 * @return The day of the month. 208 */ 209 public int getDayOfMonth() { 210 return this.serialDate.getDayOfMonth(); 211 } 212 213 /** 214 * Returns the first millisecond of the day. This will be determined 215 * relative to the time zone specified in the constructor, or in the 216 * calendar instance passed in the most recent call to the 217 * {@link #peg(Calendar)} method. 218 * 219 * @return The first millisecond of the day. 220 * 221 * @see #getLastMillisecond() 222 */ 223 @Override 224 public long getFirstMillisecond() { 225 return this.firstMillisecond; 226 } 227 228 /** 229 * Returns the last millisecond of the day. This will be 230 * determined relative to the time zone specified in the constructor, or 231 * in the calendar instance passed in the most recent call to the 232 * {@link #peg(Calendar)} method. 233 * 234 * @return The last millisecond of the day. 235 * 236 * @see #getFirstMillisecond() 237 */ 238 @Override 239 public long getLastMillisecond() { 240 return this.lastMillisecond; 241 } 242 243 /** 244 * Recalculates the start date/time and end date/time for this time period 245 * relative to the supplied calendar (which incorporates a time zone). 246 * 247 * @param calendar the calendar ({@code null} not permitted). 248 */ 249 @Override 250 public void peg(Calendar calendar) { 251 this.firstMillisecond = getFirstMillisecond(calendar); 252 this.lastMillisecond = getLastMillisecond(calendar); 253 } 254 255 /** 256 * Returns the day preceding this one. 257 * No matter what time zone and locale this instance was created with, 258 * the returned instance will use the default calendar for time 259 * calculations, obtained with {@link RegularTimePeriod#getCalendarInstance()}. 260 * 261 * @return The day preceding this one. 262 */ 263 @Override 264 public RegularTimePeriod previous() { 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 * Returns the day following this one, or {@code null} if some limit 279 * has been reached. 280 * No matter what time zone and locale this instance was created with, 281 * the returned instance will use the default calendar for time 282 * calculations, obtained with {@link RegularTimePeriod#getCalendarInstance()}. 283 * 284 * @return The day following this one, or {@code null} if some limit 285 * has been reached. 286 */ 287 @Override 288 public RegularTimePeriod next() { 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 * Returns a serial index number for the day. 303 * 304 * @return The serial index number. 305 */ 306 @Override 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} not permitted). 316 * 317 * @return The start of the day as milliseconds since 01-01-1970. 318 * 319 * @throws NullPointerException if {@code calendar} is 320 * {@code null}. 321 */ 322 @Override 323 public long getFirstMillisecond(Calendar calendar) { 324 int year = this.serialDate.getYYYY(); 325 int month = this.serialDate.getMonth(); 326 int day = this.serialDate.getDayOfMonth(); 327 calendar.clear(); 328 calendar.set(year, month - 1, day, 0, 0, 0); 329 calendar.set(Calendar.MILLISECOND, 0); 330 return calendar.getTimeInMillis(); 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} not permitted). 338 * 339 * @return The end of the day as milliseconds since 01-01-1970. 340 * 341 * @throws NullPointerException if {@code calendar} is 342 * {@code null}. 343 */ 344 @Override 345 public long getLastMillisecond(Calendar calendar) { 346 int year = this.serialDate.getYYYY(); 347 int month = this.serialDate.getMonth(); 348 int day = this.serialDate.getDayOfMonth(); 349 calendar.clear(); 350 calendar.set(year, month - 1, day, 23, 59, 59); 351 calendar.set(Calendar.MILLISECOND, 999); 352 return calendar.getTimeInMillis(); 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} permitted). 362 * 363 * @return A flag indicating whether or not an object is equal to this day. 364 */ 365 @Override 366 public boolean equals(Object obj) { 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 * Returns a hash code for this object instance. The approach described by 382 * Joshua Bloch in "Effective Java" has been used here: 383 * <p> 384 * {@code http://developer.java.sun.com/developer/Books/effectivejava 385 * /Chapter3.pdf} 386 * 387 * @return A hash code. 388 */ 389 @Override 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 @Override 405 public int compareTo(Object o1) { 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 * Returns a string representing the day. 434 * 435 * @return A string representing the day. 436 */ 437 @Override 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} if the string does not contain any parseable 451 * string, the day otherwise. 452 */ 453 public static Day parseDay(String s) { 454 try { 455 return new Day (Day.DATE_FORMAT.parse(s)); 456 } 457 catch (ParseException e1) { 458 try { 459 return new Day (Day.DATE_FORMAT_SHORT.parse(s)); 460 } 461 catch (ParseException e2) { 462 // ignore 463 } 464 } 465 return null; 466 } 467 468}