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