001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2009, 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 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * -----------------
028 * DateTickUnit.java
029 * -----------------
030 * (C) Copyright 2000-2009, 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 *
054 */
055
056package org.jfree.chart.axis;
057
058import java.io.Serializable;
059import java.text.DateFormat;
060import java.util.Calendar;
061import java.util.Date;
062import java.util.TimeZone;
063
064import org.jfree.util.ObjectUtilities;
065
066/**
067 * A tick unit for use by subclasses of {@link DateAxis}.  Instances of this
068 * class are immutable.
069 */
070public class DateTickUnit extends TickUnit implements Serializable {
071
072    /** For serialization. */
073    private static final long serialVersionUID = -7289292157229621901L;
074
075    /**
076     * The units.
077     *
078     * @since 1.0.13
079     */
080    private DateTickUnitType unitType;
081
082    /** The unit count. */
083    private int count;
084
085    /**
086     * The roll unit type.
087     *
088     * @since 1.0.13
089     */
090    private DateTickUnitType rollUnitType;
091
092    /** The roll count. */
093    private int rollCount;
094
095    /** The date formatter. */
096    private DateFormat formatter;
097
098    /**
099     * Creates a new date tick unit.
100     *
101     * @param unitType  the unit type (<code>null</code> not permitted).
102     * @param multiple  the multiple (of the unit type, must be > 0).
103     *
104     * @since 1.0.13
105     */
106    public DateTickUnit(DateTickUnitType unitType, int multiple) {
107        this(unitType, multiple, DateFormat.getDateInstance(DateFormat.SHORT));
108    }
109
110    /**
111     * Creates a new date tick unit.
112     *
113     * @param unitType  the unit type (<code>null</code> not permitted).
114     * @param multiple  the multiple (of the unit type, must be > 0).
115     * @param formatter  the date formatter (<code>null</code> not permitted).
116     *
117     * @since 1.0.13
118     */
119    public DateTickUnit(DateTickUnitType unitType, int multiple,
120            DateFormat formatter) {
121        this(unitType, multiple, unitType, multiple, formatter);
122    }
123
124    /**
125     * Creates a new unit.
126     *
127     * @param unitType  the unit.
128     * @param multiple  the multiple.
129     * @param rollUnitType  the roll unit.
130     * @param rollMultiple  the roll multiple.
131     * @param formatter  the date formatter (<code>null</code> not permitted).
132     *
133     * @since 1.0.13
134     */
135    public DateTickUnit(DateTickUnitType unitType, int multiple,
136            DateTickUnitType rollUnitType, int rollMultiple,
137            DateFormat formatter) {
138        super(DateTickUnit.getMillisecondCount(unitType, multiple));
139        if (formatter == null) {
140            throw new IllegalArgumentException("Null 'formatter' argument.");
141        }
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 > 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    public String valueToString(double milliseconds) {
209        return this.formatter.format(new Date((long) milliseconds));
210    }
211
212    /**
213     * Formats a date using the tick unit's formatter.
214     *
215     * @param date  the date.
216     *
217     * @return The formatted date.
218     */
219    public String dateToString(Date date) {
220        return this.formatter.format(date);
221    }
222
223    /**
224     * Calculates a new date by adding this unit to the base date.
225     *
226     * @param base  the base date.
227     * @param zone  the time zone for the date calculation.
228     *
229     * @return A new date one unit after the base date.
230     *
231     * @since 1.0.6
232     */
233    public Date addToDate(Date base, TimeZone zone) {
234        // as far as I know, the Locale for the calendar only affects week
235        // number calculations, and since DateTickUnit doesn't do week
236        // arithmetic, the default locale (whatever it is) should be fine
237        // here...
238        Calendar calendar = Calendar.getInstance(zone);
239        calendar.setTime(base);
240        calendar.add(this.unitType.getCalendarField(), this.count);
241        return calendar.getTime();
242    }
243
244    /**
245     * Rolls the date forward by the amount specified by the roll unit and
246     * count.
247     *
248     * @param base  the base date.
249
250     * @return The rolled date.
251     *
252     * @see #rollDate(Date, TimeZone)
253     */
254    public Date rollDate(Date base) {
255        return rollDate(base, TimeZone.getDefault());
256    }
257
258    /**
259     * Rolls the date forward by the amount specified by the roll unit and
260     * count.
261     *
262     * @param base  the base date.
263     * @param zone  the time zone.
264     *
265     * @return The rolled date.
266     *
267     * @since 1.0.6
268     */
269    public Date rollDate(Date base, TimeZone zone) {
270        // as far as I know, the Locale for the calendar only affects week
271        // number calculations, and since DateTickUnit doesn't do week
272        // arithmetic, the default locale (whatever it is) should be fine
273        // here...
274        Calendar calendar = Calendar.getInstance(zone);
275        calendar.setTime(base);
276        calendar.add(this.rollUnitType.getCalendarField(), this.rollCount);
277        return calendar.getTime();
278    }
279
280    /**
281     * Returns a field code that can be used with the <code>Calendar</code>
282     * class.
283     *
284     * @return The field code.
285     */
286    public int getCalendarField() {
287        return this.unitType.getCalendarField();
288    }
289
290    /**
291     * Returns the (approximate) number of milliseconds for the given unit and
292     * unit count.
293     * <P>
294     * This value is an approximation some of the time (e.g. months are
295     * assumed to have 31 days) but this shouldn't matter.
296     *
297     * @param unit  the unit.
298     * @param count  the unit count.
299     *
300     * @return The number of milliseconds.
301     *
302     * @since 1.0.13
303     */
304    private static long getMillisecondCount(DateTickUnitType unit, int count) {
305
306        if (unit.equals(DateTickUnitType.YEAR)) {
307            return (365L * 24L * 60L * 60L * 1000L) * count;
308        }
309        else if (unit.equals(DateTickUnitType.MONTH)) {
310            return (31L * 24L * 60L * 60L * 1000L) * count;
311        }
312        else if (unit.equals(DateTickUnitType.DAY)) {
313            return (24L * 60L * 60L * 1000L) * count;
314        }
315        else if (unit.equals(DateTickUnitType.HOUR)) {
316            return (60L * 60L * 1000L) * count;
317        }
318        else if (unit.equals(DateTickUnitType.MINUTE)) {
319            return (60L * 1000L) * count;
320        }
321        else if (unit.equals(DateTickUnitType.SECOND)) {
322            return 1000L * count;
323        }
324        else if (unit.equals(DateTickUnitType.MILLISECOND)) {
325            return count;
326        }
327        else {
328            throw new IllegalArgumentException("The 'unit' argument has a " +
329                        "value that is not recognised.");
330        }
331
332    }
333
334    /**
335     * A utility method that is used internally to convert the old unit
336     * constants into the corresponding enumerated value.
337     *
338     * @param unit  the unit specified using the deprecated integer codes.
339     *
340     * @return The unit type.
341     *
342     * @since 1.0.13
343     */
344    private static DateTickUnitType intToUnitType(int unit) {
345        switch (unit) {
346            case YEAR: return DateTickUnitType.YEAR;
347            case MONTH: return DateTickUnitType.MONTH;
348            case DAY: return DateTickUnitType.DAY;
349            case HOUR: return DateTickUnitType.HOUR;
350            case MINUTE: return DateTickUnitType.MINUTE;
351            case SECOND: return DateTickUnitType.SECOND;
352            case MILLISECOND: return DateTickUnitType.MILLISECOND;
353            default: throw new IllegalArgumentException(
354                    "Unrecognised 'unit' value " + unit + ".");
355        }
356    }
357
358    /**
359     * Converts a unit type to the corresponding deprecated integer constant.
360     *
361     * @param unitType  the unit type (<code>null</code> not permitted).
362     *
363     * @return The int code.
364     *
365     * @since 1.0.13
366     */
367    private static int unitTypeToInt(DateTickUnitType unitType) {
368        if (unitType == null) {
369            throw new IllegalArgumentException("Null 'unitType' argument.");
370        }
371        if (unitType.equals(DateTickUnitType.YEAR)) {
372            return YEAR;
373        }
374        else if (unitType.equals(DateTickUnitType.MONTH)) {
375            return MONTH;
376        }
377        else if (unitType.equals(DateTickUnitType.DAY)) {
378            return DAY;
379        }
380        else if (unitType.equals(DateTickUnitType.HOUR)) {
381            return HOUR;
382        }
383        else if (unitType.equals(DateTickUnitType.MINUTE)) {
384            return MINUTE;
385        }
386        else if (unitType.equals(DateTickUnitType.SECOND)) {
387            return SECOND;
388        }
389        else if (unitType.equals(DateTickUnitType.MILLISECOND)) {
390            return MILLISECOND;
391        }
392        else {
393            throw new IllegalArgumentException(
394                    "The 'unitType' is not recognised");
395        }
396    }
397
398    /**
399     * A utility method to put a default in place if a null formatter is
400     * supplied.
401     *
402     * @param formatter  the formatter (<code>null</code> permitted).
403     *
404     * @return The formatter if it is not null, otherwise a default.
405     */
406    private static DateFormat notNull(DateFormat formatter) {
407        if (formatter == null) {
408            return DateFormat.getDateInstance(DateFormat.SHORT);
409        }
410        else {
411            return formatter;
412        }
413    }
414
415    /**
416     * Tests this unit for equality with another object.
417     *
418     * @param obj  the object (<code>null</code> permitted).
419     *
420     * @return <code>true</code> or <code>false</code>.
421     */
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    public int hashCode() {
451        int result = 19;
452        result = 37 * result + this.unitType.hashCode();
453        result = 37 * result + this.count;
454        result = 37 * result + this.formatter.hashCode();
455        return result;
456    }
457
458    /**
459     * Returns a string representation of this instance, primarily used for
460     * debugging purposes.
461     *
462     * @return A string representation of this instance.
463     */
464    public String toString() {
465        return "DateTickUnit[" + this.unitType.toString() + ", "
466                + this.count + "]";
467    }
468
469    /**
470     * A constant for years.
471     *
472     * @deprecated As of version 1.0.13, use {@link DateTickUnitType} instead.
473     */
474    public static final int YEAR = 0;
475
476    /**
477     * A constant for months.
478     *
479     * @deprecated As of version 1.0.13, use {@link DateTickUnitType} instead.
480     */
481    public static final int MONTH = 1;
482
483    /**
484     * A constant for days.
485     *
486     * @deprecated As of version 1.0.13, use {@link DateTickUnitType} instead.
487     */
488    public static final int DAY = 2;
489
490    /**
491     * A constant for hours.
492     *
493     * @deprecated As of version 1.0.13, use {@link DateTickUnitType} instead.
494     */
495    public static final int HOUR = 3;
496
497    /**
498     * A constant for minutes.
499     *
500     * @deprecated As of version 1.0.13, use {@link DateTickUnitType} instead.
501     */
502    public static final int MINUTE = 4;
503
504    /**
505     * A constant for seconds.
506     *
507     * @deprecated As of version 1.0.13, use {@link DateTickUnitType} instead.
508     */
509    public static final int SECOND = 5;
510
511    /**
512     * A constant for milliseconds.
513     *
514     * @deprecated As of version 1.0.13, use {@link DateTickUnitType} instead.
515     */
516    public static final int MILLISECOND = 6;
517
518    /**
519     * The unit.
520     *
521     * @deprecated As of version 1.0.13, use the unitType field.
522     */
523    private int unit;
524
525    /**
526     * The roll unit.
527     *
528     * @deprecated As of version 1.0.13, use the rollUnitType field.
529     */
530    private int rollUnit;
531
532    /**
533     * Creates a new date tick unit.  You can specify the units using one of
534     * the constants YEAR, MONTH, DAY, HOUR, MINUTE, SECOND or MILLISECOND.
535     * In addition, you can specify a unit count, and a date format.
536     *
537     * @param unit  the unit.
538     * @param count  the unit count.
539     * @param formatter  the date formatter (defaults to DateFormat.SHORT).
540     *
541     * @deprecated As of version 1.0.13, use {@link #DateTickUnit(
542     *     DateTickUnitType, int, DateFormat)}.
543     */
544    public DateTickUnit(int unit, int count, DateFormat formatter) {
545        this(unit, count, unit, count, formatter);
546    }
547
548    /**
549     * Creates a new date tick unit.  The dates will be formatted using a
550     * SHORT format for the default locale.
551     *
552     * @param unit  the unit.
553     * @param count  the unit count.
554     *
555     * @deprecated As of version 1.0.13, use {@link #DateTickUnit(
556     *     DateTickUnitType, int)}.
557     */
558    public DateTickUnit(int unit, int count) {
559        this(unit, count, null);
560    }
561
562    /**
563     * Creates a new unit.
564     *
565     * @param unit  the unit.
566     * @param count  the count.
567     * @param rollUnit  the roll unit.
568     * @param rollCount  the roll count.
569     * @param formatter  the date formatter (defaults to DateFormat.SHORT).
570     *
571     * @deprecated As of version 1.0.13, use {@link #DateTickUnit(
572     *     DateTickUnitType, int, DateTickUnitType, int, DateFormat)}.
573     */
574    public DateTickUnit(int unit, int count, int rollUnit, int rollCount,
575                        DateFormat formatter) {
576        this(intToUnitType(unit), count, intToUnitType(rollUnit), rollCount,
577                notNull(formatter));
578    }
579
580    /**
581     * Returns the date unit.  This will be one of the constants
582     * <code>YEAR</code>, <code>MONTH</code>, <code>DAY</code>,
583     * <code>HOUR</code>, <code>MINUTE</code>, <code>SECOND</code> or
584     * <code>MILLISECOND</code>, defined by this class.  Note that these
585     * constants do NOT correspond to those defined in Java's
586     * <code>Calendar</code> class.
587     *
588     * @return The date unit.
589     *
590     * @deprecated As of 1.0.13, use the getUnitType() method.
591     */
592    public int getUnit() {
593        return this.unit;
594    }
595
596    /**
597     * Returns the unit count.
598     *
599     * @return The unit count.
600     *
601     * @deprecated As of version 1.0.13, use {@link #getMultiple()}.
602     */
603    public int getCount() {
604        return this.count;
605    }
606
607    /**
608     * Returns the roll unit.  This is the amount by which the tick advances if
609     * it is "hidden" when displayed on a segmented date axis.  Typically the
610     * roll will be smaller than the regular tick unit (for example, a 7 day
611     * tick unit might use a 1 day roll).
612     *
613     * @return The roll unit.
614     *
615     * @deprecated As of version 1.0.13, use {@link #getRollUnitType()}.
616     */
617    public int getRollUnit() {
618        return this.rollUnit;
619    }
620
621    /**
622     * Returns the roll count.
623     *
624     * @return The roll count.
625     *
626     * @deprecated As of version 1.0.13, use the {@link #getRollMultiple()}
627     *
628     */
629    public int getRollCount() {
630        return this.rollCount;
631    }
632
633    /**
634     * Calculates a new date by adding this unit to the base date, with
635     * calculations performed in the default timezone and locale.
636     *
637     * @param base  the base date.
638     *
639     * @return A new date one unit after the base date.
640     *
641     * @see #addToDate(Date, TimeZone)
642     *
643     * @deprecated As of JFreeChart 1.0.10, this method is deprecated - you
644     *     should use {@link #addToDate(Date, TimeZone)} instead.
645     */
646    public Date addToDate(Date base) {
647        return addToDate(base, TimeZone.getDefault());
648    }
649
650}