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