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 }