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