DateTimeComponents.java

  1. /* Copyright 2002-2022 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS 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.  *   http://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. package org.orekit.time;

  18. import java.io.Serializable;
  19. import java.text.DecimalFormat;
  20. import java.text.DecimalFormatSymbols;
  21. import java.util.Locale;

  22. import org.hipparchus.util.FastMath;
  23. import org.orekit.utils.Constants;

  24. /** Holder for date and time components.
  25.  * <p>This class is a simple holder with no processing methods.</p>
  26.  * <p>Instance of this class are guaranteed to be immutable.</p>
  27.  * @see AbsoluteDate
  28.  * @see DateComponents
  29.  * @see TimeComponents
  30.  * @author Luc Maisonobe
  31.  */
  32. public class DateTimeComponents implements Serializable, Comparable<DateTimeComponents> {

  33.     /**
  34.      * The Julian Epoch.
  35.      *
  36.      * @see TimeScales#getJulianEpoch()
  37.      */
  38.     public static final DateTimeComponents JULIAN_EPOCH =
  39.             new DateTimeComponents(DateComponents.JULIAN_EPOCH, TimeComponents.H12);

  40.     /** Serializable UID. */
  41.     private static final long serialVersionUID = 5061129505488924484L;

  42.     /** Date component. */
  43.     private final DateComponents date;

  44.     /** Time component. */
  45.     private final TimeComponents time;

  46.     /** Build a new instance from its components.
  47.      * @param date date component
  48.      * @param time time component
  49.      */
  50.     public DateTimeComponents(final DateComponents date, final TimeComponents time) {
  51.         this.date = date;
  52.         this.time = time;
  53.     }

  54.     /** Build an instance from raw level components.
  55.      * @param year year number (may be 0 or negative for BC years)
  56.      * @param month month number from 1 to 12
  57.      * @param day day number from 1 to 31
  58.      * @param hour hour number from 0 to 23
  59.      * @param minute minute number from 0 to 59
  60.      * @param second second number from 0.0 to 60.0 (excluded)
  61.      * @exception IllegalArgumentException if inconsistent arguments
  62.      * are given (parameters out of range, february 29 for non-leap years,
  63.      * dates during the gregorian leap in 1582 ...)
  64.      */
  65.     public DateTimeComponents(final int year, final int month, final int day,
  66.                               final int hour, final int minute, final double second)
  67.         throws IllegalArgumentException {
  68.         this.date = new DateComponents(year, month, day);
  69.         this.time = new TimeComponents(hour, minute, second);
  70.     }

  71.     /** Build an instance from raw level components.
  72.      * @param year year number (may be 0 or negative for BC years)
  73.      * @param month month enumerate
  74.      * @param day day number from 1 to 31
  75.      * @param hour hour number from 0 to 23
  76.      * @param minute minute number from 0 to 59
  77.      * @param second second number from 0.0 to 60.0 (excluded)
  78.      * @exception IllegalArgumentException if inconsistent arguments
  79.      * are given (parameters out of range, february 29 for non-leap years,
  80.      * dates during the gregorian leap in 1582 ...)
  81.      */
  82.     public DateTimeComponents(final int year, final Month month, final int day,
  83.                               final int hour, final int minute, final double second)
  84.         throws IllegalArgumentException {
  85.         this.date = new DateComponents(year, month, day);
  86.         this.time = new TimeComponents(hour, minute, second);
  87.     }

  88.     /** Build an instance from raw level components.
  89.      * <p>The hour is set to 00:00:00.000.</p>
  90.      * @param year year number (may be 0 or negative for BC years)
  91.      * @param month month number from 1 to 12
  92.      * @param day day number from 1 to 31
  93.      * @exception IllegalArgumentException if inconsistent arguments
  94.      * are given (parameters out of range, february 29 for non-leap years,
  95.      * dates during the gregorian leap in 1582 ...)
  96.      */
  97.     public DateTimeComponents(final int year, final int month, final int day)
  98.         throws IllegalArgumentException {
  99.         this.date = new DateComponents(year, month, day);
  100.         this.time = TimeComponents.H00;
  101.     }

  102.     /** Build an instance from raw level components.
  103.      * <p>The hour is set to 00:00:00.000.</p>
  104.      * @param year year number (may be 0 or negative for BC years)
  105.      * @param month month enumerate
  106.      * @param day day number from 1 to 31
  107.      * @exception IllegalArgumentException if inconsistent arguments
  108.      * are given (parameters out of range, february 29 for non-leap years,
  109.      * dates during the gregorian leap in 1582 ...)
  110.      */
  111.     public DateTimeComponents(final int year, final Month month, final int day)
  112.         throws IllegalArgumentException {
  113.         this.date = new DateComponents(year, month, day);
  114.         this.time = TimeComponents.H00;
  115.     }

  116.     /** Build an instance from a seconds offset with respect to another one.
  117.      * @param reference reference date/time
  118.      * @param offset offset from the reference in seconds
  119.      * @see #offsetFrom(DateTimeComponents)
  120.      */
  121.     public DateTimeComponents(final DateTimeComponents reference,
  122.                               final double offset) {

  123.         // extract linear data from reference date/time
  124.         int    day     = reference.getDate().getJ2000Day();
  125.         double seconds = reference.getTime().getSecondsInLocalDay();

  126.         // apply offset
  127.         seconds += offset;

  128.         // fix range
  129.         final int dayShift = (int) FastMath.floor(seconds / Constants.JULIAN_DAY);
  130.         seconds -= Constants.JULIAN_DAY * dayShift;
  131.         day     += dayShift;
  132.         final TimeComponents tmpTime = new TimeComponents(seconds);

  133.         // set up components
  134.         this.date = new DateComponents(day);
  135.         this.time = new TimeComponents(tmpTime.getHour(), tmpTime.getMinute(), tmpTime.getSecond(),
  136.                                        reference.getTime().getMinutesFromUTC());

  137.     }

  138.     /** Parse a string in ISO-8601 format to build a date/time.
  139.      * <p>The supported formats are all date formats supported by {@link DateComponents#parseDate(String)}
  140.      * and all time formats supported by {@link TimeComponents#parseTime(String)} separated
  141.      * by the standard time separator 'T', or date components only (in which case a 00:00:00 hour is
  142.      * implied). Typical examples are 2000-01-01T12:00:00Z or 1976W186T210000.
  143.      * </p>
  144.      * @param string string to parse
  145.      * @return a parsed date/time
  146.      * @exception IllegalArgumentException if string cannot be parsed
  147.      */
  148.     public static DateTimeComponents parseDateTime(final String string) {

  149.         // is there a time ?
  150.         final int tIndex = string.indexOf('T');
  151.         if (tIndex > 0) {
  152.             return new DateTimeComponents(DateComponents.parseDate(string.substring(0, tIndex)),
  153.                                           TimeComponents.parseTime(string.substring(tIndex + 1)));
  154.         }

  155.         return new DateTimeComponents(DateComponents.parseDate(string), TimeComponents.H00);

  156.     }

  157.     /** Compute the seconds offset between two instances.
  158.      * @param dateTime dateTime to subtract from the instance
  159.      * @return offset in seconds between the two instants
  160.      * (positive if the instance is posterior to the argument)
  161.      * @see #DateTimeComponents(DateTimeComponents, double)
  162.      */
  163.     public double offsetFrom(final DateTimeComponents dateTime) {
  164.         final int dateOffset = date.getJ2000Day() - dateTime.date.getJ2000Day();
  165.         final double timeOffset = time.getSecondsInUTCDay() - dateTime.time.getSecondsInUTCDay();
  166.         return Constants.JULIAN_DAY * dateOffset + timeOffset;
  167.     }

  168.     /** Get the date component.
  169.      * @return date component
  170.      */
  171.     public DateComponents getDate() {
  172.         return date;
  173.     }

  174.     /** Get the time component.
  175.      * @return time component
  176.      */
  177.     public TimeComponents getTime() {
  178.         return time;
  179.     }

  180.     /** {@inheritDoc} */
  181.     public int compareTo(final DateTimeComponents other) {
  182.         final int dateComparison = date.compareTo(other.date);
  183.         if (dateComparison < 0) {
  184.             return -1;
  185.         } else if (dateComparison > 0) {
  186.             return 1;
  187.         }
  188.         return time.compareTo(other.time);
  189.     }

  190.     /** {@inheritDoc} */
  191.     public boolean equals(final Object other) {
  192.         try {
  193.             final DateTimeComponents otherDateTime = (DateTimeComponents) other;
  194.             return otherDateTime != null &&
  195.                    date.equals(otherDateTime.date) && time.equals(otherDateTime.time);
  196.         } catch (ClassCastException cce) {
  197.             return false;
  198.         }
  199.     }

  200.     /** {@inheritDoc} */
  201.     public int hashCode() {
  202.         return (date.hashCode() << 16) ^ time.hashCode();
  203.     }

  204.     /** Return a string representation of this pair.
  205.      * <p>The format used is ISO8601 including the UTC offset.</p>
  206.      * @return string representation of this pair
  207.      */
  208.     public String toString() {
  209.         return date.toString() + 'T' + time.toString();
  210.     }

  211.     /**
  212.      * Get a string representation of the date-time without the offset from UTC. The
  213.      * format used is ISO6801, except without the offset from UTC.
  214.      *
  215.      * @return a string representation of the date-time.
  216.      * @see #toStringWithoutUtcOffset(int, int)
  217.      * @see #toString(int, int)
  218.      * @see #toStringRfc3339()
  219.      */
  220.     public String toStringWithoutUtcOffset() {
  221.         return date.toString() + 'T' + time.toStringWithoutUtcOffset();
  222.     }


  223.     /**
  224.      * Return a string representation of this date-time, rounded to millisecond
  225.      * precision.
  226.      *
  227.      * <p>The format used is ISO8601 including the UTC offset.</p>
  228.      *
  229.      * @param minuteDuration 60, 61, or 62 seconds depending on the date being close to a
  230.      *                       leap second introduction and the magnitude of the leap
  231.      *                       second.
  232.      * @return string representation of this date, time, and UTC offset
  233.      * @see #toString(int, int)
  234.      */
  235.     public String toString(final int minuteDuration) {
  236.         return toString(minuteDuration, 3);
  237.     }

  238.     /**
  239.      * Return a string representation of this date-time, rounded to the given precision.
  240.      *
  241.      * <p>The format used is ISO8601 including the UTC offset.</p>
  242.      *
  243.      * @param minuteDuration 59, 60, 61, or 62 seconds depending on the date being close
  244.      *                       to a leap second introduction and the magnitude of the leap
  245.      *                       second.
  246.      * @param fractionDigits the number of digits to include after the decimal point in
  247.      *                       the string representation of the seconds. The date and time
  248.      *                       is first rounded as necessary. {@code fractionDigits} must
  249.      *                       be greater than or equal to {@code 0}.
  250.      * @return string representation of this date, time, and UTC offset
  251.      * @see #toStringRfc3339()
  252.      * @see #toStringWithoutUtcOffset()
  253.      * @see #toStringWithoutUtcOffset(int, int)
  254.      * @since 11.0
  255.      */
  256.     public String toString(final int minuteDuration, final int fractionDigits) {
  257.         return toStringWithoutUtcOffset(minuteDuration, fractionDigits) +
  258.                 time.formatUtcOffset();
  259.     }

  260.     /**
  261.      * Return a string representation of this date-time, rounded to the given precision.
  262.      *
  263.      * <p>The format used is ISO8601 without the UTC offset.</p>
  264.      *
  265.      * @param minuteDuration 59, 60, 61, or 62 seconds depending on the date being close
  266.      *                       to a leap second introduction and the magnitude of the leap
  267.      *                       second.
  268.      * @param fractionDigits the number of digits to include after the decimal point in
  269.      *                       the string representation of the seconds. The date and time
  270.      *                       is first rounded as necessary. {@code fractionDigits} must
  271.      *                       be greater than or equal to {@code 0}.
  272.      * @return string representation of this date, time, and UTC offset
  273.      * @see #toStringRfc3339()
  274.      * @see #toStringWithoutUtcOffset()
  275.      * @see #toString(int, int)
  276.      * @since 11.1
  277.      */
  278.     public String toStringWithoutUtcOffset(final int minuteDuration,
  279.                                            final int fractionDigits) {
  280.         final DecimalFormat secondsFormat =
  281.                 new DecimalFormat("00", new DecimalFormatSymbols(Locale.US));
  282.         secondsFormat.setMaximumFractionDigits(fractionDigits);
  283.         secondsFormat.setMinimumFractionDigits(fractionDigits);
  284.         DateComponents roundedDate = this.date;
  285.         TimeComponents roundedTime = this.time;
  286.         double second = time.getSecond();
  287.         final double wrap = minuteDuration - 0.5 * FastMath.pow(10, -fractionDigits);
  288.         if (second >= wrap) {
  289.             // we should wrap around to the next minute
  290.             int minute = time.getMinute();
  291.             int hour   = time.getHour();
  292.             int j2000  = date.getJ2000Day();
  293.             second = 0;
  294.             ++minute;
  295.             if (minute > 59) {
  296.                 minute = 0;
  297.                 ++hour;
  298.                 if (hour > 23) {
  299.                     hour = 0;
  300.                     ++j2000;
  301.                 }
  302.             }
  303.             roundedDate = new DateComponents(j2000);
  304.             roundedTime = new TimeComponents(hour, minute, second);
  305.         }
  306.         return roundedDate.toString() + 'T' +
  307.                 roundedTime.toStringWithoutUtcOffset(secondsFormat);
  308.     }

  309.     /**
  310.      * Represent the given date and time as a string according to the format in RFC 3339.
  311.      * RFC3339 is a restricted subset of ISO 8601 with a well defined grammar. This method
  312.      * includes enough precision to represent the point in time without rounding up to the
  313.      * next minute.
  314.      *
  315.      * <p>RFC3339 is unable to represent BC years, years of 10000 or more, time zone
  316.      * offsets of 100 hours or more, or NaN. In these cases the value returned from this
  317.      * method will not be valid RFC3339 format.
  318.      *
  319.      * @return RFC 3339 format string.
  320.      * @see <a href="https://tools.ietf.org/html/rfc3339#page-8">RFC 3339</a>
  321.      * @see AbsoluteDate#toStringRfc3339(TimeScale)
  322.      * @see #toString(int, int)
  323.      * @see #toStringWithoutUtcOffset()
  324.      */
  325.     public String toStringRfc3339() {
  326.         final DateComponents d = this.getDate();
  327.         final TimeComponents t = this.getTime();
  328.         // date
  329.         final String dateString = String.format("%04d-%02d-%02dT",
  330.                 d.getYear(), d.getMonth(), d.getDay());
  331.         // time
  332.         final String timeString;
  333.         if (t.getSecondsInLocalDay() != 0) {
  334.             final DecimalFormat format = new DecimalFormat("00.##############", new DecimalFormatSymbols(Locale.US));
  335.             timeString = String.format("%02d:%02d:", t.getHour(), t.getMinute()) +
  336.                     format.format(t.getSecond());
  337.         } else {
  338.             // shortcut for midnight local time
  339.             timeString = "00:00:00";
  340.         }
  341.         // offset
  342.         final int minutesFromUTC = t.getMinutesFromUTC();
  343.         final String timeZoneString;
  344.         if (minutesFromUTC == 0) {
  345.             timeZoneString = "Z";
  346.         } else {
  347.             // sign must be accounted for separately because there is no -0 in Java.
  348.             final String sign = minutesFromUTC < 0 ? "-" : "+";
  349.             final int utcOffset = FastMath.abs(minutesFromUTC);
  350.             final int hourOffset = utcOffset / 60;
  351.             final int minuteOffset = utcOffset % 60;
  352.             timeZoneString = sign + String.format("%02d:%02d", hourOffset, minuteOffset);
  353.         }
  354.         return dateString + timeString + timeZoneString;
  355.     }

  356. }