001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, 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 * DateTickUnit.java 029 * ----------------- 030 * (C) Copyright 2000-present, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Chris Boek; 034 * 035 */ 036 037package org.jfree.chart.axis; 038 039import java.io.Serializable; 040import java.text.DateFormat; 041import java.util.Calendar; 042import java.util.Date; 043import java.util.Objects; 044import java.util.TimeZone; 045import org.jfree.chart.util.Args; 046 047/** 048 * A tick unit for use by subclasses of {@link DateAxis}. Instances of this 049 * class are immutable. 050 */ 051public class DateTickUnit extends TickUnit implements Serializable { 052 053 /** For serialization. */ 054 private static final long serialVersionUID = -7289292157229621901L; 055 056 /** 057 * The units. 058 */ 059 private DateTickUnitType unitType; 060 061 /** The unit count. */ 062 private int count; 063 064 /** 065 * The roll unit type. 066 */ 067 private DateTickUnitType rollUnitType; 068 069 /** The roll count. */ 070 private int rollCount; 071 072 /** The date formatter. */ 073 private DateFormat formatter; 074 075 /** 076 * Creates a new date tick unit. 077 * 078 * @param unitType the unit type ({@code null} not permitted). 079 * @param multiple the multiple (of the unit type, must be > 0). 080 */ 081 public DateTickUnit(DateTickUnitType unitType, int multiple) { 082 this(unitType, multiple, DateFormat.getDateInstance(DateFormat.SHORT)); 083 } 084 085 /** 086 * Creates a new date tick unit. 087 * 088 * @param unitType the unit type ({@code null} not permitted). 089 * @param multiple the multiple (of the unit type, must be > 0). 090 * @param formatter the date formatter ({@code null} not permitted). 091 */ 092 public DateTickUnit(DateTickUnitType unitType, int multiple, 093 DateFormat formatter) { 094 this(unitType, multiple, unitType, multiple, formatter); 095 } 096 097 /** 098 * Creates a new unit. 099 * 100 * @param unitType the unit. 101 * @param multiple the multiple. 102 * @param rollUnitType the roll unit. 103 * @param rollMultiple the roll multiple. 104 * @param formatter the date formatter ({@code null} not permitted). 105 */ 106 public DateTickUnit(DateTickUnitType unitType, int multiple, 107 DateTickUnitType rollUnitType, int rollMultiple, 108 DateFormat formatter) { 109 super(DateTickUnit.getMillisecondCount(unitType, multiple)); 110 Args.nullNotPermitted(formatter, "formatter"); 111 if (multiple <= 0) { 112 throw new IllegalArgumentException("Requires 'multiple' > 0."); 113 } 114 if (rollMultiple <= 0) { 115 throw new IllegalArgumentException("Requires 'rollMultiple' > 0."); 116 } 117 this.unitType = unitType; 118 this.count = multiple; 119 this.rollUnitType = rollUnitType; 120 this.rollCount = rollMultiple; 121 this.formatter = formatter; 122 } 123 124 /** 125 * Returns the unit type. 126 * 127 * @return The unit type (never {@code null}). 128 */ 129 public DateTickUnitType getUnitType() { 130 return this.unitType; 131 } 132 133 /** 134 * Returns the unit multiple. 135 * 136 * @return The unit multiple (always > 0). 137 */ 138 public int getMultiple() { 139 return this.count; 140 } 141 142 /** 143 * Returns the roll unit type. 144 * 145 * @return The roll unit type (never {@code null}). 146 */ 147 public DateTickUnitType getRollUnitType() { 148 return this.rollUnitType; 149 } 150 151 /** 152 * Returns the roll unit multiple. 153 * 154 * @return The roll unit multiple. 155 */ 156 public int getRollMultiple() { 157 return this.rollCount; 158 } 159 160 /** 161 * Formats a value. 162 * 163 * @param milliseconds date in milliseconds since 01-01-1970. 164 * 165 * @return The formatted date. 166 */ 167 @Override 168 public String valueToString(double milliseconds) { 169 return this.formatter.format(new Date((long) milliseconds)); 170 } 171 172 /** 173 * Formats a date using the tick unit's formatter. 174 * 175 * @param date the date. 176 * 177 * @return The formatted date. 178 */ 179 public String dateToString(Date date) { 180 return this.formatter.format(date); 181 } 182 183 /** 184 * Calculates a new date by adding this unit to the base date. 185 * 186 * @param base the base date. 187 * @param zone the time zone for the date calculation. 188 * 189 * @return A new date one unit after the base date. 190 */ 191 public Date addToDate(Date base, TimeZone zone) { 192 // as far as I know, the Locale for the calendar only affects week 193 // number calculations, and since DateTickUnit doesn't do week 194 // arithmetic, the default locale (whatever it is) should be fine 195 // here... 196 Calendar calendar = Calendar.getInstance(zone); 197 calendar.setTime(base); 198 calendar.add(this.unitType.getCalendarField(), this.count); 199 return calendar.getTime(); 200 } 201 202 /** 203 * Rolls the date forward by the amount specified by the roll unit and 204 * count. 205 * 206 * @param base the base date. 207 208 * @return The rolled date. 209 * 210 * @see #rollDate(Date, TimeZone) 211 */ 212 public Date rollDate(Date base) { 213 return rollDate(base, TimeZone.getDefault()); 214 } 215 216 /** 217 * Rolls the date forward by the amount specified by the roll unit and 218 * count. 219 * 220 * @param base the base date. 221 * @param zone the time zone. 222 * 223 * @return The rolled date. 224 */ 225 public Date rollDate(Date base, TimeZone zone) { 226 // as far as I know, the Locale for the calendar only affects week 227 // number calculations, and since DateTickUnit doesn't do week 228 // arithmetic, the default locale (whatever it is) should be fine 229 // here... 230 Calendar calendar = Calendar.getInstance(zone); 231 calendar.setTime(base); 232 calendar.add(this.rollUnitType.getCalendarField(), this.rollCount); 233 return calendar.getTime(); 234 } 235 236 /** 237 * Returns a field code that can be used with the {@code Calendar} 238 * class. 239 * 240 * @return The field code. 241 */ 242 public int getCalendarField() { 243 return this.unitType.getCalendarField(); 244 } 245 246 /** 247 * Returns the (approximate) number of milliseconds for the given unit and 248 * unit count. 249 * <P> 250 * This value is an approximation some of the time (e.g. months are 251 * assumed to have 31 days) but this shouldn't matter. 252 * 253 * @param unit the unit. 254 * @param count the unit count. 255 * 256 * @return The number of milliseconds. 257 */ 258 private static long getMillisecondCount(DateTickUnitType unit, int count) { 259 260 if (unit.equals(DateTickUnitType.YEAR)) { 261 return (365L * 24L * 60L * 60L * 1000L) * count; 262 } 263 else if (unit.equals(DateTickUnitType.MONTH)) { 264 return (31L * 24L * 60L * 60L * 1000L) * count; 265 } 266 else if (unit.equals(DateTickUnitType.DAY)) { 267 return (24L * 60L * 60L * 1000L) * count; 268 } 269 else if (unit.equals(DateTickUnitType.HOUR)) { 270 return (60L * 60L * 1000L) * count; 271 } 272 else if (unit.equals(DateTickUnitType.MINUTE)) { 273 return (60L * 1000L) * count; 274 } 275 else if (unit.equals(DateTickUnitType.SECOND)) { 276 return 1000L * count; 277 } 278 else if (unit.equals(DateTickUnitType.MILLISECOND)) { 279 return count; 280 } 281 else { 282 throw new IllegalArgumentException("The 'unit' argument has a " 283 + "value that is not recognised."); 284 } 285 286 } 287 288 /** 289 * Tests this unit for equality with another object. 290 * 291 * @param obj the object ({@code null} permitted). 292 * 293 * @return {@code true} or {@code false}. 294 */ 295 @Override 296 public boolean equals(Object obj) { 297 if (obj == this) { 298 return true; 299 } 300 if (!(obj instanceof DateTickUnit)) { 301 return false; 302 } 303 if (!super.equals(obj)) { 304 return false; 305 } 306 DateTickUnit that = (DateTickUnit) obj; 307 if (!(this.unitType.equals(that.unitType))) { 308 return false; 309 } 310 if (this.count != that.count) { 311 return false; 312 } 313 if (!Objects.equals(this.formatter, that.formatter)) { 314 return false; 315 } 316 return true; 317 } 318 319 /** 320 * Returns a hash code for this object. 321 * 322 * @return A hash code. 323 */ 324 @Override 325 public int hashCode() { 326 int result = 19; 327 result = 37 * result + this.unitType.hashCode(); 328 result = 37 * result + this.count; 329 result = 37 * result + this.formatter.hashCode(); 330 return result; 331 } 332 333 /** 334 * Returns a string representation of this instance, primarily used for 335 * debugging purposes. 336 * 337 * @return A string representation of this instance. 338 */ 339 @Override 340 public String toString() { 341 return "DateTickUnit[" + this.unitType.toString() + ", " 342 + this.count + "]"; 343 } 344 345}