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