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