TimeUnit.java

package org.djunits.unit;

import java.util.GregorianCalendar;

import org.djunits.quantity.Quantity;
import org.djunits.unit.scale.OffsetLinearScale;
import org.djunits.unit.si.SIPrefixes;
import org.djunits.unit.unitsystem.UnitSystem;

/**
 * Standard absolute time units. Note that when the offset of a stored absolute Time becomes large, precision of a float or
 * double might not be enough for the required resolution of a Time. A float has around 7 significant digits (23 bit mantissa),
 * whereas a double has around 16 significant digits (52 bit mantissa). This means that when we need to have a float time that
 * is precise to microseconds, the Time value should not go above 2^22 = 4.0E6. This is <b>not</b> enough to store Epoch values!
 * So feeding System.TimeInMillis() to a FloatTime with TimeUnit.BASE as its unit is not having the required precision. For a
 * (double) Time with TimeUnit.BASE as its unit, the largest value where the ms precision is reached is 2^51 = 2.3E15, which is
 * around 71000 years. This is sufficient to store a date on an Epoch level precise to a ms.
 * <p>
 * Copyright (c) 2015-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
 * BSD-style license. See <a href="https://djunits.org/docs/license.html">DJUNITS License</a>.
 * </p>
 * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
 */
public class TimeUnit extends AbsoluteLinearUnit<TimeUnit, DurationUnit>
{
    /** */
    private static final long serialVersionUID = 20140607L;

    /** The base, with "m2" as the SI signature. */
    public static final Quantity<TimeUnit> BASE = new Quantity<>("Time", "s");

    /**
     * The base unit for time with an artifical "zero" point with a calculation in seconds. Note that when the offset becomes
     * large, precision of a float or double might not be enough for the required resolution of a Time. A float has around 7
     * significant digits (23 bit mantissa), whereas a double has around 16 significant digits (52 bit mantissa). This means
     * that when we need to have a float time that is precise to microseconds, the Time value should not go above 2^22 = 4.0E6.
     * This is <b>not</b> enough to store Epoch values! So feeding System.TimeInMillis() to a FloatTime with TimeUnit.BASE as
     * its unit is not having the required precision. For a (double) Time with TimeUnit.BASE as its unit, the largest value
     * where the ms precision is reached is 2^51 = 2.3E15, which is around 71000 years. This is sufficient to store a date on an
     * Epoch level precise to a ms.
     */
    public static final TimeUnit BASE_SECOND = new TimeUnit().build(new AbsoluteLinearUnit.Builder<TimeUnit, DurationUnit>()
            .setQuantity(BASE).setId("s").setName("second").setUnitSystem(UnitSystem.SI_DERIVED)
            .setSiPrefixes(SIPrefixes.UNIT, 1.0).setRelativeUnit(DurationUnit.SECOND).setScale(new OffsetLinearScale(1.0, 0.0))
            .setDefaultDisplayAbbreviation("s").setDefaultTextualAbbreviation("s").setAdditionalAbbreviations("sec"));

    /** The default unit for time is BASE_SECOND. */
    public static final TimeUnit DEFAULT = BASE_SECOND;

    /** The base unit for time with an artificial "zero" point with a calculation in microseconds. */
    public static final TimeUnit BASE_MICROSECOND = BASE_SECOND.deriveLinearOffset(1.0E-6, 0.0, DurationUnit.MICROSECOND, "mus",
            "microsecond", UnitSystem.SI_DERIVED, "\u03BCs", "mus", "\u03BCsec", "musec");

    /** The base unit for time with an artificial "zero" point with a calculation in milliseconds. */
    public static final TimeUnit BASE_MILLISECOND = BASE_SECOND.deriveLinearOffset(1.0E-3, 0.0, DurationUnit.MILLISECOND, "ms",
            "millisecond", UnitSystem.SI_DERIVED, "ms", "ms", "msec");

    /** The base unit for time with an artificial "zero" point with a calculation in minutes. */
    public static final TimeUnit BASE_MINUTE = BASE_SECOND.deriveLinearOffset(60.0, 0.0, DurationUnit.MINUTE, "min", "minute",
            UnitSystem.SI_DERIVED, "min", "min");

    /** The base unit for time with an artificial "zero" point with a calculation in hours. */
    public static final TimeUnit BASE_HOUR = BASE_SECOND.deriveLinearOffset(3600.0, 0.0, DurationUnit.HOUR, "h", "hour",
            UnitSystem.SI_DERIVED, "h", "h", "hr", "hour");

    /** The base unit for time with an artificial "zero" point with a calculation in days. */
    public static final TimeUnit BASE_DAY = BASE_SECOND.deriveLinearOffset(24.0 * 3600.0, 0.0, DurationUnit.DAY, "day", "day",
            UnitSystem.SI_DERIVED, "day", "day");

    /** The base unit for time with an artificial "zero" point with a calculation in weeks. */
    public static final TimeUnit BASE_WEEK = BASE_SECOND.deriveLinearOffset(7.0 * 24.0 * 3600.0, 0.0, DurationUnit.WEEK, "wk",
            "week", UnitSystem.SI_DERIVED, "wk", "wk", "week");

    /**
     * The POSIX and Gregorian Epoch: January 1, 1970 at 00:00 UTC with a calculation in seconds. The base should be taken in
     * such a way that a resolution of a millisecond is still 'visible' on a date in, say, 2020. When 1-1-1970 is used as the
     * origin, 1-1-2021 has a value of 1,577,836,800,000 milliseconds = 1.6E12 ms. If we want to be precise on the ms level, we
     * need 12 significant digits. A float has around 7 significant digits (23 bit mantissa), whereas a double has around 16
     * significant digits (52 bit mantissa). This means that a float time with an offset of 1-1-1970 is at best precise to a
     * minute level. A double time is precise to microseconds. Therefore, avoid using float times that use the EPOCH.
     */
    public static final TimeUnit EPOCH_SECOND = BASE_SECOND.deriveLinearOffset(1.0, 0.0, DurationUnit.SECOND, "s(Y1970)",
            "seconds since 1/1/70", UnitSystem.OTHER, "s(Y1970)", "s(Y1970)", "sec(Y1970)");

    /** The POSIX and Gregorian Epoch: January 1, 1970 at 00:00 UTC with a calculation in microseconds. */
    public static final TimeUnit EPOCH_MICROSECOND =
            BASE_SECOND.deriveLinearOffset(1.0E-6, 0.0, DurationUnit.MICROSECOND, "mus(Y1970)", "microseconds since 1/1/70",
                    UnitSystem.OTHER, "\u03BCs(Y1970)", "mus(Y1970)", "\u03BCsec(Y1970)", "musec(Y1970)");

    /** The POSIX and Gregorian Epoch: January 1, 1970 at 00:00 UTC with a calculation in milliseconds. */
    public static final TimeUnit EPOCH_MILLISECOND = BASE_SECOND.deriveLinearOffset(1.0E-3, 0.0, DurationUnit.MILLISECOND,
            "ms(Y1970)", "milliseconds since 1/1/70", UnitSystem.OTHER, "ms(Y1970)", "ms(Y1970)", "msec(Y1970)");

    /** The POSIX and Gregorian Epoch: January 1, 1970 at 00:00 UTC with a calculation in minutes. */
    public static final TimeUnit EPOCH_MINUTE = BASE_SECOND.deriveLinearOffset(60.0, 0.0, DurationUnit.MINUTE, "min(Y1970)",
            "minutes since 1/1/70", UnitSystem.OTHER, "min(Y1970)", "min(Y1970)");

    /** The POSIX and Gregorian Epoch: January 1, 1970 at 00:00 UTC with a calculation in hours. */
    public static final TimeUnit EPOCH_HOUR = BASE_SECOND.deriveLinearOffset(3600.0, 0.0, DurationUnit.HOUR, "h(Y1970)",
            "hours since 1/1/70", UnitSystem.OTHER, "h(Y1970)", "h(Y1970)", "hour(Y1970)", "hr(Y1970)");

    /** The POSIX and Gregorian Epoch: January 1, 1970 at 00:00 UTC with a calculation in days. */
    public static final TimeUnit EPOCH_DAY = BASE_SECOND.deriveLinearOffset(24.0 * 3600.0, 0.0, DurationUnit.DAY, "day(Y1970)",
            "days since 1/1/70", UnitSystem.OTHER, "day(Y1970)", "day(Y1970)");

    /** The POSIX and Gregorian Epoch: January 1, 1970 at 00:00 UTC with a calculation in weeks. */
    public static final TimeUnit EPOCH_WEEK = BASE_SECOND.deriveLinearOffset(7.0 * 24.0 * 3600.0, 0.0, DurationUnit.WEEK,
            "wk(Y1970)", "weeks since 1/1/70", UnitSystem.OTHER, "wk(Y1970)", "wk(Y1970)", "week(Y1970)");

    /**
     * The Epoch with 0001-01-01 AD at 00:00 as the origin with a calculation in seconds. When 1-1-0001 is used as the origin,
     * 1-1-2021 has a value of around 6.4E13 ms. If we want to be precise on the ms level, we need 13 significant digits. A
     * float has around 7 significant digits (23 bit mantissa), whereas a double has around 16 significant digits (52 bit
     * mantissa). This means that a float time with an offset of 1-1-0001 is at best precise to an hour level. A double time is
     * precise to microseconds. Therefore, avoid using float times that use the EPOCH_YEAR1_SECOND.
     */
    public static final TimeUnit EPOCH_YEAR1_SECOND = EPOCH_SECOND.deriveLinearOffset(1.0,
            new GregorianCalendar(1, 0, 1, 0, 0, 0).getTimeInMillis() / 1000.0, DurationUnit.SECOND, "s(Y1)",
            "seconds since 1-1-0001 00:00", UnitSystem.OTHER, "s(Y1)", "s(Y1)", "sec(Y1)");

    /**
     * The Epoch with J2000.0 as the origin, which is The Gregorian date January 1, 2000 at 12:00 GMT (noon) with a calculation
     * in seconds. When 1-1-2000 is used as the origin, 1-1-2021 has a value of around 6.3E11 ms. If we want to be precise on
     * the ms level, we need 11 significant digits. A float has around 7 significant digits (23 bit mantissa), whereas a double
     * has around 16 significant digits (52 bit mantissa). This means that a float time with an offset of 1-1-2000 is at best
     * precise to a minute level. A double time is precise to fractions of microseconds. Therefore, avoid using float times that
     * use the EPOCH_J2000_SECOND.
     */
    public static final TimeUnit EPOCH_J2000_SECOND = EPOCH_SECOND.deriveLinearOffset(1.0,
            new GregorianCalendar(2000, 0, 1, 12, 0, 0).getTimeInMillis() / 1000.0, DurationUnit.SECOND, "s(Y2000)",
            "seconds since 1-1-2000 12:00 GMT", UnitSystem.OTHER, "s(Y2000)", "s(Y2000)", "sec(Y2000)");

}