    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    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
    066package org.jfree.data.time;
    067
    068import java.io.Serializable;
    069import java.util.Calendar;
    070import java.util.Date;
    071import java.util.Locale;
    072import java.util.TimeZone;
    073
    074/**
    075 * Represents a minute.  This class is immutable, which is a requirement for
    076 * all {@link RegularTimePeriod} subclasses.
    077 */
    078public 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}