001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.net.ntp; 019 020import java.io.ObjectInputStream; 021import java.io.ObjectOutputStream; 022import java.io.Serializable; 023import java.text.DateFormat; 024import java.text.SimpleDateFormat; 025import java.util.Date; 026import java.util.Locale; 027import java.util.TimeZone; 028 029/** 030 * 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 031 * 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 032 * 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 033 * basis is used otherwise 1900 if MSB=1. 034 * <p> 035 * 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 036 * known as "the epoch", namely January 1, 1970, 00:00:00 GMT. 037 * </p> 038 * 039 * @see java.util.Date 040 */ 041public class TimeStamp implements Serializable, Comparable<TimeStamp> { 042 private static final long serialVersionUID = 8139806907588338737L; 043 044 /** 045 * Baseline NTP time if bit-0=0 is 7-Feb-2036 @ 06:28:16 UTC 046 */ 047 protected static final long msb0baseTime = 2085978496000L; 048 049 /** 050 * Baseline NTP time if bit-0=1 is 1-Jan-1900 @ 01:00:00 UTC 051 */ 052 protected static final long msb1baseTime = -2208988800000L; 053 054 /** 055 * Default NTP date string format. E.g. Fri, Sep 12 2003 21:06:23.860. See {@code java.text.SimpleDateFormat} for code descriptions. 056 */ 057 public static final String NTP_DATE_FORMAT = "EEE, MMM dd yyyy HH:mm:ss.SSS"; 058 059 /** 060 * Left-pad 8-character hexadecimal string with 0's 061 * 062 * @param buf StringBuilder which is appended with leading 0's. 063 * @param l a long. 064 */ 065 private static void appendHexString(final StringBuilder buf, final long l) { 066 final String s = Long.toHexString(l); 067 for (int i = s.length(); i < 8; i++) { 068 buf.append('0'); 069 } 070 buf.append(s); 071 } 072 073 /** 074 * Convert NTP timestamp hexstring (e.g. "c1a089bd.fc904f6d") to the NTP 64-bit unsigned fixed-point number. 075 * 076 * @param hexString the string to convert 077 * @return NTP 64-bit timestamp value. 078 * @throws NumberFormatException - if the string does not contain a parsable timestamp. 079 */ 080 protected static long decodeNtpHexString(final String hexString) throws NumberFormatException { 081 if (hexString == null) { 082 throw new NumberFormatException("null"); 083 } 084 final int ind = hexString.indexOf('.'); 085 if (ind == -1) { 086 if (hexString.isEmpty()) { 087 return 0; 088 } 089 return Long.parseLong(hexString, 16) << 32; // no decimal 090 } 091 092 return Long.parseLong(hexString.substring(0, ind), 16) << 32 | Long.parseLong(hexString.substring(ind + 1), 16); 093 } 094 095 /** 096 * Gets an NTP timestamp object and initializes it so that it represents the time at which it was allocated, measured to the nearest millisecond. 097 * 098 * @return NTP timestamp object set to the current time. 099 * @see System#currentTimeMillis() 100 */ 101 public static TimeStamp getCurrentTime() { 102 return getNtpTime(System.currentTimeMillis()); 103 } 104 105 // initialization of static time bases 106 /* 107 * static { TimeZone utcZone = TimeZone.getTimeZone("UTC"); Calendar calendar = Calendar.getInstance(utcZone); calendar.set(1900, Calendar.JANUARY, 1, 0, 0, 108 * 0); calendar.set(Calendar.MILLISECOND, 0); msb1baseTime = calendar.getTime().getTime(); calendar.set(2036, Calendar.FEBRUARY, 7, 6, 28, 16); 109 * calendar.set(Calendar.MILLISECOND, 0); msb0baseTime = calendar.getTime().getTime(); } 110 */ 111 112 /** 113 * Gets an NTP timestamp object from a Java time. Note that Java time (milliseconds) by definition has less precision than NTP time 114 * (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 115 * single Java-based time value of f22cd1fc8a, but its NTP equivalent are all values from c1a9ae1c.cf5c28f5 to c1a9ae1c.cf9db22c. 116 * 117 * @param dateMillis the milliseconds since January 1, 1970, 00:00:00 GMT. 118 * @return NTP timestamp object at the specified date. 119 */ 120 public static TimeStamp getNtpTime(final long dateMillis) { 121 return new TimeStamp(toNtpTime(dateMillis)); 122 } 123 124 /** 125 * Gets a Java standard time from a 64-bit NTP timestamp. 126 * 127 * 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 128 * 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 129 * equivalent are all values ranging from c1a9ae1c.cf5c28f5 to c1a9ae1c.cf9db22c. 130 * 131 * @param ntpTimeValue the input time 132 * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this NTP timestamp value. 133 */ 134 public static long getTime(final long ntpTimeValue) { 135 final long seconds = ntpTimeValue >>> 32 & 0xffffffffL; // high-order 32-bits 136 long fraction = ntpTimeValue & 0xffffffffL; // low-order 32-bits 137 138 // Use round-off on fractional part to preserve going to lower precision 139 fraction = Math.round(1000D * fraction / 0x100000000L); 140 141 /* 142 * 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): 143 * 144 * 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 145 * is in the range 2036-2104 and UTC time is reckoned from 6h 28m 16s UTC on 7 February 2036. 146 */ 147 final long msb = seconds & 0x80000000L; 148 if (msb == 0) { 149 // use base: 7-Feb-2036 @ 06:28:16 UTC 150 return msb0baseTime + seconds * 1000 + fraction; 151 } 152 // use base: 1-Jan-1900 @ 01:00:00 UTC 153 return msb1baseTime + seconds * 1000 + fraction; 154 } 155 156 /** 157 * Parses the string argument as a NTP hexidecimal timestamp representation string (e.g. "c1a089bd.fc904f6d"). 158 * 159 * @param s hexstring. 160 * @return the Timestamp represented by the argument in hexidecimal. 161 * @throws NumberFormatException - if the string does not contain a parsable timestamp. 162 */ 163 public static TimeStamp parseNtpString(final String s) throws NumberFormatException { 164 return new TimeStamp(decodeNtpHexString(s)); 165 } 166 167 /** 168 * Converts Java time to 64-bit NTP time representation. 169 * 170 * @param millis Java time 171 * @return NTP timestamp representation of Java time value. 172 */ 173 protected static long toNtpTime(final long millis) { 174 final boolean useBase1 = millis < msb0baseTime; // time < Feb-2036 175 final long baseTimeMillis; 176 if (useBase1) { 177 baseTimeMillis = millis - msb1baseTime; // dates <= Feb-2036 178 } else { 179 // if base0 needed for dates >= Feb-2036 180 baseTimeMillis = millis - msb0baseTime; 181 } 182 183 long seconds = baseTimeMillis / 1000; 184 final long fraction = baseTimeMillis % 1000 * 0x100000000L / 1000; 185 186 if (useBase1) { 187 seconds |= 0x80000000L; // set high-order bit if msb1baseTime 1900 used 188 } 189 190 return seconds << 32 | fraction; 191 } 192 193 /** 194 * Converts 64-bit NTP timestamp value to a {@code String}. The NTP timestamp value is represented as hexadecimal string with seconds separated by 195 * fractional seconds by a decimal point; e.g. c1a089bd.fc904f6d == Tue, Dec 10 2002 10:41:49.986 196 * 197 * @param ntpTime the 64 bit timestamp 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 /** 219 * Formats dates. 220 */ 221 private DateFormat simpleFormatter; 222 223 /** 224 * Formats UTC strings. 225 */ 226 private DateFormat utcFormatter; 227 228 /** 229 * Constructs a newly allocated NTP timestamp object that represents the Java Date argument. 230 * 231 * @param d the Date to be represented by the Timestamp object. 232 */ 233 public TimeStamp(final Date d) { 234 ntpTime = d == null ? 0 : toNtpTime(d.getTime()); 235 } 236 237 /** 238 * Constructs a newly allocated NTP timestamp object that represents the native 64-bit long argument. 239 * 240 * @param ntpTime the timestamp 241 */ 242 public TimeStamp(final long ntpTime) { 243 this.ntpTime = ntpTime; 244 } 245 246 /** 247 * Constructs a newly allocated NTP timestamp object that represents the value represented by the string in hexdecimal form (e.g. "c1a089bd.fc904f6d"). 248 * 249 * @param hexStamp the hexadecimal timestamp 250 * @throws NumberFormatException - if the string does not contain a parsable timestamp. 251 */ 252 public TimeStamp(final String hexStamp) throws NumberFormatException { 253 ntpTime = decodeNtpHexString(hexStamp); 254 } 255 256 /** 257 * Compares two Timestamps numerically. 258 * 259 * @param anotherTimeStamp the {@code TimeStamp} to be compared. 260 * @return the value {@code 0} if the argument TimeStamp is equal to this TimeStamp; a value less than {@code 0} if this TimeStamp is numerically 261 * less than the TimeStamp argument; and a value greater than {@code 0} if this TimeStamp is numerically greater than the TimeStamp argument 262 * (signed comparison). 263 */ 264 @Override 265 public int compareTo(final TimeStamp anotherTimeStamp) { 266 final long thisVal = ntpTime; 267 final long anotherVal = anotherTimeStamp.ntpTime; 268 return Long.compare(thisVal, anotherVal); 269 } 270 271 /** 272 * 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 273 * {@code Long} object that contains the same {@code long} value as this object. 274 * 275 * @param obj the object to compare with. 276 * @return {@code true} if the objects are the same; {@code false} otherwise. 277 */ 278 @Override 279 public boolean equals(final Object obj) { 280 if (obj instanceof TimeStamp) { 281 return ntpTime == ((TimeStamp) obj).ntpValue(); 282 } 283 return false; 284 } 285 286 /** 287 * Gets a Date for an NTP timestamp. 288 * 289 * @return NTP Timestamp in Java Date 290 */ 291 public Date getDate() { 292 return new Date(getTime(ntpTime)); 293 } 294 295 /** 296 * Gets the low-order 32-bits representing the fractional seconds. 297 * 298 * @return fractional seconds represented by this NTP timestamp. 299 */ 300 public long getFraction() { 301 return ntpTime & 0xffffffffL; 302 } 303 304 /** 305 * Gets the high-order 32-bits representing the seconds of this NTP timestamp. 306 * 307 * @return seconds represented by this NTP timestamp. 308 */ 309 public long getSeconds() { 310 return ntpTime >>> 32 & 0xffffffffL; 311 } 312 313 /** 314 * Gets a Java standard time in milliseconds from the NTP timestamp. 315 * 316 * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this NTP timestamp value. 317 */ 318 public long getTime() { 319 return getTime(ntpTime); 320 } 321 322 /** 323 * Computes a hash code for this Timestamp. The result is the exclusive OR of the two halves of the primitive {@code long} value represented by this 324 * {@code TimeStamp} object. That is, the hash code is the value of the expression: <blockquote> 325 * 326 * <pre> 327 * {@code 328 * (int) (this.ntpValue() ^ (this.ntpValue() >>> 32)) 329 * } 330 * </pre> 331 * 332 * </blockquote> 333 * 334 * @return a hash code value for this object. 335 */ 336 @Override 337 public int hashCode() { 338 return (int) (ntpTime ^ ntpTime >>> 32); 339 } 340 341 /** 342 * Returns the value of this Timestamp as a long value. 343 * 344 * @return the 64-bit long value represented by this object. 345 */ 346 public long ntpValue() { 347 return ntpTime; 348 } 349 350 /** 351 * Throws UnsupportedOperationException. 352 * 353 * @param ignored Ignored. 354 */ 355 private void readObject(final ObjectInputStream ignored) { 356 throw new UnsupportedOperationException("Serialization is not supported"); 357 } 358 359 /** 360 * Converts this {@code TimeStamp} object to a {@code String} of the form: <blockquote> 361 * 362 * <pre> 363 * EEE, MMM dd yyyy HH:mm:ss.SSS 364 * </pre> 365 * 366 * </blockquote> See java.text.SimpleDataFormat for code descriptions. 367 * 368 * @return a string representation of this date. 369 */ 370 public String toDateString() { 371 if (simpleFormatter == null) { 372 simpleFormatter = new SimpleDateFormat(NTP_DATE_FORMAT, Locale.US); 373 simpleFormatter.setTimeZone(TimeZone.getDefault()); 374 } 375 final Date ntpDate = getDate(); 376 return simpleFormatter.format(ntpDate); 377 } 378 379 /** 380 * Converts this {@code TimeStamp} object to a {@code String}. The NTP timestamp 64-bit long value is represented as hexadecimal string with 381 * seconds separated by fractional seconds by a decimal point; e.g. c1a089bd.fc904f6d == Tue, Dec 10 2002 10:41:49.986 382 * 383 * @return NTP timestamp 64-bit long value as hexadecimal string with seconds separated by fractional seconds. 384 */ 385 @Override 386 public String toString() { 387 return toString(ntpTime); 388 } 389 390 /* 391 * Serialization is unnecessary for this class. Reject attempts to do so until such time as the Serializable attribute can be dropped. 392 */ 393 394 /** 395 * Converts this {@code TimeStamp} object to a {@code String} of the form: <blockquote> 396 * 397 * <pre> 398 * EEE, MMM dd yyyy HH:mm:ss.SSS UTC 399 * </pre> 400 * 401 * </blockquote> See java.text.SimpleDataFormat for code descriptions. 402 * 403 * @return a string representation of this date in UTC. 404 */ 405 public String toUTCString() { 406 if (utcFormatter == null) { 407 utcFormatter = new SimpleDateFormat(NTP_DATE_FORMAT + " 'UTC'", Locale.US); 408 utcFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); 409 } 410 final Date ntpDate = getDate(); 411 return utcFormatter.format(ntpDate); 412 } 413 414 /** 415 * Always throws {@link UnsupportedOperationException}. 416 * 417 * @param ignored ignored. 418 * @throws UnsupportedOperationException Always thrown. 419 */ 420 private void writeObject(final ObjectOutputStream ignored) { 421 throw new UnsupportedOperationException("Serialization is not supported"); 422 } 423 424}