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