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 * Day.java 029 * -------- 030 * (C) Copyright 2001-2022, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): -; 034 * 035 */ 036 037package org.jfree.data.time; 038 039import java.io.Serializable; 040import java.text.DateFormat; 041import java.text.ParseException; 042import java.text.SimpleDateFormat; 043import java.util.Calendar; 044import java.util.Date; 045import java.util.Locale; 046import java.util.TimeZone; 047 048import org.jfree.chart.date.SerialDate; 049import org.jfree.chart.internal.Args; 050 051/** 052 * Represents a single day in the range 1-Jan-1900 to 31-Dec-9999. This class 053 * is immutable, which is a requirement for all {@link RegularTimePeriod} 054 * subclasses. 055 */ 056public class Day extends RegularTimePeriod implements Serializable { 057 058 /** For serialization. */ 059 private static final long serialVersionUID = -7082667380758962755L; 060 061 /** 062 * A date formatter - used for parsing, therefore we fix the locale 063 * so we get dependable results. 064 */ 065 protected static final DateFormat DATE_FORMAT 066 = new SimpleDateFormat("yyyy-MM-dd", Locale.UK); 067 068 /** A date formatter for the default locale. */ 069 protected static final DateFormat DATE_FORMAT_SHORT 070 = DateFormat.getDateInstance(DateFormat.SHORT); 071 072 /** A date formatter for the default locale. */ 073 protected static final DateFormat DATE_FORMAT_MEDIUM 074 = DateFormat.getDateInstance(DateFormat.MEDIUM); 075 076 /** A date formatter for the default locale. */ 077 protected static final DateFormat DATE_FORMAT_LONG 078 = DateFormat.getDateInstance(DateFormat.LONG); 079 080 /** The day (uses SerialDate for convenience). */ 081 private SerialDate serialDate; 082 083 /** The first millisecond. */ 084 private long firstMillisecond; 085 086 /** The last millisecond. */ 087 private long lastMillisecond; 088 089 /** 090 * Creates a new instance, derived from the system date/time. 091 * The time zone and locale are determined by the calendar 092 * returned by {@link RegularTimePeriod#getCalendarInstance()}. 093 */ 094 public Day() { 095 this(new Date()); 096 } 097 098 /** 099 * Constructs a new one day time period. 100 * The time zone and locale are determined by the calendar 101 * returned by {@link RegularTimePeriod#getCalendarInstance()}. 102 * 103 * @param day the day-of-the-month. 104 * @param month the month (1 to 12). 105 * @param year the year (1900 <= year <= 9999). 106 */ 107 public Day(int day, int month, int year) { 108 this.serialDate = SerialDate.createInstance(day, month, year); 109 peg(getCalendarInstance()); 110 } 111 112 /** 113 * Constructs a new one day time period. 114 * The time zone and locale are determined by the calendar 115 * returned by {@link RegularTimePeriod#getCalendarInstance()}. 116 * 117 * @param serialDate the day ({@code null} not permitted). 118 */ 119 public Day(SerialDate serialDate) { 120 Args.nullNotPermitted(serialDate, "serialDate"); 121 this.serialDate = serialDate; 122 peg(getCalendarInstance()); 123 } 124 125 /** 126 * Constructs a new instance, based on a particular date/time. 127 * The time zone and locale are determined by the calendar 128 * returned by {@link RegularTimePeriod#getCalendarInstance()}. 129 * 130 * @param time the time ({@code null} not permitted). 131 * 132 * @see #Day(Date, TimeZone, Locale) 133 */ 134 public Day(Date time) { 135 // defer argument checking... 136 this(time, getCalendarInstance()); 137 } 138 139 /** 140 * Constructs a new instance, based on a particular date/time and time zone. 141 * 142 * @param time the date/time ({@code null} not permitted). 143 * @param zone the time zone ({@code null} not permitted). 144 * @param locale the locale ({@code null} not permitted). 145 */ 146 public Day(Date time, TimeZone zone, Locale locale) { 147 Args.nullNotPermitted(time, "time"); 148 Args.nullNotPermitted(zone, "zone"); 149 Args.nullNotPermitted(locale, "locale"); 150 Calendar calendar = Calendar.getInstance(zone, locale); 151 calendar.setTime(time); 152 initUsing(calendar); 153 peg(calendar); 154 } 155 156 /** 157 * Constructs a new instance, based on a particular date/time. 158 * The time zone and locale are determined by the {@code calendar} 159 * parameter. 160 * 161 * @param time the date/time ({@code null} not permitted). 162 * @param calendar the calendar to use for calculations ({@code null} not permitted). 163 */ 164 public Day(Date time, Calendar calendar) { 165 Args.nullNotPermitted(time, "time"); 166 Args.nullNotPermitted(calendar, "calendar"); 167 calendar.setTime(time); 168 initUsing(calendar); 169 peg(calendar); 170 } 171 172 private void initUsing(Calendar calendar) { 173 int d = calendar.get(Calendar.DAY_OF_MONTH); 174 int m = calendar.get(Calendar.MONTH) + 1; 175 int y = calendar.get(Calendar.YEAR); 176 this.serialDate = SerialDate.createInstance(d, m, y); 177 } 178 179 /** 180 * Returns the day as a {@link SerialDate}. Note: the reference that is 181 * returned should be an instance of an immutable {@link SerialDate} 182 * (otherwise the caller could use the reference to alter the state of 183 * this {@code Day} instance, and {@code Day} is supposed 184 * to be immutable). 185 * 186 * @return The day as a {@link SerialDate}. 187 */ 188 public SerialDate getSerialDate() { 189 return this.serialDate; 190 } 191 192 /** 193 * Returns the year. 194 * 195 * @return The year. 196 */ 197 public int getYear() { 198 return this.serialDate.getYYYY(); 199 } 200 201 /** 202 * Returns the month. 203 * 204 * @return The month. 205 */ 206 public int getMonth() { 207 return this.serialDate.getMonth(); 208 } 209 210 /** 211 * Returns the day of the month. 212 * 213 * @return The day of the month. 214 */ 215 public int getDayOfMonth() { 216 return this.serialDate.getDayOfMonth(); 217 } 218 219 /** 220 * Returns the first millisecond of the day. This will be determined 221 * relative to the time zone specified in the constructor, or in the 222 * calendar instance passed in the most recent call to the 223 * {@link #peg(Calendar)} method. 224 * 225 * @return The first millisecond of the day. 226 * 227 * @see #getLastMillisecond() 228 */ 229 @Override 230 public long getFirstMillisecond() { 231 return this.firstMillisecond; 232 } 233 234 /** 235 * Returns the last millisecond of the day. This will be 236 * determined relative to the time zone specified in the constructor, or 237 * in the calendar instance passed in the most recent call to the 238 * {@link #peg(Calendar)} method. 239 * 240 * @return The last millisecond of the day. 241 * 242 * @see #getFirstMillisecond() 243 */ 244 @Override 245 public long getLastMillisecond() { 246 return this.lastMillisecond; 247 } 248 249 /** 250 * Recalculates the start date/time and end date/time for this time period 251 * relative to the supplied calendar (which incorporates a time zone). 252 * 253 * @param calendar the calendar ({@code null} not permitted). 254 * 255 * @since 1.0.3 256 */ 257 @Override 258 public void peg(Calendar calendar) { 259 this.firstMillisecond = getFirstMillisecond(calendar); 260 this.lastMillisecond = getLastMillisecond(calendar); 261 } 262 263 /** 264 * Returns the day preceding this one. 265 * No matter what time zone and locale this instance was created with, 266 * the returned instance will use the default calendar for time 267 * calculations, obtained with {@link RegularTimePeriod#getCalendarInstance()}. 268 * 269 * @return The day preceding this one. 270 */ 271 @Override 272 public RegularTimePeriod previous() { 273 Day result; 274 int serial = this.serialDate.toSerial(); 275 if (serial > SerialDate.SERIAL_LOWER_BOUND) { 276 SerialDate yesterday = SerialDate.createInstance(serial - 1); 277 return new Day(yesterday); 278 } 279 else { 280 result = null; 281 } 282 return result; 283 } 284 285 /** 286 * Returns the day following this one, or {@code null} if some limit 287 * has been reached. 288 * No matter what time zone and locale this instance was created with, 289 * the returned instance will use the default calendar for time 290 * calculations, obtained with {@link RegularTimePeriod#getCalendarInstance()}. 291 * 292 * @return The day following this one, or {@code null} if some limit 293 * has been reached. 294 */ 295 @Override 296 public RegularTimePeriod next() { 297 Day result; 298 int serial = this.serialDate.toSerial(); 299 if (serial < SerialDate.SERIAL_UPPER_BOUND) { 300 SerialDate tomorrow = SerialDate.createInstance(serial + 1); 301 return new Day(tomorrow); 302 } 303 else { 304 result = null; 305 } 306 return result; 307 } 308 309 /** 310 * Returns a serial index number for the day. 311 * 312 * @return The serial index number. 313 */ 314 @Override 315 public long getSerialIndex() { 316 return this.serialDate.toSerial(); 317 } 318 319 /** 320 * Returns the first millisecond of the day, evaluated using the supplied 321 * calendar (which determines the time zone). 322 * 323 * @param calendar calendar to use ({@code null} not permitted). 324 * 325 * @return The start of the day as milliseconds since 01-01-1970. 326 * 327 * @throws NullPointerException if {@code calendar} is 328 * {@code null}. 329 */ 330 @Override 331 public long getFirstMillisecond(Calendar calendar) { 332 int year = this.serialDate.getYYYY(); 333 int month = this.serialDate.getMonth(); 334 int day = this.serialDate.getDayOfMonth(); 335 calendar.clear(); 336 calendar.set(year, month - 1, day, 0, 0, 0); 337 calendar.set(Calendar.MILLISECOND, 0); 338 return calendar.getTimeInMillis(); 339 } 340 341 /** 342 * Returns the last millisecond of the day, evaluated using the supplied 343 * calendar (which determines the time zone). 344 * 345 * @param calendar calendar to use ({@code null} not permitted). 346 * 347 * @return The end of the day as milliseconds since 01-01-1970. 348 * 349 * @throws NullPointerException if {@code calendar} is 350 * {@code null}. 351 */ 352 @Override 353 public long getLastMillisecond(Calendar calendar) { 354 int year = this.serialDate.getYYYY(); 355 int month = this.serialDate.getMonth(); 356 int day = this.serialDate.getDayOfMonth(); 357 calendar.clear(); 358 calendar.set(year, month - 1, day, 23, 59, 59); 359 calendar.set(Calendar.MILLISECOND, 999); 360 return calendar.getTimeInMillis(); 361 } 362 363 /** 364 * Tests the equality of this Day object to an arbitrary object. Returns 365 * true if the target is a Day instance or a SerialDate instance 366 * representing the same day as this object. In all other cases, 367 * returns false. 368 * 369 * @param obj the object ({@code null} permitted). 370 * 371 * @return A flag indicating whether or not an object is equal to this day. 372 */ 373 @Override 374 public boolean equals(Object obj) { 375 if (obj == this) { 376 return true; 377 } 378 if (!(obj instanceof Day)) { 379 return false; 380 } 381 Day that = (Day) obj; 382 if (!this.serialDate.equals(that.getSerialDate())) { 383 return false; 384 } 385 return true; 386 } 387 388 /** 389 * Returns a hash code for this object instance. The approach described by 390 * Joshua Bloch in "Effective Java" has been used here: 391 * <p> 392 * {@code http://developer.java.sun.com/developer/Books/effectivejava 393 * /Chapter3.pdf} 394 * 395 * @return A hash code. 396 */ 397 @Override 398 public int hashCode() { 399 return this.serialDate.hashCode(); 400 } 401 402 /** 403 * Returns an integer indicating the order of this Day object relative to 404 * the specified object: 405 * 406 * negative == before, zero == same, positive == after. 407 * 408 * @param o1 the object to compare. 409 * 410 * @return negative == before, zero == same, positive == after. 411 */ 412 @Override 413 public int compareTo(Object o1) { 414 int result; 415 416 // CASE 1 : Comparing to another Day object 417 // ---------------------------------------- 418 if (o1 instanceof Day) { 419 Day d = (Day) o1; 420 result = -d.getSerialDate().compare(this.serialDate); 421 } 422 423 // CASE 2 : Comparing to another TimePeriod object 424 // ----------------------------------------------- 425 else if (o1 instanceof RegularTimePeriod) { 426 // more difficult case - evaluate later... 427 result = 0; 428 } 429 430 // CASE 3 : Comparing to a non-TimePeriod object 431 // --------------------------------------------- 432 else { 433 // consider time periods to be ordered after general objects 434 result = 1; 435 } 436 437 return result; 438 } 439 440 /** 441 * Returns a string representing the day. 442 * 443 * @return A string representing the day. 444 */ 445 @Override 446 public String toString() { 447 return this.serialDate.toString(); 448 } 449 450 /** 451 * Parses the string argument as a day. 452 * <P> 453 * This method is required to recognise YYYY-MM-DD as a valid format. 454 * Anything else, for now, is a bonus. 455 * 456 * @param s the date string to parse. 457 * 458 * @return {@code null} if the string does not contain any parseable 459 * string, the day otherwise. 460 */ 461 public static Day parseDay(String s) { 462 try { 463 return new Day (Day.DATE_FORMAT.parse(s)); 464 } 465 catch (ParseException e1) { 466 try { 467 return new Day (Day.DATE_FORMAT_SHORT.parse(s)); 468 } 469 catch (ParseException e2) { 470 // ignore 471 } 472 } 473 return null; 474 } 475 476}