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