001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2009, 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-2009, 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 * 02-Mar-2009 : Added new constructor that specifies Locale (DG);
063 *
064 */
065
066 package org.jfree.data.time;
067
068 import java.io.Serializable;
069 import java.util.Calendar;
070 import java.util.Date;
071 import java.util.Locale;
072 import java.util.TimeZone;
073
074 /**
075 * Represents a minute. This class is immutable, which is a requirement for
076 * all {@link RegularTimePeriod} subclasses.
077 */
078 public class Minute extends RegularTimePeriod implements Serializable {
079
080 /** For serialization. */
081 private static final long serialVersionUID = 2144572840034842871L;
082
083 /** Useful constant for the first minute in a day. */
084 public static final int FIRST_MINUTE_IN_HOUR = 0;
085
086 /** Useful constant for the last minute in a day. */
087 public static final int LAST_MINUTE_IN_HOUR = 59;
088
089 /** The day. */
090 private Day day;
091
092 /** The hour in which the minute falls. */
093 private byte hour;
094
095 /** The minute. */
096 private byte minute;
097
098 /** The first millisecond. */
099 private long firstMillisecond;
100
101 /** The last millisecond. */
102 private long lastMillisecond;
103
104 /**
105 * Constructs a new Minute, based on the system date/time.
106 */
107 public Minute() {
108 this(new Date());
109 }
110
111 /**
112 * Constructs a new Minute.
113 *
114 * @param minute the minute (0 to 59).
115 * @param hour the hour (<code>null</code> not permitted).
116 */
117 public Minute(int minute, Hour hour) {
118 if (hour == null) {
119 throw new IllegalArgumentException("Null 'hour' argument.");
120 }
121 this.minute = (byte) minute;
122 this.hour = (byte) hour.getHour();
123 this.day = hour.getDay();
124 peg(Calendar.getInstance());
125 }
126
127 /**
128 * Constructs a new instance, based on the supplied date/time and
129 * the default time zone.
130 *
131 * @param time the time (<code>null</code> not permitted).
132 *
133 * @see #Minute(Date, TimeZone)
134 */
135 public Minute(Date time) {
136 // defer argument checking
137 this(time, TimeZone.getDefault(), Locale.getDefault());
138 }
139
140 /**
141 * Constructs a new Minute, based on the supplied date/time and timezone.
142 *
143 * @param time the time (<code>null</code> not permitted).
144 * @param zone the time zone (<code>null</code> not permitted).
145 *
146 * @deprecated As of 1.0.13, use the constructor that specifies the locale
147 * also.
148 */
149 public Minute(Date time, TimeZone zone) {
150 this(time, zone, Locale.getDefault());
151 }
152
153 /**
154 * Constructs a new Minute, based on the supplied date/time and timezone.
155 *
156 * @param time the time (<code>null</code> not permitted).
157 * @param zone the time zone (<code>null</code> not permitted).
158 * @param locale the locale (<code>null</code> not permitted).
159 *
160 * @since 1.0.13
161 */
162 public Minute(Date time, TimeZone zone, Locale locale) {
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 if (locale == null) {
170 throw new IllegalArgumentException("Null 'locale' argument.");
171 }
172 Calendar calendar = Calendar.getInstance(zone, locale);
173 calendar.setTime(time);
174 int min = calendar.get(Calendar.MINUTE);
175 this.minute = (byte) min;
176 this.hour = (byte) calendar.get(Calendar.HOUR_OF_DAY);
177 this.day = new Day(time, zone, locale);
178 peg(calendar);
179 }
180
181 /**
182 * Creates a new minute.
183 *
184 * @param minute the minute (0-59).
185 * @param hour the hour (0-23).
186 * @param day the day (1-31).
187 * @param month the month (1-12).
188 * @param year the year (1900-9999).
189 */
190 public Minute(int minute, int hour, int day, int month, int year) {
191 this(minute, new Hour(hour, new Day(day, month, year)));
192 }
193
194 /**
195 * Returns the day.
196 *
197 * @return The day.
198 *
199 * @since 1.0.3
200 */
201 public Day getDay() {
202 return this.day;
203 }
204
205 /**
206 * Returns the hour.
207 *
208 * @return The hour (never <code>null</code>).
209 */
210 public Hour getHour() {
211 return new Hour(this.hour, this.day);
212 }
213
214 /**
215 * Returns the hour.
216 *
217 * @return The hour.
218 *
219 * @since 1.0.3
220 */
221 public int getHourValue() {
222 return this.hour;
223 }
224
225 /**
226 * Returns the minute.
227 *
228 * @return The minute.
229 */
230 public int getMinute() {
231 return this.minute;
232 }
233
234 /**
235 * Returns the first millisecond of the minute. This will be determined
236 * relative to the time zone specified in the constructor, or in the
237 * calendar instance passed in the most recent call to the
238 * {@link #peg(Calendar)} method.
239 *
240 * @return The first millisecond of the minute.
241 *
242 * @see #getLastMillisecond()
243 */
244 public long getFirstMillisecond() {
245 return this.firstMillisecond;
246 }
247
248 /**
249 * Returns the last millisecond of the minute. This will be
250 * determined relative to the time zone specified in the constructor, or
251 * in the calendar instance passed in the most recent call to the
252 * {@link #peg(Calendar)} method.
253 *
254 * @return The last millisecond of the minute.
255 *
256 * @see #getFirstMillisecond()
257 */
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 public void peg(Calendar calendar) {
271 this.firstMillisecond = getFirstMillisecond(calendar);
272 this.lastMillisecond = getLastMillisecond(calendar);
273 }
274
275 /**
276 * Returns the minute preceding this one.
277 *
278 * @return The minute preceding this one.
279 */
280 public RegularTimePeriod previous() {
281 Minute result;
282 if (this.minute != FIRST_MINUTE_IN_HOUR) {
283 result = new Minute(this.minute - 1, getHour());
284 }
285 else {
286 Hour h = (Hour) getHour().previous();
287 if (h != null) {
288 result = new Minute(LAST_MINUTE_IN_HOUR, h);
289 }
290 else {
291 result = null;
292 }
293 }
294 return result;
295 }
296
297 /**
298 * Returns the minute following this one.
299 *
300 * @return The minute following this one.
301 */
302 public RegularTimePeriod next() {
303 Minute result;
304 if (this.minute != LAST_MINUTE_IN_HOUR) {
305 result = new Minute(this.minute + 1, getHour());
306 }
307 else { // we are at the last minute in the hour...
308 Hour nextHour = (Hour) getHour().next();
309 if (nextHour != null) {
310 result = new Minute(FIRST_MINUTE_IN_HOUR, nextHour);
311 }
312 else {
313 result = null;
314 }
315 }
316 return result;
317 }
318
319 /**
320 * Returns a serial index number for the minute.
321 *
322 * @return The serial index number.
323 */
324 public long getSerialIndex() {
325 long hourIndex = this.day.getSerialIndex() * 24L + this.hour;
326 return hourIndex * 60L + this.minute;
327 }
328
329 /**
330 * Returns the first millisecond of the minute.
331 *
332 * @param calendar the calendar which defines the timezone
333 * (<code>null</code> not permitted).
334 *
335 * @return The first millisecond.
336 *
337 * @throws NullPointerException if <code>calendar</code> is
338 * <code>null</code>.
339 */
340 public long getFirstMillisecond(Calendar calendar) {
341 int year = this.day.getYear();
342 int month = this.day.getMonth() - 1;
343 int day = this.day.getDayOfMonth();
344
345 calendar.clear();
346 calendar.set(year, month, day, this.hour, this.minute, 0);
347 calendar.set(Calendar.MILLISECOND, 0);
348
349 //return calendar.getTimeInMillis(); // this won't work for JDK 1.3
350 return calendar.getTime().getTime();
351 }
352
353 /**
354 * Returns the last millisecond of the minute.
355 *
356 * @param calendar the calendar / timezone (<code>null</code> not
357 * permitted).
358 *
359 * @return The last millisecond.
360 *
361 * @throws NullPointerException if <code>calendar</code> is
362 * <code>null</code>.
363 */
364 public long getLastMillisecond(Calendar calendar) {
365 int year = this.day.getYear();
366 int month = this.day.getMonth() - 1;
367 int day = this.day.getDayOfMonth();
368
369 calendar.clear();
370 calendar.set(year, month, day, this.hour, this.minute, 59);
371 calendar.set(Calendar.MILLISECOND, 999);
372
373 //return calendar.getTimeInMillis(); // this won't work for JDK 1.3
374 return calendar.getTime().getTime();
375 }
376
377 /**
378 * Tests the equality of this object against an arbitrary Object.
379 * <P>
380 * This method will return true ONLY if the object is a Minute object
381 * representing the same minute as this instance.
382 *
383 * @param obj the object to compare (<code>null</code> permitted).
384 *
385 * @return <code>true</code> if the minute and hour value of this and the
386 * object are the same.
387 */
388 public boolean equals(Object obj) {
389 if (obj == this) {
390 return true;
391 }
392 if (!(obj instanceof Minute)) {
393 return false;
394 }
395 Minute that = (Minute) obj;
396 if (this.minute != that.minute) {
397 return false;
398 }
399 if (this.hour != that.hour) {
400 return false;
401 }
402 return true;
403 }
404
405 /**
406 * Returns a hash code for this object instance. The approach described
407 * by Joshua Bloch in "Effective Java" has been used here:
408 * <p>
409 * <code>http://developer.java.sun.com/developer/Books/effectivejava
410 * /Chapter3.pdf</code>
411 *
412 * @return A hash code.
413 */
414 public int hashCode() {
415 int result = 17;
416 result = 37 * result + this.minute;
417 result = 37 * result + this.hour;
418 result = 37 * result + this.day.hashCode();
419 return result;
420 }
421
422 /**
423 * Returns an integer indicating the order of this Minute object relative
424 * to the specified object:
425 *
426 * negative == before, zero == same, positive == after.
427 *
428 * @param o1 object to compare.
429 *
430 * @return negative == before, zero == same, positive == after.
431 */
432 public int compareTo(Object o1) {
433 int result;
434
435 // CASE 1 : Comparing to another Minute object
436 // -------------------------------------------
437 if (o1 instanceof Minute) {
438 Minute m = (Minute) o1;
439 result = getHour().compareTo(m.getHour());
440 if (result == 0) {
441 result = this.minute - m.getMinute();
442 }
443 }
444
445 // CASE 2 : Comparing to another TimePeriod object
446 // -----------------------------------------------
447 else if (o1 instanceof RegularTimePeriod) {
448 // more difficult case - evaluate later...
449 result = 0;
450 }
451
452 // CASE 3 : Comparing to a non-TimePeriod object
453 // ---------------------------------------------
454 else {
455 // consider time periods to be ordered after general objects
456 result = 1;
457 }
458
459 return result;
460 }
461
462 /**
463 * Creates a Minute instance by parsing a string. The string is assumed to
464 * be in the format "YYYY-MM-DD HH:MM", perhaps with leading or trailing
465 * whitespace.
466 *
467 * @param s the minute string to parse.
468 *
469 * @return <code>null</code>, if the string is not parseable, the minute
470 * otherwise.
471 */
472 public static Minute parseMinute(String s) {
473 Minute result = null;
474 s = s.trim();
475
476 String daystr = s.substring(0, Math.min(10, s.length()));
477 Day day = Day.parseDay(daystr);
478 if (day != null) {
479 String hmstr = s.substring(
480 Math.min(daystr.length() + 1, s.length()), s.length()
481 );
482 hmstr = hmstr.trim();
483
484 String hourstr = hmstr.substring(0, Math.min(2, hmstr.length()));
485 int hour = Integer.parseInt(hourstr);
486
487 if ((hour >= 0) && (hour <= 23)) {
488 String minstr = hmstr.substring(
489 Math.min(hourstr.length() + 1, hmstr.length()),
490 hmstr.length()
491 );
492 int minute = Integer.parseInt(minstr);
493 if ((minute >= 0) && (minute <= 59)) {
494 result = new Minute(minute, new Hour(hour, day));
495 }
496 }
497 }
498 return result;
499 }
500
501 }