View Javadoc
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    *      https://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.ObjectInputStream;
21  import java.io.ObjectOutputStream;
22  import java.io.Serializable;
23  import java.text.DateFormat;
24  import java.text.SimpleDateFormat;
25  import java.util.Date;
26  import java.util.Locale;
27  import java.util.TimeZone;
28  
29  /**
30   * 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
31   * 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
32   * 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
33   * basis is used otherwise 1900 if MSB=1.
34   * <p>
35   * 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
36   * known as "the epoch", namely January 1, 1970, 00:00:00 GMT.
37   * </p>
38   *
39   * @see java.util.Date
40   */
41  public class TimeStamp implements Serializable, Comparable<TimeStamp> {
42      private static final long serialVersionUID = 8139806907588338737L;
43  
44      /**
45       * Baseline NTP time if bit-0=0 is 7-Feb-2036 @ 06:28:16 UTC
46       */
47      protected static final long msb0baseTime = 2085978496000L;
48  
49      /**
50       * Baseline NTP time if bit-0=1 is 1-Jan-1900 @ 01:00:00 UTC
51       */
52      protected static final long msb1baseTime = -2208988800000L;
53  
54      /**
55       * Default NTP date string format. E.g. Fri, Sep 12 2003 21:06:23.860. See {@code java.text.SimpleDateFormat} for code descriptions.
56       */
57      public static final String NTP_DATE_FORMAT = "EEE, MMM dd yyyy HH:mm:ss.SSS";
58  
59      /**
60       * Left-pad 8-character hexadecimal string with 0's
61       *
62       * @param buf   StringBuilder which is appended with leading 0's.
63       * @param l     a long.
64       */
65      private static void appendHexString(final StringBuilder buf, final long l) {
66          final String s = Long.toHexString(l);
67          for (int i = s.length(); i < 8; i++) {
68              buf.append('0');
69          }
70          buf.append(s);
71      }
72  
73      /**
74       * Convert NTP timestamp hexstring (e.g. "c1a089bd.fc904f6d") to the NTP 64-bit unsigned fixed-point number.
75       *
76       * @param hexString the string to convert
77       * @return NTP 64-bit timestamp value.
78       * @throws NumberFormatException - if the string does not contain a parsable timestamp.
79       */
80      protected static long decodeNtpHexString(final String hexString) throws NumberFormatException {
81          if (hexString == null) {
82              throw new NumberFormatException("null");
83          }
84          final int ind = hexString.indexOf('.');
85          if (ind == -1) {
86              if (hexString.isEmpty()) {
87                  return 0;
88              }
89              return Long.parseLong(hexString, 16) << 32; // no decimal
90          }
91  
92          return Long.parseLong(hexString.substring(0, ind), 16) << 32 | Long.parseLong(hexString.substring(ind + 1), 16);
93      }
94  
95      /**
96       * Gets an NTP timestamp object and initializes it so that it represents the time at which it was allocated, measured to the nearest millisecond.
97       *
98       * @return NTP timestamp object set to the current time.
99       * @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 }