001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, 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 * Minute.java
029 * -----------
030 * (C) Copyright 2001-2008, 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 * 18-Dec-2001 : Changed order of parameters in constructor (DG);
039 * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG);
040 * 14-Feb-2002 : Fixed bug in Minute(Date) constructor, and changed the range
041 * to start from zero instead of one (DG);
042 * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to
043 * evaluate with reference to a particular time zone (DG);
044 * 13-Mar-2002 : Added parseMinute() method (DG);
045 * 19-Mar-2002 : Changed API, the minute is now defined in relation to an
046 * Hour (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, and new constructor for
053 * convenience (DG);
054 * 30-Sep-2004 : Replaced getTime().getTime() with getTimeInMillis() (DG);
055 * 04-Nov-2004 : Reverted change of 30-Sep-2004, because it won't work for
056 * JDK 1.3 (DG);
057 * ------------- JFREECHART 1.0.x ---------------------------------------------
058 * 05-Oct-2006 : Updated API docs (DG);
059 * 06-Oct-2006 : Refactored to cache first and last millisecond values (DG);
060 * 11-Dec-2006 : Fix for previous() - bug 1611872 (DG);
061 * 16-Sep-2008 : Deprecated DEFAULT_TIME_ZONE (DG);
062 *
063 */
064
065 package org.jfree.data.time;
066
067 import java.io.Serializable;
068 import java.util.Calendar;
069 import java.util.Date;
070 import java.util.TimeZone;
071
072 /**
073 * Represents a minute. This class is immutable, which is a requirement for
074 * all {@link RegularTimePeriod} subclasses.
075 */
076 public class Minute extends RegularTimePeriod implements Serializable {
077
078 /** For serialization. */
079 private static final long serialVersionUID = 2144572840034842871L;
080
081 /** Useful constant for the first minute in a day. */
082 public static final int FIRST_MINUTE_IN_HOUR = 0;
083
084 /** Useful constant for the last minute in a day. */
085 public static final int LAST_MINUTE_IN_HOUR = 59;
086
087 /** The day. */
088 private Day day;
089
090 /** The hour in which the minute falls. */
091 private byte hour;
092
093 /** The minute. */
094 private byte minute;
095
096 /** The first millisecond. */
097 private long firstMillisecond;
098
099 /** The last millisecond. */
100 private long lastMillisecond;
101
102 /**
103 * Constructs a new Minute, based on the system date/time.
104 */
105 public Minute() {
106 this(new Date());
107 }
108
109 /**
110 * Constructs a new Minute.
111 *
112 * @param minute the minute (0 to 59).
113 * @param hour the hour (<code>null</code> not permitted).
114 */
115 public Minute(int minute, Hour hour) {
116 if (hour == null) {
117 throw new IllegalArgumentException("Null 'hour' argument.");
118 }
119 this.minute = (byte) minute;
120 this.hour = (byte) hour.getHour();
121 this.day = hour.getDay();
122 peg(Calendar.getInstance());
123 }
124
125 /**
126 * Constructs a new instance, based on the supplied date/time and
127 * the default time zone.
128 *
129 * @param time the time (<code>null</code> not permitted).
130 *
131 * @see #Minute(Date, TimeZone)
132 */
133 public Minute(Date time) {
134 // defer argument checking
135 this(time, TimeZone.getDefault());
136 }
137
138 /**
139 * Constructs a new Minute, based on the supplied date/time and timezone.
140 *
141 * @param time the time (<code>null</code> not permitted).
142 * @param zone the time zone (<code>null</code> not permitted).
143 */
144 public Minute(Date time, TimeZone zone) {
145 // FIXME: need a locale as well as a timezone
146 if (time == null) {
147 throw new IllegalArgumentException("Null 'time' argument.");
148 }
149 if (zone == null) {
150 throw new IllegalArgumentException("Null 'zone' argument.");
151 }
152 Calendar calendar = Calendar.getInstance(zone);
153 calendar.setTime(time);
154 int min = calendar.get(Calendar.MINUTE);
155 this.minute = (byte) min;
156 this.hour = (byte) calendar.get(Calendar.HOUR_OF_DAY);
157 this.day = new Day(time, zone);
158 peg(calendar);
159 }
160
161 /**
162 * Creates a new minute.
163 *
164 * @param minute the minute (0-59).
165 * @param hour the hour (0-23).
166 * @param day the day (1-31).
167 * @param month the month (1-12).
168 * @param year the year (1900-9999).
169 */
170 public Minute(int minute,
171 int hour,
172 int day,
173 int month,
174 int year) {
175 this(minute, new Hour(hour, new Day(day, month, year)));
176 }
177
178 /**
179 * Returns the day.
180 *
181 * @return The day.
182 *
183 * @since 1.0.3
184 */
185 public Day getDay() {
186 return this.day;
187 }
188
189 /**
190 * Returns the hour.
191 *
192 * @return The hour (never <code>null</code>).
193 */
194 public Hour getHour() {
195 return new Hour(this.hour, this.day);
196 }
197
198 /**
199 * Returns the hour.
200 *
201 * @return The hour.
202 *
203 * @since 1.0.3
204 */
205 public int getHourValue() {
206 return this.hour;
207 }
208
209 /**
210 * Returns the minute.
211 *
212 * @return The minute.
213 */
214 public int getMinute() {
215 return this.minute;
216 }
217
218 /**
219 * Returns the first millisecond of the minute. 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 minute.
225 *
226 * @see #getLastMillisecond()
227 */
228 public long getFirstMillisecond() {
229 return this.firstMillisecond;
230 }
231
232 /**
233 * Returns the last millisecond of the minute. 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 minute.
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 minute preceding this one.
261 *
262 * @return The minute preceding this one.
263 */
264 public RegularTimePeriod previous() {
265 Minute result;
266 if (this.minute != FIRST_MINUTE_IN_HOUR) {
267 result = new Minute(this.minute - 1, getHour());
268 }
269 else {
270 Hour h = (Hour) getHour().previous();
271 if (h != null) {
272 result = new Minute(LAST_MINUTE_IN_HOUR, h);
273 }
274 else {
275 result = null;
276 }
277 }
278 return result;
279 }
280
281 /**
282 * Returns the minute following this one.
283 *
284 * @return The minute following this one.
285 */
286 public RegularTimePeriod next() {
287
288 Minute result;
289 if (this.minute != LAST_MINUTE_IN_HOUR) {
290 result = new Minute(this.minute + 1, getHour());
291 }
292 else { // we are at the last minute in the hour...
293 Hour nextHour = (Hour) getHour().next();
294 if (nextHour != null) {
295 result = new Minute(FIRST_MINUTE_IN_HOUR, nextHour);
296 }
297 else {
298 result = null;
299 }
300 }
301 return result;
302
303 }
304
305 /**
306 * Returns a serial index number for the minute.
307 *
308 * @return The serial index number.
309 */
310 public long getSerialIndex() {
311 long hourIndex = this.day.getSerialIndex() * 24L + this.hour;
312 return hourIndex * 60L + this.minute;
313 }
314
315 /**
316 * Returns the first millisecond of the minute.
317 *
318 * @param calendar the calendar which defines the timezone
319 * (<code>null</code> not permitted).
320 *
321 * @return The first millisecond.
322 *
323 * @throws NullPointerException if <code>calendar</code> is
324 * <code>null</code>.
325 */
326 public long getFirstMillisecond(Calendar calendar) {
327
328 int year = this.day.getYear();
329 int month = this.day.getMonth() - 1;
330 int day = this.day.getDayOfMonth();
331
332 calendar.clear();
333 calendar.set(year, month, day, this.hour, this.minute, 0);
334 calendar.set(Calendar.MILLISECOND, 0);
335
336 //return calendar.getTimeInMillis(); // this won't work for JDK 1.3
337 return calendar.getTime().getTime();
338
339 }
340
341 /**
342 * Returns the last millisecond of the minute.
343 *
344 * @param calendar the calendar / timezone (<code>null</code> not
345 * permitted).
346 *
347 * @return The last millisecond.
348 *
349 * @throws NullPointerException if <code>calendar</code> is
350 * <code>null</code>.
351 */
352 public long getLastMillisecond(Calendar calendar) {
353
354 int year = this.day.getYear();
355 int month = this.day.getMonth() - 1;
356 int day = this.day.getDayOfMonth();
357
358 calendar.clear();
359 calendar.set(year, month, day, this.hour, this.minute, 59);
360 calendar.set(Calendar.MILLISECOND, 999);
361
362 //return calendar.getTimeInMillis(); // this won't work for JDK 1.3
363 return calendar.getTime().getTime();
364
365 }
366
367 /**
368 * Tests the equality of this object against an arbitrary Object.
369 * <P>
370 * This method will return true ONLY if the object is a Minute object
371 * representing the same minute as this instance.
372 *
373 * @param obj the object to compare (<code>null</code> permitted).
374 *
375 * @return <code>true</code> if the minute and hour value of this and the
376 * object are the same.
377 */
378 public boolean equals(Object obj) {
379 if (obj == this) {
380 return true;
381 }
382 if (!(obj instanceof Minute)) {
383 return false;
384 }
385 Minute that = (Minute) obj;
386 if (this.minute != that.minute) {
387 return false;
388 }
389 if (this.hour != that.hour) {
390 return false;
391 }
392 return true;
393 }
394
395 /**
396 * Returns a hash code for this object instance. The approach described
397 * by 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 public int hashCode() {
405 int result = 17;
406 result = 37 * result + this.minute;
407 result = 37 * result + this.hour;
408 result = 37 * result + this.day.hashCode();
409 return result;
410 }
411
412 /**
413 * Returns an integer indicating the order of this Minute object relative
414 * to the specified object:
415 *
416 * negative == before, zero == same, positive == after.
417 *
418 * @param o1 object to compare.
419 *
420 * @return negative == before, zero == same, positive == after.
421 */
422 public int compareTo(Object o1) {
423
424 int result;
425
426 // CASE 1 : Comparing to another Minute object
427 // -------------------------------------------
428 if (o1 instanceof Minute) {
429 Minute m = (Minute) o1;
430 result = getHour().compareTo(m.getHour());
431 if (result == 0) {
432 result = this.minute - m.getMinute();
433 }
434 }
435
436 // CASE 2 : Comparing to another TimePeriod object
437 // -----------------------------------------------
438 else if (o1 instanceof RegularTimePeriod) {
439 // more difficult case - evaluate later...
440 result = 0;
441 }
442
443 // CASE 3 : Comparing to a non-TimePeriod object
444 // ---------------------------------------------
445 else {
446 // consider time periods to be ordered after general objects
447 result = 1;
448 }
449
450 return result;
451
452 }
453
454 /**
455 * Creates a Minute instance by parsing a string. The string is assumed to
456 * be in the format "YYYY-MM-DD HH:MM", perhaps with leading or trailing
457 * whitespace.
458 *
459 * @param s the minute string to parse.
460 *
461 * @return <code>null</code>, if the string is not parseable, the minute
462 * otherwise.
463 */
464 public static Minute parseMinute(String s) {
465
466 Minute result = null;
467 s = s.trim();
468
469 String daystr = s.substring(0, Math.min(10, s.length()));
470 Day day = Day.parseDay(daystr);
471 if (day != null) {
472 String hmstr = s.substring(
473 Math.min(daystr.length() + 1, s.length()), s.length()
474 );
475 hmstr = hmstr.trim();
476
477 String hourstr = hmstr.substring(0, Math.min(2, hmstr.length()));
478 int hour = Integer.parseInt(hourstr);
479
480 if ((hour >= 0) && (hour <= 23)) {
481 String minstr = hmstr.substring(
482 Math.min(hourstr.length() + 1, hmstr.length()),
483 hmstr.length()
484 );
485 int minute = Integer.parseInt(minstr);
486 if ((minute >= 0) && (minute <= 59)) {
487 result = new Minute(minute, new Hour(hour, day));
488 }
489 }
490 }
491
492 return result;
493
494 }
495
496 }