001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, by David Gilbert 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-2022, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.data.time;
038
039import java.io.Serializable;
040import java.util.Calendar;
041import java.util.Date;
042import java.util.Locale;
043import java.util.TimeZone;
044import org.jfree.chart.internal.Args;
045
046/**
047 * Represents a minute.  This class is immutable, which is a requirement for
048 * all {@link RegularTimePeriod} subclasses.
049 */
050public class Minute extends RegularTimePeriod implements Serializable {
051
052    /** For serialization. */
053    private static final long serialVersionUID = 2144572840034842871L;
054
055    /** Useful constant for the first minute in a day. */
056    public static final int FIRST_MINUTE_IN_HOUR = 0;
057
058    /** Useful constant for the last minute in a day. */
059    public static final int LAST_MINUTE_IN_HOUR = 59;
060
061    /** The day. */
062    private Day day;
063
064    /** The hour in which the minute falls. */
065    private byte hour;
066
067    /** The minute. */
068    private byte minute;
069
070    /** The first millisecond. */
071    private long firstMillisecond;
072
073    /** The last millisecond. */
074    private long lastMillisecond;
075
076    /**
077     * Constructs a new Minute, based on the system date/time.
078     * The time zone and locale are determined by the calendar
079     * returned by {@link RegularTimePeriod#getCalendarInstance()}.
080     */
081    public Minute() {
082        this(new Date());
083    }
084
085    /**
086     * Constructs a new Minute.
087     * The time zone and locale are determined by the calendar
088     * returned by {@link RegularTimePeriod#getCalendarInstance()}.
089     *
090     * @param minute  the minute (0 to 59).
091     * @param hour  the hour ({@code null} not permitted).
092     */
093    public Minute(int minute, Hour hour) {
094        Args.nullNotPermitted(hour, "hour");
095        this.minute = (byte) minute;
096        this.hour = (byte) hour.getHour();
097        this.day = hour.getDay();
098        peg(getCalendarInstance());
099    }
100
101    /**
102     * Constructs a new instance, based on the supplied date/time.
103     * The time zone and locale are determined by the calendar
104     * returned by {@link RegularTimePeriod#getCalendarInstance()}.
105     *
106     * @param time  the time ({@code null} not permitted).
107     *
108     * @see #Minute(Date, TimeZone, Locale)
109     */
110    public Minute(Date time) {
111        // defer argument checking
112        this(time, getCalendarInstance());
113    }
114
115    /**
116     * Constructs a new Minute, based on the supplied date/time and timezone.
117     *
118     * @param time  the time ({@code null} not permitted).
119     * @param zone  the time zone ({@code null} not permitted).
120     * @param locale  the locale ({@code null} not permitted).
121     *
122     * @since 1.0.13
123     */
124    public Minute(Date time, TimeZone zone, Locale locale) {
125        Args.nullNotPermitted(time, "time");
126        Args.nullNotPermitted(zone, "zone");
127        Args.nullNotPermitted(locale, "locale");
128        Calendar calendar = Calendar.getInstance(zone, locale);
129        calendar.setTime(time);
130        int min = calendar.get(Calendar.MINUTE);
131        this.minute = (byte) min;
132        this.hour = (byte) calendar.get(Calendar.HOUR_OF_DAY);
133        this.day = new Day(time, zone, locale);
134        peg(calendar);
135    }
136
137    /**
138     * Constructs a new instance, based on a particular date/time.
139     * The time zone and locale are determined by the {@code calendar}
140     * parameter.
141     *
142     * @param time the date/time ({@code null} not permitted).
143     * @param calendar the calendar to use for calculations ({@code null} not permitted).
144     */
145    public Minute(Date time, Calendar calendar) {
146        Args.nullNotPermitted(time, "time");
147        Args.nullNotPermitted(calendar, "calendar");
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, calendar);
153        peg(calendar);
154    }
155
156    /**
157     * Creates a new minute.
158     * The time zone and locale are determined by the calendar
159     * returned by {@link RegularTimePeriod#getCalendarInstance()}.
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, int hour, int day, int month, int year) {
168        this(minute, new Hour(hour, new Day(day, month, year)));
169    }
170
171    /**
172     * Returns the day.
173     *
174     * @return The day.
175     *
176     * @since 1.0.3
177     */
178    public Day getDay() {
179        return this.day;
180    }
181
182    /**
183     * Returns the hour.
184     *
185     * @return The hour (never {@code null}).
186     */
187    public Hour getHour() {
188        return new Hour(this.hour, this.day);
189    }
190
191    /**
192     * Returns the hour.
193     *
194     * @return The hour.
195     *
196     * @since 1.0.3
197     */
198    public int getHourValue() {
199        return this.hour;
200    }
201
202    /**
203     * Returns the minute.
204     *
205     * @return The minute.
206     */
207    public int getMinute() {
208        return this.minute;
209    }
210
211    /**
212     * Returns the first millisecond of the minute.  This will be determined
213     * relative to the time zone specified in the constructor, or in the
214     * calendar instance passed in the most recent call to the
215     * {@link #peg(Calendar)} method.
216     *
217     * @return The first millisecond of the minute.
218     *
219     * @see #getLastMillisecond()
220     */
221    @Override
222    public long getFirstMillisecond() {
223        return this.firstMillisecond;
224    }
225
226    /**
227     * Returns the last millisecond of the minute.  This will be
228     * determined relative to the time zone specified in the constructor, or
229     * in the calendar instance passed in the most recent call to the
230     * {@link #peg(Calendar)} method.
231     *
232     * @return The last millisecond of the minute.
233     *
234     * @see #getFirstMillisecond()
235     */
236    @Override
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} not permitted).
246     *
247     * @since 1.0.3
248     */
249    @Override
250    public void peg(Calendar calendar) {
251        this.firstMillisecond = getFirstMillisecond(calendar);
252        this.lastMillisecond = getLastMillisecond(calendar);
253    }
254
255    /**
256     * Returns the minute preceding this one.
257     * No matter what time zone and locale this instance was created with,
258     * the returned instance will use the default calendar for time
259     * calculations, obtained with {@link RegularTimePeriod#getCalendarInstance()}.
260     *
261     * @return The minute preceding this one.
262     */
263    @Override
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     * No matter what time zone and locale this instance was created with,
284     * the returned instance will use the default calendar for time
285     * calculations, obtained with {@link RegularTimePeriod#getCalendarInstance()}.
286     *
287     * @return The minute following this one.
288     */
289    @Override
290    public RegularTimePeriod next() {
291        Minute result;
292        if (this.minute != LAST_MINUTE_IN_HOUR) {
293            result = new Minute(this.minute + 1, getHour());
294        }
295        else { // we are at the last minute in the hour...
296            Hour nextHour = (Hour) getHour().next();
297            if (nextHour != null) {
298                result = new Minute(FIRST_MINUTE_IN_HOUR, nextHour);
299            }
300            else {
301                result = null;
302            }
303        }
304        return result;
305    }
306
307    /**
308     * Returns a serial index number for the minute.
309     *
310     * @return The serial index number.
311     */
312    @Override
313    public long getSerialIndex() {
314        long hourIndex = this.day.getSerialIndex() * 24L + this.hour;
315        return hourIndex * 60L + this.minute;
316    }
317
318    /**
319     * Returns the first millisecond of the minute.
320     *
321     * @param calendar  the calendar which defines the timezone
322     *     ({@code null} not permitted).
323     *
324     * @return The first millisecond.
325     *
326     * @throws NullPointerException if {@code calendar} is
327     *     {@code null}.
328     */
329    @Override
330    public long getFirstMillisecond(Calendar calendar) {
331        int year = this.day.getYear();
332        int month = this.day.getMonth() - 1;
333        int d = this.day.getDayOfMonth();
334
335        calendar.clear();
336        calendar.set(year, month, d, this.hour, this.minute, 0);
337        calendar.set(Calendar.MILLISECOND, 0);
338
339        return calendar.getTimeInMillis();
340    }
341
342    /**
343     * Returns the last millisecond of the minute.
344     *
345     * @param calendar  the calendar / timezone ({@code null} not
346     *     permitted).
347     *
348     * @return The last millisecond.
349     *
350     * @throws NullPointerException if {@code calendar} is
351     *     {@code null}.
352     */
353    @Override
354    public long getLastMillisecond(Calendar calendar) {
355        int year = this.day.getYear();
356        int month = this.day.getMonth() - 1;
357        int d = this.day.getDayOfMonth();
358
359        calendar.clear();
360        calendar.set(year, month, d, this.hour, this.minute, 59);
361        calendar.set(Calendar.MILLISECOND, 999);
362
363        return calendar.getTimeInMillis();
364    }
365
366    /**
367     * Tests the equality of this object against an arbitrary Object.
368     * <P>
369     * This method will return true ONLY if the object is a Minute object
370     * representing the same minute as this instance.
371     *
372     * @param obj  the object to compare ({@code null} permitted).
373     *
374     * @return {@code true} if the minute and hour value of this and the
375     *      object are the same.
376     */
377    @Override
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}
401     *
402     * @return A hash code.
403     */
404    @Override
405    public int hashCode() {
406        int result = 17;
407        result = 37 * result + this.minute;
408        result = 37 * result + this.hour;
409        result = 37 * result + this.day.hashCode();
410        return result;
411    }
412
413    /**
414     * Returns an integer indicating the order of this Minute object relative
415     * to the specified object:
416     *
417     * negative == before, zero == same, positive == after.
418     *
419     * @param o1  object to compare.
420     *
421     * @return negative == before, zero == same, positive == after.
422     */
423    @Override
424    public int compareTo(Object o1) {
425        int result;
426
427        // CASE 1 : Comparing to another Minute object
428        // -------------------------------------------
429        if (o1 instanceof Minute) {
430            Minute m = (Minute) o1;
431            result = getHour().compareTo(m.getHour());
432            if (result == 0) {
433                result = this.minute - m.getMinute();
434            }
435        }
436
437        // CASE 2 : Comparing to another TimePeriod object
438        // -----------------------------------------------
439        else if (o1 instanceof RegularTimePeriod) {
440            // more difficult case - evaluate later...
441            result = 0;
442        }
443
444        // CASE 3 : Comparing to a non-TimePeriod object
445        // ---------------------------------------------
446        else {
447            // consider time periods to be ordered after general objects
448            result = 1;
449        }
450
451        return result;
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}, if the string is not parseable, the minute
462     *      otherwise.
463     */
464    public static Minute parseMinute(String s) {
465        Minute result = null;
466        s = s.trim();
467
468        String daystr = s.substring(0, Math.min(10, s.length()));
469        Day day = Day.parseDay(daystr);
470        if (day != null) {
471            String hmstr = s.substring(
472                Math.min(daystr.length() + 1, s.length()), s.length()
473            );
474            hmstr = hmstr.trim();
475
476            String hourstr = hmstr.substring(0, Math.min(2, hmstr.length()));
477            int hour = Integer.parseInt(hourstr);
478
479            if ((hour >= 0) && (hour <= 23)) {
480                String minstr = hmstr.substring(
481                    Math.min(hourstr.length() + 1, hmstr.length()),
482                    hmstr.length()
483                );
484                int minute = Integer.parseInt(minstr);
485                if ((minute >= 0) && (minute <= 59)) {
486                    result = new Minute(minute, new Hour(hour, day));
487                }
488            }
489        }
490        return result;
491    }
492
493}