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