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 * Hour.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 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     * @since 1.0.13
134     */
135    public Hour(Date time, TimeZone zone, Locale locale) {
136        Args.nullNotPermitted(time, "time");
137        Args.nullNotPermitted(zone, "zone");
138        Args.nullNotPermitted(locale, "locale");
139        Calendar calendar = Calendar.getInstance(zone, locale);
140        calendar.setTime(time);
141        this.hour = (byte) calendar.get(Calendar.HOUR_OF_DAY);
142        this.day = new Day(time, zone, locale);
143        peg(calendar);
144    }
145
146    /**
147     * Constructs a new instance, based on a particular date/time.
148     * The time zone and locale are determined by the {@code calendar}
149     * parameter.
150     *
151     * @param time the date/time ({@code null} not permitted).
152     * @param calendar the calendar to use for calculations ({@code null} not permitted).
153     */
154    public Hour(Date time, Calendar calendar) {
155        Args.nullNotPermitted(time, "time");
156        Args.nullNotPermitted(calendar, "calendar");
157        calendar.setTime(time);
158        this.hour = (byte) calendar.get(Calendar.HOUR_OF_DAY);
159        this.day = new Day(time, calendar);
160        peg(calendar);
161    }
162
163    /**
164     * Returns the hour.
165     *
166     * @return The hour (0 <= hour <= 23).
167     */
168    public int getHour() {
169        return this.hour;
170    }
171
172    /**
173     * Returns the day in which this hour falls.
174     *
175     * @return The day.
176     */
177    public Day getDay() {
178        return this.day;
179    }
180
181    /**
182     * Returns the year in which this hour falls.
183     *
184     * @return The year.
185     */
186    public int getYear() {
187        return this.day.getYear();
188    }
189
190    /**
191     * Returns the month in which this hour falls.
192     *
193     * @return The month.
194     */
195    public int getMonth() {
196        return this.day.getMonth();
197    }
198
199    /**
200     * Returns the day-of-the-month in which this hour falls.
201     *
202     * @return The day-of-the-month.
203     */
204    public int getDayOfMonth() {
205        return this.day.getDayOfMonth();
206    }
207
208    /**
209     * Returns the first millisecond of the hour.  This will be determined
210     * relative to the time zone specified in the constructor, or in the
211     * calendar instance passed in the most recent call to the
212     * {@link #peg(Calendar)} method.
213     *
214     * @return The first millisecond of the hour.
215     *
216     * @see #getLastMillisecond()
217     */
218    @Override
219    public long getFirstMillisecond() {
220        return this.firstMillisecond;
221    }
222
223    /**
224     * Returns the last millisecond of the hour.  This will be
225     * determined relative to the time zone specified in the constructor, or
226     * in the calendar instance passed in the most recent call to the
227     * {@link #peg(Calendar)} method.
228     *
229     * @return The last millisecond of the hour.
230     *
231     * @see #getFirstMillisecond()
232     */
233    @Override
234    public long getLastMillisecond() {
235        return this.lastMillisecond;
236    }
237
238    /**
239     * Recalculates the start date/time and end date/time for this time period
240     * relative to the supplied calendar (which incorporates a time zone).
241     *
242     * @param calendar  the calendar ({@code null} not permitted).
243     *
244     * @since 1.0.3
245     */
246    @Override
247    public void peg(Calendar calendar) {
248        this.firstMillisecond = getFirstMillisecond(calendar);
249        this.lastMillisecond = getLastMillisecond(calendar);
250    }
251
252    /**
253     * Returns the hour preceding this one.
254     * No matter what time zone and locale this instance was created with,
255     * the returned instance will use the default calendar for time
256     * calculations, obtained with {@link RegularTimePeriod#getCalendarInstance()}.
257     *
258     * @return The hour preceding this one.
259     */
260    @Override
261    public RegularTimePeriod previous() {
262        Hour result;
263        if (this.hour != FIRST_HOUR_IN_DAY) {
264            result = new Hour(this.hour - 1, this.day);
265        }
266        else { // we are at the first hour in the day...
267            Day prevDay = (Day) this.day.previous();
268            if (prevDay != null) {
269                result = new Hour(LAST_HOUR_IN_DAY, prevDay);
270            }
271            else {
272                result = null;
273            }
274        }
275        return result;
276    }
277
278    /**
279     * Returns the hour following this one.
280     * No matter what time zone and locale this instance was created with,
281     * the returned instance will use the default calendar for time
282     * calculations, obtained with {@link RegularTimePeriod#getCalendarInstance()}.
283     *
284     * @return The hour following this one.
285     */
286    @Override
287    public RegularTimePeriod next() {
288        Hour result;
289        if (this.hour != LAST_HOUR_IN_DAY) {
290            result = new Hour(this.hour + 1, this.day);
291        }
292        else { // we are at the last hour in the day...
293            Day nextDay = (Day) this.day.next();
294            if (nextDay != null) {
295                result = new Hour(FIRST_HOUR_IN_DAY, nextDay);
296            }
297            else {
298                result = null;
299            }
300        }
301        return result;
302    }
303
304    /**
305     * Returns a serial index number for the hour.
306     *
307     * @return The serial index number.
308     */
309    @Override
310    public long getSerialIndex() {
311        return this.day.getSerialIndex() * 24L + this.hour;
312    }
313
314    /**
315     * Returns the first millisecond of the hour.
316     *
317     * @param calendar  the calendar/timezone ({@code null} not permitted).
318     *
319     * @return The first millisecond.
320     *
321     * @throws NullPointerException if {@code calendar} is
322     *     {@code null}.
323     */
324    @Override
325    public long getFirstMillisecond(Calendar calendar) {
326        int year = this.day.getYear();
327        int month = this.day.getMonth() - 1;
328        int dom = this.day.getDayOfMonth();
329        calendar.set(year, month, dom, this.hour, 0, 0);
330        calendar.set(Calendar.MILLISECOND, 0);
331        return calendar.getTimeInMillis();
332    }
333
334    /**
335     * Returns the last millisecond of the hour.
336     *
337     * @param calendar  the calendar/timezone ({@code null} not permitted).
338     *
339     * @return The last millisecond.
340     *
341     * @throws NullPointerException if {@code calendar} is
342     *     {@code null}.
343     */
344    @Override
345    public long getLastMillisecond(Calendar calendar) {
346        int year = this.day.getYear();
347        int month = this.day.getMonth() - 1;
348        int dom = this.day.getDayOfMonth();
349        calendar.set(year, month, dom, this.hour, 59, 59);
350        calendar.set(Calendar.MILLISECOND, 999);
351        return calendar.getTimeInMillis();
352    }
353
354    /**
355     * Tests the equality of this object against an arbitrary Object.
356     * <P>
357     * This method will return true ONLY if the object is an Hour object
358     * representing the same hour as this instance.
359     *
360     * @param obj  the object to compare ({@code null} permitted).
361     *
362     * @return {@code true} if the hour and day value of the object
363     *      is the same as this.
364     */
365    @Override
366    public boolean equals(Object obj) {
367        if (obj == this) {
368            return true;
369        }
370        if (!(obj instanceof Hour)) {
371            return false;
372        }
373        Hour that = (Hour) obj;
374        if (this.hour != that.hour) {
375            return false;
376        }
377        if (!this.day.equals(that.day)) {
378            return false;
379        }
380        return true;
381    }
382
383    /**
384     * Returns a string representation of this instance, for debugging
385     * purposes.
386     *
387     * @return A string.
388     */
389    @Override
390    public String toString() {
391        return "[" + this.hour + "," + getDayOfMonth() + "/" + getMonth() + "/"
392                + getYear() + "]";
393    }
394 
395    /**
396     * Returns a hash code for this object instance.  The approach described by
397     * 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.hour;
408        result = 37 * result + this.day.hashCode();
409        return result;
410    }
411
412    /**
413     * Returns an integer indicating the order of this Hour object relative to
414     * the specified object:
415     *
416     * negative == before, zero == same, positive == after.
417     *
418     * @param o1  the object to compare.
419     *
420     * @return negative == before, zero == same, positive == after.
421     */
422    @Override
423    public int compareTo(Object o1) {
424        int result;
425
426        // CASE 1 : Comparing to another Hour object
427        // -----------------------------------------
428        if (o1 instanceof Hour) {
429            Hour h = (Hour) o1;
430            result = getDay().compareTo(h.getDay());
431            if (result == 0) {
432                result = this.hour - h.getHour();
433            }
434        }
435
436        // CASE 2 : Comparing to another TimePeriod object
437        // -----------------------------------------------
438        else if (o1 instanceof RegularTimePeriod) {
439            // more difficult case - evaluate later...
440            result = 0;
441        }
442
443        // CASE 3 : Comparing to a non-TimePeriod object
444        // ---------------------------------------------
445        else {
446            // consider time periods to be ordered after general objects
447            result = 1;
448        }
449
450        return result;
451    }
452
453    /**
454     * Creates an Hour instance by parsing a string.  The string is assumed to
455     * be in the format "YYYY-MM-DD HH", perhaps with leading or trailing
456     * whitespace.
457     *
458     * @param s  the hour string to parse.
459     *
460     * @return {@code null} if the string is not parseable, the hour
461     *         otherwise.
462     */
463    public static Hour parseHour(String s) {
464        Hour result = null;
465        s = s.trim();
466
467        String daystr = s.substring(0, Math.min(10, s.length()));
468        Day day = Day.parseDay(daystr);
469        if (day != null) {
470            String hourstr = s.substring(
471                Math.min(daystr.length() + 1, s.length()), s.length()
472            );
473            hourstr = hourstr.trim();
474            int hour = Integer.parseInt(hourstr);
475            // if the hour is 0 - 23 then create an hour
476            if ((hour >= FIRST_HOUR_IN_DAY) && (hour <= LAST_HOUR_IN_DAY)) {
477                result = new Hour(hour, day);
478            }
479        }
480
481        return result;
482    }
483
484}