    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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-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}