All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.nps.moves.disutil.DisTime Maven / Gradle / Ivy

package edu.nps.moves.disutil;

import java.util.*;

/**
 * DIS time units are a pain in the ass. DIS time units are arbitrary, and set
 * equal to 2^31 - 1 time units per hour. The DIS time is set to the number of
 * time units since the start of the hour. The timestamp field in the PDU header
 * is four bytes long and is specified to be an unsigned integer value.
 *
 * There are two types of official timestamps in the PDU header: absolute time
 * and relative time. Absolute time is used when the host is sync'd to UTC, ie
 * the host has access to UTC via Network Time Protocol (NTP). This time can be
 * legitimately compared to the timestamp of packets received from other hosts,
 * since they all refer to the same universal time.
 *
 * Relative timestamps are used when the host does NOT have access to NTP, and
 * hence the system time might not be coordinated with that of other hosts. This
 * means that a host receiving DIS packets from several hosts might have to set
 * up a per-host table to order packets, and that the PDU timestamp fields from
 * one host is not directly comparable to the PDU timestamp field from another
 * host.
 *
 * Absolute timestamps have their LSB set to 1, and relative timestamps have
 * their LSB set to 0. The idea is to get the current time since the top of the
 * hour, divide by 2^31-1, shift left one bit, then set the LSB to either 0 for
 * relative timestamps or 1 for absolute timestamps.
 *
 * The nature of the data is such that the timestamp fields will roll over once
 * an hour, and simulations must be prepared for that. Ie, at the top of the
 * hour outgoing PDUs will have a timestamp of 1, just before the end of the
 * hour the PDUs will have a timestamp of 2^31 - 1, and then they will roll back
 * over to 1. Receiving applications should expect this behavior, and not simply
 * expect a monotonically increasing timestamp field.
 *
 * The official DIS timestamps don't work all that well in our (NPS's)
 * applications, which often expect a monotonically increasing timestamp field.
 * To get around this, we use hundreds of a second since the start of the year.
 * The maximum value for this field is 3,153,600,000, which can fit into an
 * unsigned int. The resolution is good enough for most applications, and you
 * typically don't have to worry about rollover, instead getting only a
 * monotonically increasing timestamp value.
 *
 * Note that many applications in the wild have been known to completely ignore
 * the standard and to simply put the Unix time (seconds since 1970) into the
 * field.
 * 
 *
 * You need to be careful with the shared instance of this class--I'm not at all
 * convinced it is thread safe. If you are using multiple threads, I suggest you
 * create a new instance of the class for each thread to prevent the values from
 * getting stomped on.
 *
 * @author DMcG
 */
public class DisTime {

    public static final int ABSOLUTE_TIMESTAMP_MASK = 0x00000001;
    public static final int RELATIVE_TIMESTAMP_MASK = 0xfffffffe;
    protected GregorianCalendar cal;
    public static DisTime disTime = null;

    /**
     * Shared instance. This is not thread-safe. If you are working in multiple
     * threads, create a new instance for each thread.
     *
     * @return singleton instance of DisTime
     */
    public static DisTime getInstance() {
        if (disTime == null) {
            disTime = new DisTime();
        }

        return disTime;
    }

    public DisTime() {
        cal = new GregorianCalendar();
    }

    /**
     * Returns the number of DIS time units since the top of the hour. there are
     * 2^31-1 DIS time units per hour.
     *
     * @return integer DIS time units since the start of the hour.
     */
    private int getDisTimeUnitsSinceTopOfHour() {
        // set cal object to current time
        long currentTime = System.currentTimeMillis(); // UTC milliseconds since 1970
        cal.setTimeInMillis(currentTime);

        // Set cal to top of the hour, then compute what the cal object says was milliseconds since 1970
        // at the top of the hour
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        long topOfHour = cal.getTimeInMillis();

        // Milliseconds since the top of the hour
        long diff = currentTime - topOfHour;

        // It turns out that Integer.MAX_VALUE is 2^31-1, which is the time unit value, ie there are
        // 2^31-1 DIS time units in an hour. 3600 sec/hr X 1000 msec/sec divided into the number of
        // msec since the start of the hour gives the percentage of DIS time units in the hour, times
        // the number of DIS time units per hour, equals the time value
        double val = (((double) diff) / (3600.0 * 1000.0)) * Integer.MAX_VALUE;
        int ts = (int) val;

        return ts;
    }

    /**
     * Returns the absolute timestamp, assuminng that this host is sync'd to
     * NTP. Fix to bitshift by mvormelch.
     *
     * @return DIS time units, get absolute timestamp
     */
    public int getDisAbsoluteTimestamp() {
        int val = this.getDisTimeUnitsSinceTopOfHour();
        val = (val << 1) | ABSOLUTE_TIMESTAMP_MASK; // always flip the lsb to 1
        return val;
    }

    /**
     * Returns the DIS standard relative timestamp, which should be used if this
     * host is not slaved to NTP. Fix to bitshift by mvormelch
     *
     * @return DIS time units, relative
     */
    public int getDisRelativeTimestamp() {
        int val = this.getDisTimeUnitsSinceTopOfHour();
        val = (val << 1) & RELATIVE_TIMESTAMP_MASK; // always flip the lsb to 0
        return val;
    }

    /**
     * Returns a useful timestamp, hundredths of a second since the start of the
     * year. This effectively eliminates the need for receivers to handle
     * timestamp rollover, as long as you're not working on New Year's Eve.
     *
     * @return a timestamp in hundredths of a second since the start of the year
     */
    public long getNpsTimestamp() {
        // set cal object to current time
        long currentTime = System.currentTimeMillis(); // UTC milliseconds since 1970
        cal.setTimeInMillis(currentTime);

        // Set cal to the start of the year
        cal.set(Calendar.MONTH, 0);
        cal.set(Calendar.DAY_OF_MONTH, 1);
        cal.set(Calendar.HOUR, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        long startOfYear = cal.getTimeInMillis();

        // Milliseconds since the top of the hour
        long diff = currentTime - startOfYear;
        diff /= 10; // milliseconds to hundredths of a second

        return diff;
    }

    /**
     * Another option for marshalling with the timestamp field set
     * automatically. The UNIX time is conventionally seconds since January 1,
     * 1970. UTC time is used, and leap seconds are excluded. This approach is
     * popular in the wild, but the time resolution is not very good for high
     * frequency updates, such as aircraft. An entity updating at 30 PDUs/second
     * would see 30 PDUs sent out with the same timestamp, and have 29 of them
     * discarded as duplicate packets.
     *
     * Note that there are other "Unix times", such milliseconds since 1/1/1970,
     * saved in a long. This cannot be used, since the value is saved in a long.
     * Java's System.getCurrentTimeMillis() uses this value.
     *
     * Unix time (in seconds) rolls over in 2038.
     *
     * See the wikipedia page on Unix time for gory details.
     *
     * @return seconds since 1970
     */
    public long getUnixTimestamp() {
        long t = System.currentTimeMillis();
        t = t / 1000l;   // NB: integer division, convert milliseconds to seconds
        return t;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy