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 * Day.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.text.DateFormat;
041import java.text.ParseException;
042import java.text.SimpleDateFormat;
043import java.util.Calendar;
044import java.util.Date;
045import java.util.Locale;
046import java.util.TimeZone;
047
048import org.jfree.chart.date.SerialDate;
049import org.jfree.chart.internal.Args;
050
051/**
052 * Represents a single day in the range 1-Jan-1900 to 31-Dec-9999.  This class
053 * is immutable, which is a requirement for all {@link RegularTimePeriod}
054 * subclasses.
055 */
056public class Day extends RegularTimePeriod implements Serializable {
057
058    /** For serialization. */
059    private static final long serialVersionUID = -7082667380758962755L;
060
061    /** 
062     * A date formatter - used for parsing, therefore we fix the locale 
063     * so we get dependable results. 
064     */
065    protected static final DateFormat DATE_FORMAT
066            = new SimpleDateFormat("yyyy-MM-dd", Locale.UK);
067
068    /** A date formatter for the default locale. */
069    protected static final DateFormat DATE_FORMAT_SHORT 
070            = DateFormat.getDateInstance(DateFormat.SHORT);
071
072    /** A date formatter for the default locale. */
073    protected static final DateFormat DATE_FORMAT_MEDIUM 
074            = DateFormat.getDateInstance(DateFormat.MEDIUM);
075
076    /** A date formatter for the default locale. */
077    protected static final DateFormat DATE_FORMAT_LONG 
078            = DateFormat.getDateInstance(DateFormat.LONG);
079
080    /** The day (uses SerialDate for convenience). */
081    private SerialDate serialDate;
082
083    /** The first millisecond. */
084    private long firstMillisecond;
085
086    /** The last millisecond. */
087    private long lastMillisecond;
088
089    /**
090     * Creates a new instance, derived from the system date/time.
091     * The time zone and locale are determined by the calendar
092     * returned by {@link RegularTimePeriod#getCalendarInstance()}.
093     */
094    public Day() {
095        this(new Date());
096    }
097
098    /**
099     * Constructs a new one day time period.
100     * The time zone and locale are determined by the calendar
101     * returned by {@link RegularTimePeriod#getCalendarInstance()}.
102     *
103     * @param day  the day-of-the-month.
104     * @param month  the month (1 to 12).
105     * @param year  the year (1900 <= year <= 9999).
106     */
107    public Day(int day, int month, int year) {
108        this.serialDate = SerialDate.createInstance(day, month, year);
109        peg(getCalendarInstance());
110    }
111
112    /**
113     * Constructs a new one day time period.
114     * The time zone and locale are determined by the calendar
115     * returned by {@link RegularTimePeriod#getCalendarInstance()}.
116     *
117     * @param serialDate  the day ({@code null} not permitted).
118     */
119    public Day(SerialDate serialDate) {
120        Args.nullNotPermitted(serialDate, "serialDate");
121        this.serialDate = serialDate;
122        peg(getCalendarInstance());
123    }
124
125    /**
126     * Constructs a new instance, based on a particular date/time.
127     * The time zone and locale are determined by the calendar
128     * returned by {@link RegularTimePeriod#getCalendarInstance()}.
129     *
130     * @param time  the time ({@code null} not permitted).
131     *
132     * @see #Day(Date, TimeZone, Locale)
133     */
134    public Day(Date time) {
135        // defer argument checking...
136        this(time, getCalendarInstance());
137    }
138
139    /**
140     * Constructs a new instance, based on a particular date/time and time zone.
141     *
142     * @param time  the date/time ({@code null} not permitted).
143     * @param zone  the time zone ({@code null} not permitted).
144     * @param locale  the locale ({@code null} not permitted).
145     */
146    public Day(Date time, TimeZone zone, Locale locale) {
147        Args.nullNotPermitted(time, "time");
148        Args.nullNotPermitted(zone, "zone");
149        Args.nullNotPermitted(locale, "locale");
150        Calendar calendar = Calendar.getInstance(zone, locale);
151        calendar.setTime(time);
152        initUsing(calendar);
153        peg(calendar);
154    }
155
156    /**
157     * Constructs a new instance, based on a particular date/time.
158     * The time zone and locale are determined by the {@code calendar}
159     * parameter.
160     *
161     * @param time the date/time ({@code null} not permitted).
162     * @param calendar the calendar to use for calculations ({@code null} not permitted).
163     */
164    public Day(Date time, Calendar calendar) {
165        Args.nullNotPermitted(time, "time");
166        Args.nullNotPermitted(calendar, "calendar");
167        calendar.setTime(time);
168        initUsing(calendar);
169        peg(calendar);
170    }
171
172    private void initUsing(Calendar calendar) {
173        int d = calendar.get(Calendar.DAY_OF_MONTH);
174        int m = calendar.get(Calendar.MONTH) + 1;
175        int y = calendar.get(Calendar.YEAR);
176        this.serialDate = SerialDate.createInstance(d, m, y);
177    }
178
179    /**
180     * Returns the day as a {@link SerialDate}.  Note: the reference that is
181     * returned should be an instance of an immutable {@link SerialDate}
182     * (otherwise the caller could use the reference to alter the state of
183     * this {@code Day} instance, and {@code Day} is supposed
184     * to be immutable).
185     *
186     * @return The day as a {@link SerialDate}.
187     */
188    public SerialDate getSerialDate() {
189        return this.serialDate;
190    }
191
192    /**
193     * Returns the year.
194     *
195     * @return The year.
196     */
197    public int getYear() {
198        return this.serialDate.getYYYY();
199    }
200
201    /**
202     * Returns the month.
203     *
204     * @return The month.
205     */
206    public int getMonth() {
207        return this.serialDate.getMonth();
208    }
209
210    /**
211     * Returns the day of the month.
212     *
213     * @return The day of the month.
214     */
215    public int getDayOfMonth() {
216        return this.serialDate.getDayOfMonth();
217    }
218
219    /**
220     * Returns the first millisecond of the day.  This will be determined
221     * relative to the time zone specified in the constructor, or in the
222     * calendar instance passed in the most recent call to the
223     * {@link #peg(Calendar)} method.
224     *
225     * @return The first millisecond of the day.
226     *
227     * @see #getLastMillisecond()
228     */
229    @Override
230    public long getFirstMillisecond() {
231        return this.firstMillisecond;
232    }
233
234    /**
235     * Returns the last millisecond of the day.  This will be
236     * determined relative to the time zone specified in the constructor, or
237     * in the calendar instance passed in the most recent call to the
238     * {@link #peg(Calendar)} method.
239     *
240     * @return The last millisecond of the day.
241     *
242     * @see #getFirstMillisecond()
243     */
244    @Override
245    public long getLastMillisecond() {
246        return this.lastMillisecond;
247    }
248
249    /**
250     * Recalculates the start date/time and end date/time for this time period
251     * relative to the supplied calendar (which incorporates a time zone).
252     *
253     * @param calendar  the calendar ({@code null} not permitted).
254     *
255     * @since 1.0.3
256     */
257    @Override
258    public void peg(Calendar calendar) {
259        this.firstMillisecond = getFirstMillisecond(calendar);
260        this.lastMillisecond = getLastMillisecond(calendar);
261    }
262
263    /**
264     * Returns the day preceding this one.
265     * No matter what time zone and locale this instance was created with,
266     * the returned instance will use the default calendar for time
267     * calculations, obtained with {@link RegularTimePeriod#getCalendarInstance()}.
268     *
269     * @return The day preceding this one.
270     */
271    @Override
272    public RegularTimePeriod previous() {
273        Day result;
274        int serial = this.serialDate.toSerial();
275        if (serial > SerialDate.SERIAL_LOWER_BOUND) {
276            SerialDate yesterday = SerialDate.createInstance(serial - 1);
277            return new Day(yesterday);
278        }
279        else {
280            result = null;
281        }
282        return result;
283    }
284
285    /**
286     * Returns the day following this one, or {@code null} if some limit
287     * has been reached.
288     * No matter what time zone and locale this instance was created with,
289     * the returned instance will use the default calendar for time
290     * calculations, obtained with {@link RegularTimePeriod#getCalendarInstance()}.
291     *
292     * @return The day following this one, or {@code null} if some limit
293     *         has been reached.
294     */
295    @Override
296    public RegularTimePeriod next() {
297        Day result;
298        int serial = this.serialDate.toSerial();
299        if (serial < SerialDate.SERIAL_UPPER_BOUND) {
300            SerialDate tomorrow = SerialDate.createInstance(serial + 1);
301            return new Day(tomorrow);
302        }
303        else {
304            result = null;
305        }
306        return result;
307    }
308
309    /**
310     * Returns a serial index number for the day.
311     *
312     * @return The serial index number.
313     */
314    @Override
315    public long getSerialIndex() {
316        return this.serialDate.toSerial();
317    }
318
319    /**
320     * Returns the first millisecond of the day, evaluated using the supplied
321     * calendar (which determines the time zone).
322     *
323     * @param calendar  calendar to use ({@code null} not permitted).
324     *
325     * @return The start of the day as milliseconds since 01-01-1970.
326     *
327     * @throws NullPointerException if {@code calendar} is
328     *     {@code null}.
329     */
330    @Override
331    public long getFirstMillisecond(Calendar calendar) {
332        int year = this.serialDate.getYYYY();
333        int month = this.serialDate.getMonth();
334        int day = this.serialDate.getDayOfMonth();
335        calendar.clear();
336        calendar.set(year, month - 1, day, 0, 0, 0);
337        calendar.set(Calendar.MILLISECOND, 0);
338        return calendar.getTimeInMillis();
339    }
340
341    /**
342     * Returns the last millisecond of the day, evaluated using the supplied
343     * calendar (which determines the time zone).
344     *
345     * @param calendar  calendar to use ({@code null} not permitted).
346     *
347     * @return The end of the day as milliseconds since 01-01-1970.
348     *
349     * @throws NullPointerException if {@code calendar} is
350     *     {@code null}.
351     */
352    @Override
353    public long getLastMillisecond(Calendar calendar) {
354        int year = this.serialDate.getYYYY();
355        int month = this.serialDate.getMonth();
356        int day = this.serialDate.getDayOfMonth();
357        calendar.clear();
358        calendar.set(year, month - 1, day, 23, 59, 59);
359        calendar.set(Calendar.MILLISECOND, 999);
360        return calendar.getTimeInMillis();
361    }
362
363    /**
364     * Tests the equality of this Day object to an arbitrary object.  Returns
365     * true if the target is a Day instance or a SerialDate instance
366     * representing the same day as this object. In all other cases,
367     * returns false.
368     *
369     * @param obj  the object ({@code null} permitted).
370     *
371     * @return A flag indicating whether or not an object is equal to this day.
372     */
373    @Override
374    public boolean equals(Object obj) {
375        if (obj == this) {
376            return true;
377        }
378        if (!(obj instanceof Day)) {
379            return false;
380        }
381        Day that = (Day) obj;
382        if (!this.serialDate.equals(that.getSerialDate())) {
383            return false;
384        }
385        return true;
386    }
387
388    /**
389     * Returns a hash code for this object instance.  The approach described by
390     * Joshua Bloch in "Effective Java" has been used here:
391     * <p>
392     * {@code http://developer.java.sun.com/developer/Books/effectivejava
393     * /Chapter3.pdf}
394     *
395     * @return A hash code.
396     */
397    @Override
398    public int hashCode() {
399        return this.serialDate.hashCode();
400    }
401
402    /**
403     * Returns an integer indicating the order of this Day object relative to
404     * the specified object:
405     *
406     * negative == before, zero == same, positive == after.
407     *
408     * @param o1  the object to compare.
409     *
410     * @return negative == before, zero == same, positive == after.
411     */
412    @Override
413    public int compareTo(Object o1) {
414        int result;
415
416        // CASE 1 : Comparing to another Day object
417        // ----------------------------------------
418        if (o1 instanceof Day) {
419            Day d = (Day) o1;
420            result = -d.getSerialDate().compare(this.serialDate);
421        }
422
423        // CASE 2 : Comparing to another TimePeriod object
424        // -----------------------------------------------
425        else if (o1 instanceof RegularTimePeriod) {
426            // more difficult case - evaluate later...
427            result = 0;
428        }
429
430        // CASE 3 : Comparing to a non-TimePeriod object
431        // ---------------------------------------------
432        else {
433            // consider time periods to be ordered after general objects
434            result = 1;
435        }
436
437        return result;
438    }
439
440    /**
441     * Returns a string representing the day.
442     *
443     * @return A string representing the day.
444     */
445    @Override
446    public String toString() {
447        return this.serialDate.toString();
448    }
449
450    /**
451     * Parses the string argument as a day.
452     * <P>
453     * This method is required to recognise YYYY-MM-DD as a valid format.
454     * Anything else, for now, is a bonus.
455     *
456     * @param s  the date string to parse.
457     *
458     * @return {@code null} if the string does not contain any parseable
459     *      string, the day otherwise.
460     */
461    public static Day parseDay(String s) {
462        try {
463            return new Day (Day.DATE_FORMAT.parse(s));
464        }
465        catch (ParseException e1) {
466            try {
467                return new Day (Day.DATE_FORMAT_SHORT.parse(s));
468            }
469            catch (ParseException e2) {
470              // ignore
471            }
472        }
473        return null;
474    }
475
476}