001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2014, 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 * DateTickUnit.java
029 * -----------------
030 * (C) Copyright 2000-2014, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   Chris Boek;
034 *
035 * Changes
036 * -------
037 * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
038 * 27-Nov-2002 : Added IllegalArgumentException to getMillisecondCount()
039 *               method (DG);
040 * 26-Mar-2003 : Implemented Serializable (DG);
041 * 12-Nov-2003 : Added roll fields that can improve the labelling on segmented
042 *               date axes (DG);
043 * 03-Dec-2003 : DateFormat constructor argument is now filled with an default
044 *               if null (TM);
045 * 07-Dec-2003 : Fixed bug (null pointer exception) in constructor (DG);
046 * ------------- JFREECHART 1.0.x ---------------------------------------------
047 * 21-Mar-2007 : Added toString() for debugging (DG);
048 * 04-Apr-2007 : Added new methods addToDate(Date, TimeZone) and rollDate(Date,
049 *               TimeZone) (CB);
050 * 09-Jun-2008 : Deprecated addToDate(Date) (DG);
051 * 09-Jan-2009 : Replaced the unit and rollUnit fields with an enumerated
052 *               type (DG);
053 * 02-Jul-2013 : Use ParamChecks (DG);
054 *
055 */
056
057package org.jfree.chart.axis;
058
059import java.io.Serializable;
060import java.text.DateFormat;
061import java.util.Calendar;
062import java.util.Date;
063import java.util.TimeZone;
064import org.jfree.chart.util.ParamChecks;
065
066import org.jfree.util.ObjectUtilities;
067
068/**
069 * A tick unit for use by subclasses of {@link DateAxis}.  Instances of this
070 * class are immutable.
071 */
072public class DateTickUnit extends TickUnit implements Serializable {
073
074    /** For serialization. */
075    private static final long serialVersionUID = -7289292157229621901L;
076
077    /**
078     * The units.
079     *
080     * @since 1.0.13
081     */
082    private DateTickUnitType unitType;
083
084    /** The unit count. */
085    private int count;
086
087    /**
088     * The roll unit type.
089     *
090     * @since 1.0.13
091     */
092    private DateTickUnitType rollUnitType;
093
094    /** The roll count. */
095    private int rollCount;
096
097    /** The date formatter. */
098    private DateFormat formatter;
099
100    /**
101     * Creates a new date tick unit.
102     *
103     * @param unitType  the unit type (<code>null</code> not permitted).
104     * @param multiple  the multiple (of the unit type, must be &gt; 0).
105     *
106     * @since 1.0.13
107     */
108    public DateTickUnit(DateTickUnitType unitType, int multiple) {
109        this(unitType, multiple, DateFormat.getDateInstance(DateFormat.SHORT));
110    }
111
112    /**
113     * Creates a new date tick unit.
114     *
115     * @param unitType  the unit type (<code>null</code> not permitted).
116     * @param multiple  the multiple (of the unit type, must be &gt; 0).
117     * @param formatter  the date formatter (<code>null</code> not permitted).
118     *
119     * @since 1.0.13
120     */
121    public DateTickUnit(DateTickUnitType unitType, int multiple,
122            DateFormat formatter) {
123        this(unitType, multiple, unitType, multiple, formatter);
124    }
125
126    /**
127     * Creates a new unit.
128     *
129     * @param unitType  the unit.
130     * @param multiple  the multiple.
131     * @param rollUnitType  the roll unit.
132     * @param rollMultiple  the roll multiple.
133     * @param formatter  the date formatter (<code>null</code> not permitted).
134     *
135     * @since 1.0.13
136     */
137    public DateTickUnit(DateTickUnitType unitType, int multiple,
138            DateTickUnitType rollUnitType, int rollMultiple,
139            DateFormat formatter) {
140        super(DateTickUnit.getMillisecondCount(unitType, multiple));
141        ParamChecks.nullNotPermitted(formatter, "formatter");
142        if (multiple <= 0) {
143            throw new IllegalArgumentException("Requires 'multiple' > 0.");
144        }
145        if (rollMultiple <= 0) {
146            throw new IllegalArgumentException("Requires 'rollMultiple' > 0.");
147        }
148        this.unitType = unitType;
149        this.count = multiple;
150        this.rollUnitType = rollUnitType;
151        this.rollCount = rollMultiple;
152        this.formatter = formatter;
153
154        // populate deprecated fields
155        this.unit = unitTypeToInt(unitType);
156        this.rollUnit = unitTypeToInt(rollUnitType);
157    }
158
159    /**
160     * Returns the unit type.
161     *
162     * @return The unit type (never <code>null</code>).
163     *
164     * @since 1.0.13
165     */
166    public DateTickUnitType getUnitType() {
167        return this.unitType;
168    }
169
170    /**
171     * Returns the unit multiple.
172     *
173     * @return The unit multiple (always &gt; 0).
174     */
175    public int getMultiple() {
176        return this.count;
177    }
178
179    /**
180     * Returns the roll unit type.
181     *
182     * @return The roll unit type (never <code>null</code>).
183     *
184     * @since 1.0.13
185     */
186    public DateTickUnitType getRollUnitType() {
187        return this.rollUnitType;
188    }
189
190    /**
191     * Returns the roll unit multiple.
192     *
193     * @return The roll unit multiple.
194     *
195     * @since 1.0.13
196     */
197    public int getRollMultiple() {
198        return this.rollCount;
199    }
200
201    /**
202     * Formats a value.
203     *
204     * @param milliseconds  date in milliseconds since 01-01-1970.
205     *
206     * @return The formatted date.
207     */
208    @Override
209    public String valueToString(double milliseconds) {
210        return this.formatter.format(new Date((long) milliseconds));
211    }
212
213    /**
214     * Formats a date using the tick unit's formatter.
215     *
216     * @param date  the date.
217     *
218     * @return The formatted date.
219     */
220    public String dateToString(Date date) {
221        return this.formatter.format(date);
222    }
223
224    /**
225     * Calculates a new date by adding this unit to the base date.
226     *
227     * @param base  the base date.
228     * @param zone  the time zone for the date calculation.
229     *
230     * @return A new date one unit after the base date.
231     *
232     * @since 1.0.6
233     */
234    public Date addToDate(Date base, TimeZone zone) {
235        // as far as I know, the Locale for the calendar only affects week
236        // number calculations, and since DateTickUnit doesn't do week
237        // arithmetic, the default locale (whatever it is) should be fine
238        // here...
239        Calendar calendar = Calendar.getInstance(zone);
240        calendar.setTime(base);
241        calendar.add(this.unitType.getCalendarField(), this.count);
242        return calendar.getTime();
243    }
244
245    /**
246     * Rolls the date forward by the amount specified by the roll unit and
247     * count.
248     *
249     * @param base  the base date.
250
251     * @return The rolled date.
252     *
253     * @see #rollDate(Date, TimeZone)
254     */
255    public Date rollDate(Date base) {
256        return rollDate(base, TimeZone.getDefault());
257    }
258
259    /**
260     * Rolls the date forward by the amount specified by the roll unit and
261     * count.
262     *
263     * @param base  the base date.
264     * @param zone  the time zone.
265     *
266     * @return The rolled date.
267     *
268     * @since 1.0.6
269     */
270    public Date rollDate(Date base, TimeZone zone) {
271        // as far as I know, the Locale for the calendar only affects week
272        // number calculations, and since DateTickUnit doesn't do week
273        // arithmetic, the default locale (whatever it is) should be fine
274        // here...
275        Calendar calendar = Calendar.getInstance(zone);
276        calendar.setTime(base);
277        calendar.add(this.rollUnitType.getCalendarField(), this.rollCount);
278        return calendar.getTime();
279    }
280
281    /**
282     * Returns a field code that can be used with the <code>Calendar</code>
283     * class.
284     *
285     * @return The field code.
286     */
287    public int getCalendarField() {
288        return this.unitType.getCalendarField();
289    }
290
291    /**
292     * Returns the (approximate) number of milliseconds for the given unit and
293     * unit count.
294     * <P>
295     * This value is an approximation some of the time (e.g. months are
296     * assumed to have 31 days) but this shouldn't matter.
297     *
298     * @param unit  the unit.
299     * @param count  the unit count.
300     *
301     * @return The number of milliseconds.
302     *
303     * @since 1.0.13
304     */
305    private static long getMillisecondCount(DateTickUnitType unit, int count) {
306
307        if (unit.equals(DateTickUnitType.YEAR)) {
308            return (365L * 24L * 60L * 60L * 1000L) * count;
309        }
310        else if (unit.equals(DateTickUnitType.MONTH)) {
311            return (31L * 24L * 60L * 60L * 1000L) * count;
312        }
313        else if (unit.equals(DateTickUnitType.DAY)) {
314            return (24L * 60L * 60L * 1000L) * count;
315        }
316        else if (unit.equals(DateTickUnitType.HOUR)) {
317            return (60L * 60L * 1000L) * count;
318        }
319        else if (unit.equals(DateTickUnitType.MINUTE)) {
320            return (60L * 1000L) * count;
321        }
322        else if (unit.equals(DateTickUnitType.SECOND)) {
323            return 1000L * count;
324        }
325        else if (unit.equals(DateTickUnitType.MILLISECOND)) {
326            return count;
327        }
328        else {
329            throw new IllegalArgumentException("The 'unit' argument has a " 
330                    + "value that is not recognised.");
331        }
332
333    }
334
335    /**
336     * A utility method that is used internally to convert the old unit
337     * constants into the corresponding enumerated value.
338     *
339     * @param unit  the unit specified using the deprecated integer codes.
340     *
341     * @return The unit type.
342     *
343     * @since 1.0.13
344     */
345    private static DateTickUnitType intToUnitType(int unit) {
346        switch (unit) {
347            case YEAR: return DateTickUnitType.YEAR;
348            case MONTH: return DateTickUnitType.MONTH;
349            case DAY: return DateTickUnitType.DAY;
350            case HOUR: return DateTickUnitType.HOUR;
351            case MINUTE: return DateTickUnitType.MINUTE;
352            case SECOND: return DateTickUnitType.SECOND;
353            case MILLISECOND: return DateTickUnitType.MILLISECOND;
354            default: throw new IllegalArgumentException(
355                    "Unrecognised 'unit' value " + unit + ".");
356        }
357    }
358
359    /**
360     * Converts a unit type to the corresponding deprecated integer constant.
361     *
362     * @param unitType  the unit type (<code>null</code> not permitted).
363     *
364     * @return The int code.
365     *
366     * @since 1.0.13
367     */
368    private static int unitTypeToInt(DateTickUnitType unitType) {
369        ParamChecks.nullNotPermitted(unitType, "unitType");
370        if (unitType.equals(DateTickUnitType.YEAR)) {
371            return YEAR;
372        }
373        else if (unitType.equals(DateTickUnitType.MONTH)) {
374            return MONTH;
375        }
376        else if (unitType.equals(DateTickUnitType.DAY)) {
377            return DAY;
378        }
379        else if (unitType.equals(DateTickUnitType.HOUR)) {
380            return HOUR;
381        }
382        else if (unitType.equals(DateTickUnitType.MINUTE)) {
383            return MINUTE;
384        }
385        else if (unitType.equals(DateTickUnitType.SECOND)) {
386            return SECOND;
387        }
388        else if (unitType.equals(DateTickUnitType.MILLISECOND)) {
389            return MILLISECOND;
390        }
391        else {
392            throw new IllegalArgumentException(
393                    "The 'unitType' is not recognised");
394        }
395    }
396
397    /**
398     * A utility method to put a default in place if a null formatter is
399     * supplied.
400     *
401     * @param formatter  the formatter (<code>null</code> permitted).
402     *
403     * @return The formatter if it is not null, otherwise a default.
404     */
405    private static DateFormat notNull(DateFormat formatter) {
406        if (formatter == null) {
407            return DateFormat.getDateInstance(DateFormat.SHORT);
408        }
409        else {
410            return formatter;
411        }
412    }
413
414    /**
415     * Tests this unit for equality with another object.
416     *
417     * @param obj  the object (<code>null</code> permitted).
418     *
419     * @return <code>true</code> or <code>false</code>.
420     */
421    @Override
422    public boolean equals(Object obj) {
423        if (obj == this) {
424            return true;
425        }
426        if (!(obj instanceof DateTickUnit)) {
427            return false;
428        }
429        if (!super.equals(obj)) {
430            return false;
431        }
432        DateTickUnit that = (DateTickUnit) obj;
433        if (!(this.unitType.equals(that.unitType))) {
434            return false;
435        }
436        if (this.count != that.count) {
437            return false;
438        }
439        if (!ObjectUtilities.equal(this.formatter, that.formatter)) {
440            return false;
441        }
442        return true;
443    }
444
445    /**
446     * Returns a hash code for this object.
447     *
448     * @return A hash code.
449     */
450    @Override
451    public int hashCode() {
452        int result = 19;
453        result = 37 * result + this.unitType.hashCode();
454        result = 37 * result + this.count;
455        result = 37 * result + this.formatter.hashCode();
456        return result;
457    }
458
459    /**
460     * Returns a string representation of this instance, primarily used for
461     * debugging purposes.
462     *
463     * @return A string representation of this instance.
464     */
465    @Override
466    public String toString() {
467        return "DateTickUnit[" + this.unitType.toString() + ", "
468                + this.count + "]";
469    }
470
471    /**
472     * A constant for years.
473     *
474     * @deprecated As of version 1.0.13, use {@link DateTickUnitType} instead.
475     */
476    public static final int YEAR = 0;
477
478    /**
479     * A constant for months.
480     *
481     * @deprecated As of version 1.0.13, use {@link DateTickUnitType} instead.
482     */
483    public static final int MONTH = 1;
484
485    /**
486     * A constant for days.
487     *
488     * @deprecated As of version 1.0.13, use {@link DateTickUnitType} instead.
489     */
490    public static final int DAY = 2;
491
492    /**
493     * A constant for hours.
494     *
495     * @deprecated As of version 1.0.13, use {@link DateTickUnitType} instead.
496     */
497    public static final int HOUR = 3;
498
499    /**
500     * A constant for minutes.
501     *
502     * @deprecated As of version 1.0.13, use {@link DateTickUnitType} instead.
503     */
504    public static final int MINUTE = 4;
505
506    /**
507     * A constant for seconds.
508     *
509     * @deprecated As of version 1.0.13, use {@link DateTickUnitType} instead.
510     */
511    public static final int SECOND = 5;
512
513    /**
514     * A constant for milliseconds.
515     *
516     * @deprecated As of version 1.0.13, use {@link DateTickUnitType} instead.
517     */
518    public static final int MILLISECOND = 6;
519
520    /**
521     * The unit.
522     *
523     * @deprecated As of version 1.0.13, use the unitType field.
524     */
525    private int unit;
526
527    /**
528     * The roll unit.
529     *
530     * @deprecated As of version 1.0.13, use the rollUnitType field.
531     */
532    private int rollUnit;
533
534    /**
535     * Creates a new date tick unit.  You can specify the units using one of
536     * the constants YEAR, MONTH, DAY, HOUR, MINUTE, SECOND or MILLISECOND.
537     * In addition, you can specify a unit count, and a date format.
538     *
539     * @param unit  the unit.
540     * @param count  the unit count.
541     * @param formatter  the date formatter (defaults to DateFormat.SHORT).
542     *
543     * @deprecated As of version 1.0.13, use {@link #DateTickUnit(
544     *     DateTickUnitType, int, DateFormat)}.
545     */
546    public DateTickUnit(int unit, int count, DateFormat formatter) {
547        this(unit, count, unit, count, formatter);
548    }
549
550    /**
551     * Creates a new date tick unit.  The dates will be formatted using a
552     * SHORT format for the default locale.
553     *
554     * @param unit  the unit.
555     * @param count  the unit count.
556     *
557     * @deprecated As of version 1.0.13, use {@link #DateTickUnit(
558     *     DateTickUnitType, int)}.
559     */
560    public DateTickUnit(int unit, int count) {
561        this(unit, count, null);
562    }
563
564    /**
565     * Creates a new unit.
566     *
567     * @param unit  the unit.
568     * @param count  the count.
569     * @param rollUnit  the roll unit.
570     * @param rollCount  the roll count.
571     * @param formatter  the date formatter (defaults to DateFormat.SHORT).
572     *
573     * @deprecated As of version 1.0.13, use {@link #DateTickUnit(
574     *     DateTickUnitType, int, DateTickUnitType, int, DateFormat)}.
575     */
576    public DateTickUnit(int unit, int count, int rollUnit, int rollCount,
577                        DateFormat formatter) {
578        this(intToUnitType(unit), count, intToUnitType(rollUnit), rollCount,
579                notNull(formatter));
580    }
581
582    /**
583     * Returns the date unit.  This will be one of the constants
584     * <code>YEAR</code>, <code>MONTH</code>, <code>DAY</code>,
585     * <code>HOUR</code>, <code>MINUTE</code>, <code>SECOND</code> or
586     * <code>MILLISECOND</code>, defined by this class.  Note that these
587     * constants do NOT correspond to those defined in Java's
588     * <code>Calendar</code> class.
589     *
590     * @return The date unit.
591     *
592     * @deprecated As of 1.0.13, use the getUnitType() method.
593     */
594    public int getUnit() {
595        return this.unit;
596    }
597
598    /**
599     * Returns the unit count.
600     *
601     * @return The unit count.
602     *
603     * @deprecated As of version 1.0.13, use {@link #getMultiple()}.
604     */
605    public int getCount() {
606        return this.count;
607    }
608
609    /**
610     * Returns the roll unit.  This is the amount by which the tick advances if
611     * it is "hidden" when displayed on a segmented date axis.  Typically the
612     * roll will be smaller than the regular tick unit (for example, a 7 day
613     * tick unit might use a 1 day roll).
614     *
615     * @return The roll unit.
616     *
617     * @deprecated As of version 1.0.13, use {@link #getRollUnitType()}.
618     */
619    public int getRollUnit() {
620        return this.rollUnit;
621    }
622
623    /**
624     * Returns the roll count.
625     *
626     * @return The roll count.
627     *
628     * @deprecated As of version 1.0.13, use the {@link #getRollMultiple()}
629     *
630     */
631    public int getRollCount() {
632        return this.rollCount;
633    }
634
635    /**
636     * Calculates a new date by adding this unit to the base date, with
637     * calculations performed in the default timezone and locale.
638     *
639     * @param base  the base date.
640     *
641     * @return A new date one unit after the base date.
642     *
643     * @see #addToDate(Date, TimeZone)
644     *
645     * @deprecated As of JFreeChart 1.0.10, this method is deprecated - you
646     *     should use {@link #addToDate(Date, TimeZone)} instead.
647     */
648    public Date addToDate(Date base) {
649        return addToDate(base, TimeZone.getDefault());
650    }
651
652}