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    }