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 * Second.java 029 * ----------- 030 * (C) Copyright 2001-2009, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes 036 * ------- 037 * 11-Oct-2001 : Version 1 (DG); 038 * 18-Dec-2001 : Changed order of parameters in constructor (DG); 039 * 19-Dec-2001 : Added a new constructor as suggested by Paul English (DG); 040 * 14-Feb-2002 : Fixed bug in Second(Date) constructor, and changed start of 041 * range to zero from one (DG); 042 * 26-Feb-2002 : Changed getStart(), getMiddle() and getEnd() methods to 043 * evaluate with reference to a particular time zone (DG); 044 * 13-Mar-2002 : Added parseSecond() method (DG); 045 * 10-Sep-2002 : Added getSerialIndex() method (DG); 046 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG); 047 * 10-Jan-2003 : Changed base class and method names (DG); 048 * 05-Mar-2003 : Fixed bug in getLastMillisecond() picked up in JUnit 049 * tests (DG); 050 * 13-Mar-2003 : Moved to com.jrefinery.data.time package and implemented 051 * Serializable (DG); 052 * 21-Oct-2003 : Added hashCode() method (DG); 053 * ------------- JFREECHART 1.0.x --------------------------------------------- 054 * 05-Oct-2006 : Updated API docs (DG); 055 * 06-Oct-2006 : Refactored to cache first and last millisecond values (DG); 056 * 16-Sep-2008 : Deprecated DEFAULT_TIME_ZONE (DG); 057 * 02-Mar-2009 : Added new constructor with Locale (DG); 058 * 059 */ 060 061 package org.jfree.data.time; 062 063 import java.io.Serializable; 064 import java.util.Calendar; 065 import java.util.Date; 066 import java.util.Locale; 067 import java.util.TimeZone; 068 069 /** 070 * Represents a second in a particular day. This class is immutable, which is 071 * a requirement for all {@link RegularTimePeriod} subclasses. 072 */ 073 public class Second extends RegularTimePeriod implements Serializable { 074 075 /** For serialization. */ 076 private static final long serialVersionUID = -6536564190712383466L; 077 078 /** Useful constant for the first second in a minute. */ 079 public static final int FIRST_SECOND_IN_MINUTE = 0; 080 081 /** Useful constant for the last second in a minute. */ 082 public static final int LAST_SECOND_IN_MINUTE = 59; 083 084 /** The day. */ 085 private Day day; 086 087 /** The hour of the day. */ 088 private byte hour; 089 090 /** The minute. */ 091 private byte minute; 092 093 /** The second. */ 094 private byte second; 095 096 /** 097 * The first millisecond. We don't store the last millisecond, because it 098 * is always firstMillisecond + 999L. 099 */ 100 private long firstMillisecond; 101 102 /** 103 * Constructs a new Second, based on the system date/time. 104 */ 105 public Second() { 106 this(new Date()); 107 } 108 109 /** 110 * Constructs a new Second. 111 * 112 * @param second the second (0 to 24*60*60-1). 113 * @param minute the minute (<code>null</code> not permitted). 114 */ 115 public Second(int second, Minute minute) { 116 if (minute == null) { 117 throw new IllegalArgumentException("Null 'minute' argument."); 118 } 119 this.day = minute.getDay(); 120 this.hour = (byte) minute.getHourValue(); 121 this.minute = (byte) minute.getMinute(); 122 this.second = (byte) second; 123 peg(Calendar.getInstance()); 124 } 125 126 /** 127 * Creates a new second. 128 * 129 * @param second the second (0-59). 130 * @param minute the minute (0-59). 131 * @param hour the hour (0-23). 132 * @param day the day (1-31). 133 * @param month the month (1-12). 134 * @param year the year (1900-9999). 135 */ 136 public Second(int second, int minute, int hour, 137 int day, int month, int year) { 138 this(second, new Minute(minute, hour, day, month, year)); 139 } 140 141 /** 142 * Constructs a new instance from the specified date/time and the default 143 * time zone.. 144 * 145 * @param time the time (<code>null</code> not permitted). 146 * 147 * @see #Second(Date, TimeZone) 148 */ 149 public Second(Date time) { 150 this(time, TimeZone.getDefault(), Locale.getDefault()); 151 } 152 153 /** 154 * Creates a new second based on the supplied time and time zone. 155 * 156 * @param time the instant in time. 157 * @param zone the time zone. 158 * 159 * @deprecated As of 1.0.13, use the constructor that specifies the locale 160 * also. 161 */ 162 public Second(Date time, TimeZone zone) { 163 this(time, zone, Locale.getDefault()); 164 } 165 166 /** 167 * Creates a new second based on the supplied time and time zone. 168 * 169 * @param time the time (<code>null</code> not permitted). 170 * @param zone the time zone (<code>null</code> not permitted). 171 * @param locale the locale (<code>null</code> not permitted). 172 * 173 * @since 1.0.13 174 */ 175 public Second(Date time, TimeZone zone, Locale locale) { 176 Calendar calendar = Calendar.getInstance(zone, locale); 177 calendar.setTime(time); 178 this.second = (byte) calendar.get(Calendar.SECOND); 179 this.minute = (byte) calendar.get(Calendar.MINUTE); 180 this.hour = (byte) calendar.get(Calendar.HOUR_OF_DAY); 181 this.day = new Day(time, zone, locale); 182 peg(calendar); 183 } 184 185 /** 186 * Returns the second within the minute. 187 * 188 * @return The second (0 - 59). 189 */ 190 public int getSecond() { 191 return this.second; 192 } 193 194 /** 195 * Returns the minute. 196 * 197 * @return The minute (never <code>null</code>). 198 */ 199 public Minute getMinute() { 200 return new Minute(this.minute, new Hour(this.hour, this.day)); 201 } 202 203 /** 204 * Returns the first millisecond of the second. This will be determined 205 * relative to the time zone specified in the constructor, or in the 206 * calendar instance passed in the most recent call to the 207 * {@link #peg(Calendar)} method. 208 * 209 * @return The first millisecond of the second. 210 * 211 * @see #getLastMillisecond() 212 */ 213 public long getFirstMillisecond() { 214 return this.firstMillisecond; 215 } 216 217 /** 218 * Returns the last millisecond of the second. This will be 219 * determined relative to the time zone specified in the constructor, or 220 * in the calendar instance passed in the most recent call to the 221 * {@link #peg(Calendar)} method. 222 * 223 * @return The last millisecond of the second. 224 * 225 * @see #getFirstMillisecond() 226 */ 227 public long getLastMillisecond() { 228 return this.firstMillisecond + 999L; 229 } 230 231 /** 232 * Recalculates the start date/time and end date/time for this time period 233 * relative to the supplied calendar (which incorporates a time zone). 234 * 235 * @param calendar the calendar (<code>null</code> not permitted). 236 * 237 * @since 1.0.3 238 */ 239 public void peg(Calendar calendar) { 240 this.firstMillisecond = getFirstMillisecond(calendar); 241 } 242 243 /** 244 * Returns the second preceding this one. 245 * 246 * @return The second preceding this one. 247 */ 248 public RegularTimePeriod previous() { 249 Second result = null; 250 if (this.second != FIRST_SECOND_IN_MINUTE) { 251 result = new Second(this.second - 1, getMinute()); 252 } 253 else { 254 Minute previous = (Minute) getMinute().previous(); 255 if (previous != null) { 256 result = new Second(LAST_SECOND_IN_MINUTE, previous); 257 } 258 } 259 return result; 260 } 261 262 /** 263 * Returns the second following this one. 264 * 265 * @return The second following this one. 266 */ 267 public RegularTimePeriod next() { 268 Second result = null; 269 if (this.second != LAST_SECOND_IN_MINUTE) { 270 result = new Second(this.second + 1, getMinute()); 271 } 272 else { 273 Minute next = (Minute) getMinute().next(); 274 if (next != null) { 275 result = new Second(FIRST_SECOND_IN_MINUTE, next); 276 } 277 } 278 return result; 279 } 280 281 /** 282 * Returns a serial index number for the minute. 283 * 284 * @return The serial index number. 285 */ 286 public long getSerialIndex() { 287 long hourIndex = this.day.getSerialIndex() * 24L + this.hour; 288 long minuteIndex = hourIndex * 60L + this.minute; 289 return minuteIndex * 60L + this.second; 290 } 291 292 /** 293 * Returns the first millisecond of the minute. 294 * 295 * @param calendar the calendar/timezone (<code>null</code> not permitted). 296 * 297 * @return The first millisecond. 298 * 299 * @throws NullPointerException if <code>calendar</code> is 300 * <code>null</code>. 301 */ 302 public long getFirstMillisecond(Calendar calendar) { 303 int year = this.day.getYear(); 304 int month = this.day.getMonth() - 1; 305 int day = this.day.getDayOfMonth(); 306 calendar.clear(); 307 calendar.set(year, month, day, this.hour, this.minute, this.second); 308 calendar.set(Calendar.MILLISECOND, 0); 309 //return calendar.getTimeInMillis(); // this won't work for JDK 1.3 310 return calendar.getTime().getTime(); 311 } 312 313 /** 314 * Returns the last millisecond of the second. 315 * 316 * @param calendar the calendar/timezone (<code>null</code> not permitted). 317 * 318 * @return The last millisecond. 319 * 320 * @throws NullPointerException if <code>calendar</code> is 321 * <code>null</code>. 322 */ 323 public long getLastMillisecond(Calendar calendar) { 324 return getFirstMillisecond(calendar) + 999L; 325 } 326 327 /** 328 * Tests the equality of this object against an arbitrary Object. 329 * <P> 330 * This method will return true ONLY if the object is a Second object 331 * representing the same second as this instance. 332 * 333 * @param obj the object to compare (<code>null</code> permitted). 334 * 335 * @return <code>true</code> if second and minute of this and the object 336 * are the same. 337 */ 338 public boolean equals(Object obj) { 339 if (obj == this) { 340 return true; 341 } 342 if (!(obj instanceof Second)) { 343 return false; 344 } 345 Second that = (Second) obj; 346 if (this.second != that.second) { 347 return false; 348 } 349 if (this.minute != that.minute) { 350 return false; 351 } 352 if (this.hour != that.hour) { 353 return false; 354 } 355 if (!this.day.equals(that.day)) { 356 return false; 357 } 358 return true; 359 } 360 361 /** 362 * Returns a hash code for this object instance. The approach described by 363 * Joshua Bloch in "Effective Java" has been used here: 364 * <p> 365 * <code>http://developer.java.sun.com/developer/Books/effectivejava 366 * /Chapter3.pdf</code> 367 * 368 * @return A hash code. 369 */ 370 public int hashCode() { 371 int result = 17; 372 result = 37 * result + this.second; 373 result = 37 * result + this.minute; 374 result = 37 * result + this.hour; 375 result = 37 * result + this.day.hashCode(); 376 return result; 377 } 378 379 /** 380 * Returns an integer indicating the order of this Second object relative 381 * to the specified 382 * object: negative == before, zero == same, positive == after. 383 * 384 * @param o1 the object to compare. 385 * 386 * @return negative == before, zero == same, positive == after. 387 */ 388 public int compareTo(Object o1) { 389 int result; 390 391 // CASE 1 : Comparing to another Second object 392 // ------------------------------------------- 393 if (o1 instanceof Second) { 394 Second s = (Second) o1; 395 if (this.firstMillisecond < s.firstMillisecond) { 396 return -1; 397 } 398 else if (this.firstMillisecond > s.firstMillisecond) { 399 return 1; 400 } 401 else { 402 return 0; 403 } 404 } 405 406 // CASE 2 : Comparing to another TimePeriod object 407 // ----------------------------------------------- 408 else if (o1 instanceof RegularTimePeriod) { 409 // more difficult case - evaluate later... 410 result = 0; 411 } 412 413 // CASE 3 : Comparing to a non-TimePeriod object 414 // --------------------------------------------- 415 else { 416 // consider time periods to be ordered after general objects 417 result = 1; 418 } 419 420 return result; 421 } 422 423 /** 424 * Creates a new instance by parsing a string. The string is assumed to 425 * be in the format "YYYY-MM-DD HH:MM:SS", perhaps with leading or trailing 426 * whitespace. 427 * 428 * @param s the string to parse. 429 * 430 * @return The second, or <code>null</code> if the string is not parseable. 431 */ 432 public static Second parseSecond(String s) { 433 Second result = null; 434 s = s.trim(); 435 String daystr = s.substring(0, Math.min(10, s.length())); 436 Day day = Day.parseDay(daystr); 437 if (day != null) { 438 String hmsstr = s.substring(Math.min(daystr.length() + 1, 439 s.length()), s.length()); 440 hmsstr = hmsstr.trim(); 441 442 int l = hmsstr.length(); 443 String hourstr = hmsstr.substring(0, Math.min(2, l)); 444 String minstr = hmsstr.substring(Math.min(3, l), Math.min(5, l)); 445 String secstr = hmsstr.substring(Math.min(6, l), Math.min(8, l)); 446 int hour = Integer.parseInt(hourstr); 447 448 if ((hour >= 0) && (hour <= 23)) { 449 450 int minute = Integer.parseInt(minstr); 451 if ((minute >= 0) && (minute <= 59)) { 452 453 Minute m = new Minute(minute, new Hour(hour, day)); 454 int second = Integer.parseInt(secstr); 455 if ((second >= 0) && (second <= 59)) { 456 result = new Second(second, m); 457 } 458 } 459 } 460 } 461 return result; 462 } 463 464 }