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 = new SimpleDateFormat(
088: "yyyy-MM-dd");
089:
090: /** A date formatter for the default locale. */
091: protected static final DateFormat DATE_FORMAT_SHORT = DateFormat
092: .getDateInstance(DateFormat.SHORT);
093:
094: /** A date formatter for the default locale. */
095: protected static final DateFormat DATE_FORMAT_MEDIUM = DateFormat
096: .getDateInstance(DateFormat.MEDIUM);
097:
098: /** A date formatter for the default locale. */
099: protected static final DateFormat DATE_FORMAT_LONG = DateFormat
100: .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(
139: "Null 'serialDate' argument.");
140: }
141: this .serialDate = serialDate;
142: peg(Calendar.getInstance());
143: }
144:
145: /**
146: * Constructs a new instance, based on a particular date/time and the
147: * default time zone.
148: *
149: * @param time the time (<code>null</code> not permitted).
150: */
151: public Day(Date time) {
152: // defer argument checking...
153: this (time, RegularTimePeriod.DEFAULT_TIME_ZONE);
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: if (time == null) {
164: throw new IllegalArgumentException("Null 'time' argument.");
165: }
166: if (zone == null) {
167: throw new IllegalArgumentException("Null 'zone' argument.");
168: }
169: Calendar calendar = Calendar.getInstance(zone);
170: calendar.setTime(time);
171: int d = calendar.get(Calendar.DAY_OF_MONTH);
172: int m = calendar.get(Calendar.MONTH) + 1;
173: int y = calendar.get(Calendar.YEAR);
174: this .serialDate = SerialDate.createInstance(d, m, y);
175: peg(calendar);
176: }
177:
178: /**
179: * Returns the day as a {@link SerialDate}. Note: the reference that is
180: * returned should be an instance of an immutable {@link SerialDate}
181: * (otherwise the caller could use the reference to alter the state of
182: * this <code>Day</code> instance, and <code>Day</code> is supposed
183: * to be immutable).
184: *
185: * @return The day as a {@link SerialDate}.
186: */
187: public SerialDate getSerialDate() {
188: return this .serialDate;
189: }
190:
191: /**
192: * Returns the year.
193: *
194: * @return The year.
195: */
196: public int getYear() {
197: return this .serialDate.getYYYY();
198: }
199:
200: /**
201: * Returns the month.
202: *
203: * @return The month.
204: */
205: public int getMonth() {
206: return this .serialDate.getMonth();
207: }
208:
209: /**
210: * Returns the day of the month.
211: *
212: * @return The day of the month.
213: */
214: public int getDayOfMonth() {
215: return this .serialDate.getDayOfMonth();
216: }
217:
218: /**
219: * Returns the first millisecond of the day. This will be determined
220: * relative to the time zone specified in the constructor, or in the
221: * calendar instance passed in the most recent call to the
222: * {@link #peg(Calendar)} method.
223: *
224: * @return The first millisecond of the day.
225: *
226: * @see #getLastMillisecond()
227: */
228: public long getFirstMillisecond() {
229: return this .firstMillisecond;
230: }
231:
232: /**
233: * Returns the last millisecond of the day. This will be
234: * determined relative to the time zone specified in the constructor, or
235: * in the calendar instance passed in the most recent call to the
236: * {@link #peg(Calendar)} method.
237: *
238: * @return The last millisecond of the day.
239: *
240: * @see #getFirstMillisecond()
241: */
242: public long getLastMillisecond() {
243: return this .lastMillisecond;
244: }
245:
246: /**
247: * Recalculates the start date/time and end date/time for this time period
248: * relative to the supplied calendar (which incorporates a time zone).
249: *
250: * @param calendar the calendar (<code>null</code> not permitted).
251: *
252: * @since 1.0.3
253: */
254: public void peg(Calendar calendar) {
255: this .firstMillisecond = getFirstMillisecond(calendar);
256: this .lastMillisecond = getLastMillisecond(calendar);
257: }
258:
259: /**
260: * Returns the day preceding this one.
261: *
262: * @return The day preceding this one.
263: */
264: public RegularTimePeriod previous() {
265:
266: Day result;
267: int serial = this .serialDate.toSerial();
268: if (serial > SerialDate.SERIAL_LOWER_BOUND) {
269: SerialDate yesterday = SerialDate
270: .createInstance(serial - 1);
271: return new Day(yesterday);
272: } else {
273: result = null;
274: }
275: return result;
276:
277: }
278:
279: /**
280: * Returns the day following this one, or <code>null</code> if some limit
281: * has been reached.
282: *
283: * @return The day following this one, or <code>null</code> if some limit
284: * has been reached.
285: */
286: public RegularTimePeriod next() {
287:
288: Day result;
289: int serial = this .serialDate.toSerial();
290: if (serial < SerialDate.SERIAL_UPPER_BOUND) {
291: SerialDate tomorrow = SerialDate.createInstance(serial + 1);
292: return new Day(tomorrow);
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: } catch (ParseException e1) {
456: try {
457: return new Day(Day.DATE_FORMAT_SHORT.parse(s));
458: } catch (ParseException e2) {
459: // ignore
460: }
461: }
462: return null;
463:
464: }
465:
466: }
|