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    }