DateTimeComponents.java

  1. /* Copyright 2002-2020 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.</p>
  206.      * @return string representation of this pair
  207.      */
  208.     public String toString() {
  209.         return toString(60);
  210.     }

  211.     /** Return a string representation of this pair.
  212.      * <p>The format used is ISO8601.</p>
  213.      * @param minuteDuration 60 or 61 depending on the date being
  214.      * close to a leap second introduction
  215.      * @return string representation of this pair
  216.      */
  217.     public String toString(final int minuteDuration) {
  218.         double second = time.getSecond();
  219.         final double wrap = minuteDuration - 0.0005;
  220.         if (second >= wrap) {
  221.             // we should wrap around next millisecond
  222.             int minute = time.getMinute();
  223.             int hour   = time.getHour();
  224.             int j2000  = date.getJ2000Day();
  225.             second = 0;
  226.             ++minute;
  227.             if (minute > 59) {
  228.                 minute = 0;
  229.                 ++hour;
  230.                 if (hour > 23) {
  231.                     hour = 0;
  232.                     ++j2000;
  233.                 }
  234.             }
  235.             return new DateComponents(j2000).toString() + 'T' + new TimeComponents(hour, minute, second).toString();
  236.         }
  237.         return date.toString() + 'T' + time.toString();
  238.     }

  239.     /**
  240.      * Represent the given date and time as a string according to the format in RFC 3339.
  241.      * RFC3339 is a restricted subset of ISO 8601 with a well defined grammar. This method
  242.      * includes enough precision to represent the point in time without rounding up to the
  243.      * next minute.
  244.      *
  245.      * <p>RFC3339 is unable to represent BC years, years of 10000 or more, time zone
  246.      * offsets of 100 hours or more, or NaN. In these cases the value returned from this
  247.      * method will not be valid RFC3339 format.
  248.      *
  249.      * @return RFC 3339 format string.
  250.      * @see <a href="https://tools.ietf.org/html/rfc3339#page-8">RFC 3339</a>
  251.      * @see AbsoluteDate#toStringRfc3339(TimeScale)
  252.      * @see #toString(int)
  253.      */
  254.     public String toStringRfc3339() {
  255.         final DateComponents d = this.getDate();
  256.         final TimeComponents t = this.getTime();
  257.         // date
  258.         final String dateString = String.format("%04d-%02d-%02dT",
  259.                 d.getYear(), d.getMonth(), d.getDay());
  260.         // time
  261.         final String timeString;
  262.         if (t.getSecondsInLocalDay() != 0) {
  263.             final DecimalFormat format = new DecimalFormat("00.##############", new DecimalFormatSymbols(Locale.US));
  264.             timeString = String.format("%02d:%02d:", t.getHour(), t.getMinute()) +
  265.                     format.format(t.getSecond());
  266.         } else {
  267.             // shortcut for midnight local time
  268.             timeString = "00:00:00";
  269.         }
  270.         // offset
  271.         final int minutesFromUTC = t.getMinutesFromUTC();
  272.         final String timeZoneString;
  273.         if (minutesFromUTC == 0) {
  274.             timeZoneString = "Z";
  275.         } else {
  276.             // sign must be accounted for separately because there is no -0 in Java.
  277.             final String sign = minutesFromUTC < 0 ? "-" : "+";
  278.             final int utcOffset = FastMath.abs(minutesFromUTC);
  279.             final int hourOffset = utcOffset / 60;
  280.             final int minuteOffset = utcOffset % 60;
  281.             timeZoneString = sign + String.format("%02d:%02d", hourOffset, minuteOffset);
  282.         }
  283.         return dateString + timeString + timeZoneString;
  284.     }

  285. }