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