1 package org.apache.commons.net.ntp; 2 /* 3 * Licensed to the Apache Software Foundation (ASF) under one or more 4 * contributor license agreements. See the NOTICE file distributed with 5 * this work for additional information regarding copyright ownership. 6 * The ASF licenses this file to You under the Apache License, Version 2.0 7 * (the "License"); you may not use this file except in compliance with 8 * the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 20 21 import java.lang.ref.SoftReference; 22 import java.text.DateFormat; 23 import java.text.SimpleDateFormat; 24 import java.util.Date; 25 import java.util.Locale; 26 import java.util.TimeZone; 27 28 /*** 29 * TimeStamp class represents the Network Time Protocol (NTP) timestamp 30 * as defined in RFC-1305 and SNTP (RFC-2030). It is represented as a 31 * 64-bit unsigned fixed-point number in seconds relative to 0-hour on 1-January-1900. 32 * The 32-bit low-order bits are the fractional seconds whose precision is 33 * about 200 picoseconds. Assumes overflow date when date passes MAX_LONG 34 * and reverts back to 0 is 2036 and not 1900. Test for most significant 35 * bit: if MSB=0 then 2036 basis is used otherwise 1900 if MSB=1. 36 * <p> 37 * Methods exist to convert NTP timestamps to and from the equivalent Java date 38 * representation, which is the number of milliseconds since the standard base 39 * time known as "the epoch", namely January 1, 1970, 00:00:00 GMT. 40 * </p> 41 * 42 * @author Jason Mathews, MITRE Corp 43 * @version $Revision: 658518 $ $Date: 2008-05-21 02:04:30 +0100 (Wed, 21 May 2008) $ 44 * @see java.util.Date 45 */ 46 public class TimeStamp implements java.io.Serializable, Comparable // TODO add comparable type? 47 { 48 49 /** 50 * baseline NTP time if bit-0=0 -> 7-Feb-2036 @ 06:28:16 UTC 51 */ 52 protected static final long msb0baseTime = 2085978496000L; 53 54 /** 55 * baseline NTP time if bit-0=1 -> 1-Jan-1900 @ 01:00:00 UTC 56 */ 57 protected static final long msb1baseTime = -2208988800000L; 58 59 /** 60 * Default NTP date string format. E.g. Fri, Sep 12 2003 21:06:23.860. 61 * See <code>java.text.SimpleDateFormat</code> for code descriptions. 62 */ 63 public final static String NTP_DATE_FORMAT = "EEE, MMM dd yyyy HH:mm:ss.SSS"; 64 65 /* 66 * Caches for the DateFormatters used by various toString methods. 67 */ 68 private static SoftReference<DateFormat> simpleFormatter = null; 69 private static SoftReference<DateFormat> utcFormatter = null; 70 71 /** 72 * NTP timestamp value: 64-bit unsigned fixed-point number as defined in RFC-1305 73 * with high-order 32 bits the seconds field and the low-order 32-bits the 74 * fractional field. 75 */ 76 private long ntpTime; 77 78 private static final long serialVersionUID = 8139806907588338737L; 79 80 // initialization of static time bases 81 /* 82 static { 83 TimeZone utcZone = TimeZone.getTimeZone("UTC"); 84 Calendar calendar = Calendar.getInstance(utcZone); 85 calendar.set(1900, Calendar.JANUARY, 1, 0, 0, 0); 86 calendar.set(Calendar.MILLISECOND, 0); 87 msb1baseTime = calendar.getTime().getTime(); 88 calendar.set(2036, Calendar.FEBRUARY, 7, 6, 28, 16); 89 calendar.set(Calendar.MILLISECOND, 0); 90 msb0baseTime = calendar.getTime().getTime(); 91 } 92 */ 93 94 /*** 95 * Constructs a newly allocated NTP timestamp object 96 * that represents the native 64-bit long argument. 97 */ 98 public TimeStamp(long ntpTime) 99 { 100 this.ntpTime = ntpTime; 101 } 102 103 /*** 104 * Constructs a newly allocated NTP timestamp object 105 * that represents the value represented by the string 106 * in hexdecimal form (e.g. "c1a089bd.fc904f6d"). 107 * 108 * @throws NumberFormatException - if the string does not contain a parsable timestamp. 109 */ 110 public TimeStamp(String s) throws NumberFormatException 111 { 112 ntpTime = decodeNtpHexString(s); 113 } 114 115 /*** 116 * Constructs a newly allocated NTP timestamp object 117 * that represents the Java Date argument. 118 * 119 * @param d - the Date to be represented by the Timestamp object. 120 */ 121 public TimeStamp(Date d) 122 { 123 ntpTime = (d == null) ? 0 : toNtpTime(d.getTime()); 124 } 125 126 /*** 127 * Returns the value of this Timestamp as a long value. 128 * 129 * @return the 64-bit long value represented by this object. 130 */ 131 public long ntpValue() 132 { 133 return ntpTime; 134 } 135 136 /*** 137 * Returns high-order 32-bits representing the seconds of this NTP timestamp. 138 * 139 * @return seconds represented by this NTP timestamp. 140 */ 141 public long getSeconds() 142 { 143 return (ntpTime >>> 32) & 0xffffffffL; 144 } 145 146 /*** 147 * Returns low-order 32-bits representing the fractional seconds. 148 * 149 * @return fractional seconds represented by this NTP timestamp. 150 */ 151 public long getFraction() 152 { 153 return ntpTime & 0xffffffffL; 154 } 155 156 /*** 157 * Convert NTP timestamp to Java standard time. 158 * 159 * @return NTP Timestamp in Java time 160 */ 161 public long getTime() 162 { 163 return getTime(ntpTime); 164 } 165 166 /*** 167 * Convert NTP timestamp to Java Date object. 168 * 169 * @return NTP Timestamp in Java Date 170 */ 171 public Date getDate() 172 { 173 long time = getTime(ntpTime); 174 return new Date(time); 175 } 176 177 /*** 178 * Convert 64-bit NTP timestamp to Java standard time. 179 * 180 * Note that java time (milliseconds) by definition has less precision 181 * then NTP time (picoseconds) so converting NTP timestamp to java time and back 182 * to NTP timestamp loses precision. For example, Tue, Dec 17 2002 09:07:24.810 EST 183 * is represented by a single Java-based time value of f22cd1fc8a, but its 184 * NTP equivalent are all values ranging from c1a9ae1c.cf5c28f5 to c1a9ae1c.cf9db22c. 185 * 186 * @param ntpTimeValue 187 * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT 188 * represented by this NTP timestamp value. 189 */ 190 public static long getTime(long ntpTimeValue) 191 { 192 long seconds = (ntpTimeValue >>> 32) & 0xffffffffL; // high-order 32-bits 193 long fraction = ntpTimeValue & 0xffffffffL; // low-order 32-bits 194 195 // Use round-off on fractional part to preserve going to lower precision 196 fraction = Math.round(1000D * fraction / 0x100000000L); 197 198 /* 199 * If the most significant bit (MSB) on the seconds field is set we use 200 * a different time base. The following text is a quote from RFC-2030 (SNTP v4): 201 * 202 * If bit 0 is set, the UTC time is in the range 1968-2036 and UTC time 203 * is reckoned from 0h 0m 0s UTC on 1 January 1900. If bit 0 is not set, 204 * the time is in the range 2036-2104 and UTC time is reckoned from 205 * 6h 28m 16s UTC on 7 February 2036. 206 */ 207 long msb = seconds & 0x80000000L; 208 if (msb == 0) { 209 // use base: 7-Feb-2036 @ 06:28:16 UTC 210 return msb0baseTime + (seconds * 1000) + fraction; 211 } else { 212 // use base: 1-Jan-1900 @ 01:00:00 UTC 213 return msb1baseTime + (seconds * 1000) + fraction; 214 } 215 } 216 217 /*** 218 * Helper method to convert Java time to NTP timestamp object. 219 * Note that Java time (milliseconds) by definition has less precision 220 * then NTP time (picoseconds) so converting Ntptime to Javatime and back 221 * to Ntptime loses precision. For example, Tue, Dec 17 2002 09:07:24.810 222 * is represented by a single Java-based time value of f22cd1fc8a, but its 223 * NTP equivalent are all values from c1a9ae1c.cf5c28f5 to c1a9ae1c.cf9db22c. 224 * @param date the milliseconds since January 1, 1970, 00:00:00 GMT. 225 * @return NTP timestamp object at the specified date. 226 */ 227 public static TimeStamp getNtpTime(long date) 228 { 229 return new TimeStamp(toNtpTime(date)); 230 } 231 232 /*** 233 * Constructs a NTP timestamp object and initializes it so that 234 * it represents the time at which it was allocated, measured to the 235 * nearest millisecond. 236 * @return NTP timestamp object set to the current time. 237 * @see java.lang.System#currentTimeMillis() 238 */ 239 public static TimeStamp getCurrentTime() 240 { 241 return getNtpTime(System.currentTimeMillis()); 242 } 243 244 /*** 245 * Convert NTP timestamp hexstring (e.g. "c1a089bd.fc904f6d") to the NTP 246 * 64-bit unsigned fixed-point number. 247 * 248 * @return NTP 64-bit timestamp value. 249 * @throws NumberFormatException - if the string does not contain a parsable timestamp. 250 */ 251 protected static long decodeNtpHexString(String s) 252 throws NumberFormatException 253 { 254 if (s == null) { 255 throw new NumberFormatException("null"); 256 } 257 int ind = s.indexOf('.'); 258 if (ind == -1) { 259 if (s.length() == 0) return 0; 260 return Long.parseLong(s, 16) << 32; // no decimal 261 } 262 263 return Long.parseLong(s.substring(0, ind), 16) << 32 | 264 Long.parseLong(s.substring(ind + 1), 16); 265 } 266 267 /*** 268 * Parses the string argument as a NTP hexidecimal timestamp representation string 269 * (e.g. "c1a089bd.fc904f6d"). 270 * 271 * @param s - hexstring. 272 * @return the Timestamp represented by the argument in hexidecimal. 273 * @throws NumberFormatException - if the string does not contain a parsable timestamp. 274 */ 275 public static TimeStamp parseNtpString(String s) 276 throws NumberFormatException 277 { 278 return new TimeStamp(decodeNtpHexString(s)); 279 } 280 281 /*** 282 * Converts Java time to 64-bit NTP time representation. 283 * 284 * @param t Java time 285 * @return NTP timestamp representation of Java time value. 286 */ 287 protected static long toNtpTime(long t) 288 { 289 boolean useBase1 = t < msb0baseTime; // time < Feb-2036 290 long baseTime; 291 if (useBase1) { 292 baseTime = t - msb1baseTime; // dates <= Feb-2036 293 } else { 294 // if base0 needed for dates >= Feb-2036 295 baseTime = t - msb0baseTime; 296 } 297 298 long seconds = baseTime / 1000; 299 long fraction = ((baseTime % 1000) * 0x100000000L) / 1000; 300 301 if (useBase1) { 302 seconds |= 0x80000000L; // set high-order bit if msb1baseTime 1900 used 303 } 304 305 long time = seconds << 32 | fraction; 306 return time; 307 } 308 309 /*** 310 * Computes a hashcode for this Timestamp. The result is the exclusive 311 * OR of the two halves of the primitive <code>long</code> value 312 * represented by this <code>TimeStamp</code> object. That is, the hashcode 313 * is the value of the expression: 314 * <blockquote><pre> 315 * (int)(this.ntpValue()^(this.ntpValue() >>> 32)) 316 * </pre></blockquote> 317 * 318 * @return a hash code value for this object. 319 */ 320 @Override 321 public int hashCode() 322 { 323 return (int) (ntpTime ^ (ntpTime >>> 32)); 324 } 325 326 /*** 327 * Compares this object against the specified object. 328 * The result is <code>true</code> if and only if the argument is 329 * not <code>null</code> and is a <code>Long</code> object that 330 * contains the same <code>long</code> value as this object. 331 * 332 * @param obj the object to compare with. 333 * @return <code>true</code> if the objects are the same; 334 * <code>false</code> otherwise. 335 */ 336 @Override 337 public boolean equals(Object obj) 338 { 339 if (obj instanceof TimeStamp) { 340 return ntpTime == ((TimeStamp) obj).ntpValue(); 341 } 342 return false; 343 } 344 345 /*** 346 * Converts this <code>TimeStamp</code> object to a <code>String</code>. 347 * The NTP timestamp 64-bit long value is represented as hex string with 348 * seconds separated by fractional seconds by a decimal point; 349 * e.g. c1a089bd.fc904f6d <=> Tue, Dec 10 2002 10:41:49.986 350 * 351 * @return NTP timestamp 64-bit long value as hex string with seconds 352 * separated by fractional seconds. 353 */ 354 @Override 355 public String toString() 356 { 357 return toString(ntpTime); 358 } 359 360 /*** 361 * Left-pad 8-character hex string with 0's 362 * 363 * @param buf - StringBuffer which is appended with leading 0's. 364 * @param l - a long. 365 */ 366 private static void appendHexString(StringBuffer buf, long l) 367 { 368 String s = Long.toHexString(l); 369 for (int i = s.length(); i < 8; i++) 370 buf.append('0'); 371 buf.append(s); 372 } 373 374 /*** 375 * Converts 64-bit NTP timestamp value to a <code>String</code>. 376 * The NTP timestamp value is represented as hex string with 377 * seconds separated by fractional seconds by a decimal point; 378 * e.g. c1a089bd.fc904f6d <=> Tue, Dec 10 2002 10:41:49.986 379 * 380 * @return NTP timestamp 64-bit long value as hex string with seconds 381 * separated by fractional seconds. 382 */ 383 public static String toString(long ntpTime) 384 { 385 StringBuffer buf = new StringBuffer(); 386 // high-order second bits (32..63) as hexstring 387 appendHexString(buf, (ntpTime >>> 32) & 0xffffffffL); 388 389 // low-order fractional seconds bits (0..31) as hexstring 390 buf.append('.'); 391 appendHexString(buf, ntpTime & 0xffffffffL); 392 393 return buf.toString(); 394 } 395 396 /*** 397 * Converts this <code>TimeStamp</code> object to a <code>String</code> 398 * of the form: 399 * <blockquote><pre> 400 * EEE, MMM dd yyyy HH:mm:ss.SSS</pre></blockquote> 401 * See java.text.SimpleDataFormat for code descriptions. 402 * 403 * @return a string representation of this date. 404 */ 405 public String toDateString() 406 { 407 DateFormat formatter = null; 408 if (simpleFormatter != null) { 409 formatter = simpleFormatter.get(); 410 } 411 if (formatter == null) { 412 // No cache yet, or cached formatter GC'd 413 formatter = new SimpleDateFormat(NTP_DATE_FORMAT, Locale.US); 414 formatter.setTimeZone(TimeZone.getDefault()); 415 simpleFormatter = new SoftReference<DateFormat>(formatter); 416 } 417 Date ntpDate = getDate(); 418 synchronized (formatter) { 419 return formatter.format(ntpDate); 420 } 421 } 422 423 /*** 424 * Converts this <code>TimeStamp</code> object to a <code>String</code> 425 * of the form: 426 * <blockquote><pre> 427 * EEE, MMM dd yyyy HH:mm:ss.SSS UTC</pre></blockquote> 428 * See java.text.SimpleDataFormat for code descriptions. 429 * 430 * @return a string representation of this date in UTC. 431 */ 432 public String toUTCString() 433 { 434 DateFormat formatter = null; 435 if (utcFormatter != null) 436 formatter = utcFormatter.get(); 437 if (formatter == null) { 438 // No cache yet, or cached formatter GC'd 439 formatter = new SimpleDateFormat(NTP_DATE_FORMAT + " 'UTC'", 440 Locale.US); 441 formatter.setTimeZone(TimeZone.getTimeZone("UTC")); 442 utcFormatter = new SoftReference<DateFormat>(formatter); 443 } 444 Date ntpDate = getDate(); 445 synchronized (formatter) { 446 return formatter.format(ntpDate); 447 } 448 } 449 450 /*** 451 * Compares two Timestamps numerically. 452 * 453 * @param anotherTimeStamp - the <code>TimeStamp</code> to be compared. 454 * @return the value <code>0</code> if the argument TimeStamp is equal to 455 * this TimeStamp; a value less than <code>0</code> if this TimeStamp 456 * is numerically less than the TimeStamp argument; and a 457 * value greater than <code>0</code> if this TimeStamp is 458 * numerically greater than the TimeStamp argument 459 * (signed comparison). 460 */ 461 public int compareTo(TimeStamp anotherTimeStamp) 462 { 463 long thisVal = this.ntpTime; 464 long anotherVal = anotherTimeStamp.ntpTime; 465 return (thisVal < anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1)); 466 } 467 468 /*** 469 * Compares this TimeStamp to another Object. If the Object is a TimeStamp, 470 * this function behaves like <code>compareTo(TimeStamp)</code>. Otherwise, 471 * it throws a <code>ClassCastException</code> (as TimeStamps are comparable 472 * only to other TimeStamps). 473 * 474 * @param o the <code>Object</code> to be compared. 475 * @return the value <code>0</code> if the argument is a TimeStamp 476 * numerically equal to this TimeStamp; a value less than 477 * <code>0</code> if the argument is a TimeStamp numerically 478 * greater than this TimeStamp; and a value greater than 479 * <code>0</code> if the argument is a TimeStamp numerically 480 * less than this TimeStamp. 481 * @exception ClassCastException if the argument is not a 482 * <code>TimeStamp</code>. 483 * @see java.lang.Comparable 484 */ 485 public int compareTo(Object o) 486 { 487 return compareTo((TimeStamp) o); 488 } 489 490 }