1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
6  *
7  * Project Info:  http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
22  * USA.
23  *
24  * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
25  * Other names may be trademarks of their respective owners.]
26  *
27  * -----------
28  * Minute.java
29  * -----------
30  * (C) Copyright 2001-2013, by Object Refinery Limited.
31  *
32  * Original Author:  David Gilbert (for Object Refinery Limited);
33  * Contributor(s):   -;
34  *
35  * Changes
36  * -------
37  * 11-Oct-2001 : Version 1 (DG);
38  * 18-Dec-2001 : Changed order of parameters in constructor (DG);
39  * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG);
40  * 14-Feb-2002 : Fixed bug in Minute(Date) constructor, and changed the range
41  *               to start from zero instead of one (DG);
42  * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to
43  *               evaluate with reference to a particular time zone (DG);
44  * 13-Mar-2002 : Added parseMinute() method (DG);
45  * 19-Mar-2002 : Changed API, the minute is now defined in relation to an
46  *               Hour (DG);
47  * 10-Sep-2002 : Added getSerialIndex() method (DG);
48  * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
49  * 10-Jan-2003 : Changed base class and method names (DG);
50  * 13-Mar-2003 : Moved to com.jrefinery.data.time package and implemented
51  *               Serializable (DG);
52  * 21-Oct-2003 : Added hashCode() method, and new constructor for
53  *               convenience (DG);
54  * 30-Sep-2004 : Replaced getTime().getTime() with getTimeInMillis() (DG);
55  * 04-Nov-2004 : Reverted change of 30-Sep-2004, because it won't work for
56  *               JDK 1.3 (DG);
57  * ------------- JFREECHART 1.0.x ---------------------------------------------
58  * 05-Oct-2006 : Updated API docs (DG);
59  * 06-Oct-2006 : Refactored to cache first and last millisecond values (DG);
60  * 11-Dec-2006 : Fix for previous() - bug 1611872 (DG);
61  * 16-Sep-2008 : Deprecated DEFAULT_TIME_ZONE (DG);
62  * 02-Mar-2009 : Added new constructor that specifies Locale (DG);
63  * 05-Jul-2012 : Replaced getTime().getTime() with getTimeInMillis() (DG);
64  * 03-Jul-2013 : Use ParamChecks (DG);
65  *
66  */
67 
68 package org.jfree.data.time;
69 
70 import java.io.Serializable;
71 import java.util.Calendar;
72 import java.util.Date;
73 import java.util.Locale;
74 import java.util.TimeZone;
75 import org.jfree.chart.util.ParamChecks;
76 
77 /**
78  * Represents a minute.  This class is immutable, which is a requirement for
79  * all {@link RegularTimePeriod} subclasses.
80  */
81 public class Minute extends RegularTimePeriod implements Serializable {
82 
83     /** For serialization. */
84     private static final long serialVersionUID = 2144572840034842871L;
85 
86     /** Useful constant for the first minute in a day. */
87     public static final int FIRST_MINUTE_IN_HOUR = 0;
88 
89     /** Useful constant for the last minute in a day. */
90     public static final int LAST_MINUTE_IN_HOUR = 59;
91 
92     /** The day. */
93     private Day day;
94 
95     /** The hour in which the minute falls. */
96     private byte hour;
97 
98     /** The minute. */
99     private byte minute;
100 
101     /** The first millisecond. */
102     private long firstMillisecond;
103 
104     /** The last millisecond. */
105     private long lastMillisecond;
106 
107     /**
108      * Constructs a new Minute, based on the system date/time.
109      */
Minute()110     public Minute() {
111         this(new Date());
112     }
113 
114     /**
115      * Constructs a new Minute.
116      *
117      * @param minute  the minute (0 to 59).
118      * @param hour  the hour (<code>null</code> not permitted).
119      */
Minute(int minute, Hour hour)120     public Minute(int minute, Hour hour) {
121         ParamChecks.nullNotPermitted(hour, "hour");
122         this.minute = (byte) minute;
123         this.hour = (byte) hour.getHour();
124         this.day = hour.getDay();
125         peg(Calendar.getInstance());
126     }
127 
128     /**
129      * Constructs a new instance, based on the supplied date/time and
130      * the default time zone.
131      *
132      * @param time  the time (<code>null</code> not permitted).
133      *
134      * @see #Minute(Date, TimeZone)
135      */
Minute(Date time)136     public Minute(Date time) {
137         // defer argument checking
138         this(time, TimeZone.getDefault(), Locale.getDefault());
139     }
140 
141     /**
142      * Constructs a new Minute, based on the supplied date/time and timezone.
143      *
144      * @param time  the time (<code>null</code> not permitted).
145      * @param zone  the time zone (<code>null</code> not permitted).
146      *
147      * @deprecated As of 1.0.13, use the constructor that specifies the locale
148      *     also.
149      */
Minute(Date time, TimeZone zone)150     public Minute(Date time, TimeZone zone) {
151         this(time, zone, Locale.getDefault());
152     }
153 
154     /**
155      * Constructs a new Minute, based on the supplied date/time and timezone.
156      *
157      * @param time  the time (<code>null</code> not permitted).
158      * @param zone  the time zone (<code>null</code> not permitted).
159      * @param locale  the locale (<code>null</code> not permitted).
160      *
161      * @since 1.0.13
162      */
Minute(Date time, TimeZone zone, Locale locale)163     public Minute(Date time, TimeZone zone, Locale locale) {
164         ParamChecks.nullNotPermitted(time, "time");
165         ParamChecks.nullNotPermitted(zone, "zone");
166         ParamChecks.nullNotPermitted(locale, "locale");
167         Calendar calendar = Calendar.getInstance(zone, locale);
168         calendar.setTime(time);
169         int min = calendar.get(Calendar.MINUTE);
170         this.minute = (byte) min;
171         this.hour = (byte) calendar.get(Calendar.HOUR_OF_DAY);
172         this.day = new Day(time, zone, locale);
173         peg(calendar);
174     }
175 
176     /**
177      * Creates a new minute.
178      *
179      * @param minute  the minute (0-59).
180      * @param hour  the hour (0-23).
181      * @param day  the day (1-31).
182      * @param month  the month (1-12).
183      * @param year  the year (1900-9999).
184      */
Minute(int minute, int hour, int day, int month, int year)185     public Minute(int minute, int hour, int day, int month, int year) {
186         this(minute, new Hour(hour, new Day(day, month, year)));
187     }
188 
189     /**
190      * Returns the day.
191      *
192      * @return The day.
193      *
194      * @since 1.0.3
195      */
getDay()196     public Day getDay() {
197         return this.day;
198     }
199 
200     /**
201      * Returns the hour.
202      *
203      * @return The hour (never <code>null</code>).
204      */
getHour()205     public Hour getHour() {
206         return new Hour(this.hour, this.day);
207     }
208 
209     /**
210      * Returns the hour.
211      *
212      * @return The hour.
213      *
214      * @since 1.0.3
215      */
getHourValue()216     public int getHourValue() {
217         return this.hour;
218     }
219 
220     /**
221      * Returns the minute.
222      *
223      * @return The minute.
224      */
getMinute()225     public int getMinute() {
226         return this.minute;
227     }
228 
229     /**
230      * Returns the first millisecond of the minute.  This will be determined
231      * relative to the time zone specified in the constructor, or in the
232      * calendar instance passed in the most recent call to the
233      * {@link #peg(Calendar)} method.
234      *
235      * @return The first millisecond of the minute.
236      *
237      * @see #getLastMillisecond()
238      */
239     @Override
getFirstMillisecond()240     public long getFirstMillisecond() {
241         return this.firstMillisecond;
242     }
243 
244     /**
245      * Returns the last millisecond of the minute.  This will be
246      * determined relative to the time zone specified in the constructor, or
247      * in the calendar instance passed in the most recent call to the
248      * {@link #peg(Calendar)} method.
249      *
250      * @return The last millisecond of the minute.
251      *
252      * @see #getFirstMillisecond()
253      */
254     @Override
getLastMillisecond()255     public long getLastMillisecond() {
256         return this.lastMillisecond;
257     }
258 
259     /**
260      * Recalculates the start date/time and end date/time for this time period
261      * relative to the supplied calendar (which incorporates a time zone).
262      *
263      * @param calendar  the calendar (<code>null</code> not permitted).
264      *
265      * @since 1.0.3
266      */
267     @Override
peg(Calendar calendar)268     public void peg(Calendar calendar) {
269         this.firstMillisecond = getFirstMillisecond(calendar);
270         this.lastMillisecond = getLastMillisecond(calendar);
271     }
272 
273     /**
274      * Returns the minute preceding this one.
275      *
276      * @return The minute preceding this one.
277      */
278     @Override
previous()279     public RegularTimePeriod previous() {
280         Minute result;
281         if (this.minute != FIRST_MINUTE_IN_HOUR) {
282             result = new Minute(this.minute - 1, getHour());
283         }
284         else {
285             Hour h = (Hour) getHour().previous();
286             if (h != null) {
287                 result = new Minute(LAST_MINUTE_IN_HOUR, h);
288             }
289             else {
290                 result = null;
291             }
292         }
293         return result;
294     }
295 
296     /**
297      * Returns the minute following this one.
298      *
299      * @return The minute following this one.
300      */
301     @Override
next()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     @Override
getSerialIndex()325     public long getSerialIndex() {
326         long hourIndex = this.day.getSerialIndex() * 24L + this.hour;
327         return hourIndex * 60L + this.minute;
328     }
329 
330     /**
331      * Returns the first millisecond of the minute.
332      *
333      * @param calendar  the calendar which defines the timezone
334      *     (<code>null</code> not permitted).
335      *
336      * @return The first millisecond.
337      *
338      * @throws NullPointerException if <code>calendar</code> is
339      *     <code>null</code>.
340      */
341     @Override
getFirstMillisecond(Calendar calendar)342     public long getFirstMillisecond(Calendar calendar) {
343         int year = this.day.getYear();
344         int month = this.day.getMonth() - 1;
345         int d = this.day.getDayOfMonth();
346 
347         calendar.clear();
348         calendar.set(year, month, d, this.hour, this.minute, 0);
349         calendar.set(Calendar.MILLISECOND, 0);
350 
351         return calendar.getTimeInMillis();
352     }
353 
354     /**
355      * Returns the last millisecond of the minute.
356      *
357      * @param calendar  the calendar / timezone (<code>null</code> not
358      *     permitted).
359      *
360      * @return The last millisecond.
361      *
362      * @throws NullPointerException if <code>calendar</code> is
363      *     <code>null</code>.
364      */
365     @Override
getLastMillisecond(Calendar calendar)366     public long getLastMillisecond(Calendar calendar) {
367         int year = this.day.getYear();
368         int month = this.day.getMonth() - 1;
369         int d = this.day.getDayOfMonth();
370 
371         calendar.clear();
372         calendar.set(year, month, d, this.hour, this.minute, 59);
373         calendar.set(Calendar.MILLISECOND, 999);
374 
375         return calendar.getTimeInMillis();
376     }
377 
378     /**
379      * Tests the equality of this object against an arbitrary Object.
380      * <P>
381      * This method will return true ONLY if the object is a Minute object
382      * representing the same minute as this instance.
383      *
384      * @param obj  the object to compare (<code>null</code> permitted).
385      *
386      * @return <code>true</code> if the minute and hour value of this and the
387      *      object are the same.
388      */
389     @Override
equals(Object obj)390     public boolean equals(Object obj) {
391         if (obj == this) {
392             return true;
393         }
394         if (!(obj instanceof Minute)) {
395             return false;
396         }
397         Minute that = (Minute) obj;
398         if (this.minute != that.minute) {
399             return false;
400         }
401         if (this.hour != that.hour) {
402             return false;
403         }
404         return true;
405     }
406 
407     /**
408      * Returns a hash code for this object instance.  The approach described
409      * by Joshua Bloch in "Effective Java" has been used here:
410      * <p>
411      * <code>http://developer.java.sun.com/developer/Books/effectivejava
412      * /Chapter3.pdf</code>
413      *
414      * @return A hash code.
415      */
416     @Override
hashCode()417     public int hashCode() {
418         int result = 17;
419         result = 37 * result + this.minute;
420         result = 37 * result + this.hour;
421         result = 37 * result + this.day.hashCode();
422         return result;
423     }
424 
425     /**
426      * Returns an integer indicating the order of this Minute object relative
427      * to the specified object:
428      *
429      * negative == before, zero == same, positive == after.
430      *
431      * @param o1  object to compare.
432      *
433      * @return negative == before, zero == same, positive == after.
434      */
435     @Override
compareTo(Object o1)436     public int compareTo(Object o1) {
437         int result;
438 
439         // CASE 1 : Comparing to another Minute object
440         // -------------------------------------------
441         if (o1 instanceof Minute) {
442             Minute m = (Minute) o1;
443             result = getHour().compareTo(m.getHour());
444             if (result == 0) {
445                 result = this.minute - m.getMinute();
446             }
447         }
448 
449         // CASE 2 : Comparing to another TimePeriod object
450         // -----------------------------------------------
451         else if (o1 instanceof RegularTimePeriod) {
452             // more difficult case - evaluate later...
453             result = 0;
454         }
455 
456         // CASE 3 : Comparing to a non-TimePeriod object
457         // ---------------------------------------------
458         else {
459             // consider time periods to be ordered after general objects
460             result = 1;
461         }
462 
463         return result;
464     }
465 
466     /**
467      * Creates a Minute instance by parsing a string.  The string is assumed to
468      * be in the format "YYYY-MM-DD HH:MM", perhaps with leading or trailing
469      * whitespace.
470      *
471      * @param s  the minute string to parse.
472      *
473      * @return <code>null</code>, if the string is not parseable, the minute
474      *      otherwise.
475      */
parseMinute(String s)476     public static Minute parseMinute(String s) {
477         Minute result = null;
478         s = s.trim();
479 
480         String daystr = s.substring(0, Math.min(10, s.length()));
481         Day day = Day.parseDay(daystr);
482         if (day != null) {
483             String hmstr = s.substring(
484                 Math.min(daystr.length() + 1, s.length()), s.length()
485             );
486             hmstr = hmstr.trim();
487 
488             String hourstr = hmstr.substring(0, Math.min(2, hmstr.length()));
489             int hour = Integer.parseInt(hourstr);
490 
491             if ((hour >= 0) && (hour <= 23)) {
492                 String minstr = hmstr.substring(
493                     Math.min(hourstr.length() + 1, hmstr.length()),
494                     hmstr.length()
495                 );
496                 int minute = Integer.parseInt(minstr);
497                 if ((minute >= 0) && (minute <= 59)) {
498                     result = new Minute(minute, new Hour(hour, day));
499                 }
500             }
501         }
502         return result;
503     }
504 
505 }
506