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 }