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