    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 *      http://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.Serializable;
    021import java.text.DateFormat;
    022import java.text.SimpleDateFormat;
    023import java.util.Date;
    024import java.util.Locale;
    025import java.util.TimeZone;
    026
    027/**
    028 * 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
    029 * 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
    030 * 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
    031 * basis is used otherwise 1900 if MSB=1.
    032 * <p>
    033 * 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
    034 * known as "the epoch", namely January 1, 1970, 00:00:00 GMT.
    035 * </p>
    036 *
    037 * @see java.util.Date
    038 */
    039public class TimeStamp implements Serializable, Comparable<TimeStamp> {
    040    private static final long serialVersionUID = 8139806907588338737L;
    041
    042    /**
    043     * Baseline NTP time if bit-0=0 is 7-Feb-2036 @ 06:28:16 UTC
    044     */
    045    protected static final long msb0baseTime = 2085978496000L;
    046
    047    /**
    048     * Baseline NTP time if bit-0=1 is 1-Jan-1900 @ 01:00:00 UTC
    049     */
    050    protected static final long msb1baseTime = -2208988800000L;
    051
    052    /**
    053     * Default NTP date string format. E.g. Fri, Sep 12 2003 21:06:23.860. See <code>java.text.SimpleDateFormat</code> for code descriptions.
    054     */
    055    public static final String NTP_DATE_FORMAT = "EEE, MMM dd yyyy HH:mm:ss.SSS";
    056
    057    /**
    058     * Left-pad 8-character hex string with 0's
    059     *
    060     * @param buf - StringBuilder which is appended with leading 0's.
    061     * @param l   - a long.
    062     */
    063    private static void appendHexString(final StringBuilder buf, final long l) {
    064        final String s = Long.toHexString(l);
    065        for (int i = s.length(); i < 8; i++) {
    066            buf.append('0');
    067        }
    068        buf.append(s);
    069    }
    070
    071    /**
    072     * Convert NTP timestamp hexstring (e.g. "c1a089bd.fc904f6d") to the NTP 64-bit unsigned fixed-point number.
    073     *
    074     * @param hexString the string to convert
    075     *
    076     * @return NTP 64-bit timestamp value.
    077     * @throws NumberFormatException - if the string does not contain a parsable timestamp.
    078     */
    079    protected static long decodeNtpHexString(final String hexString) throws NumberFormatException {
    080        if (hexString == null) {
    081            throw new NumberFormatException("null");
    082        }
    083        final int ind = hexString.indexOf('.');
    084        if (ind == -1) {
    085            if (hexString.isEmpty()) {
    086                return 0;
    087            }
    088            return Long.parseLong(hexString, 16) << 32; // no decimal
    089        }
    090
    091        return Long.parseLong(hexString.substring(0, ind), 16) << 32 | Long.parseLong(hexString.substring(ind + 1), 16);
    092    }
    093
    094    /**
    095     * Constructs a NTP timestamp object and initializes it so that it represents the time at which it was allocated, measured to the nearest millisecond.
    096     *
    097     * @return NTP timestamp object set to the current time.
    098     * @see System#currentTimeMillis()
    099     */
    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 then 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 then 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 hex string with seconds separated by fractional
    194     * 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 hex 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 hex 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</code> if and only if the argument is not <code>null</code> 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</code> if the objects are the same; <code>false</code> 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 hashcode 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 hashcode 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 hex string with seconds
    371     * 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 hex 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}