TimeComponents.java

  1. /* Copyright 2002-2018 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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 java.util.regex.Matcher;
  23. import java.util.regex.Pattern;

  24. import org.hipparchus.util.FastMath;
  25. import org.orekit.errors.OrekitIllegalArgumentException;
  26. import org.orekit.errors.OrekitMessages;


  27. /** Class representing a time within the day broken up as hour,
  28.  * minute and second components.
  29.  * <p>Instances of this class are guaranteed to be immutable.</p>
  30.  * @see DateComponents
  31.  * @see DateTimeComponents
  32.  * @author Luc Maisonobe
  33.  */
  34. public class TimeComponents implements Serializable, Comparable<TimeComponents> {

  35.     /** Constant for commonly used hour 00:00:00. */
  36.     public static final TimeComponents H00   = new TimeComponents(0, 0, 0);

  37.     /** Constant for commonly used hour 12:00:00. */
  38.     public static final TimeComponents H12 = new TimeComponents(12, 0, 0);

  39.     /** Serializable UID. */
  40.     private static final long serialVersionUID = 20160331L;

  41.     /** Format for hours and minutes. */
  42.     private static final DecimalFormat TWO_DIGITS = new DecimalFormat("00");

  43.     /** Format for seconds. */
  44.     private static final DecimalFormat SECONDS_FORMAT =
  45.         new DecimalFormat("00.000", new DecimalFormatSymbols(Locale.US));

  46.     /** Basic and extends formats for local time, with optional timezone. */
  47.     private static Pattern ISO8601_FORMATS = Pattern.compile("^(\\d\\d):?(\\d\\d):?(\\d\\d(?:[.,]\\d+)?)?(?:Z|([-+]\\d\\d(?::?\\d\\d)?))?$");

  48.     /** Hour number. */
  49.     private final int hour;

  50.     /** Minute number. */
  51.     private final int minute;

  52.     /** Second number. */
  53.     private final double second;

  54.     /** Offset between the specified date and UTC.
  55.      * <p>
  56.      * Always an integral number of minutes, as per ISO-8601 standard.
  57.      * </p>
  58.      * @since 7.2
  59.      */
  60.     private final int minutesFromUTC;

  61.     /** Build a time from its clock elements.
  62.      * <p>Note that seconds between 60.0 (inclusive) and 61.0 (exclusive) are allowed
  63.      * in this method, since they do occur during leap seconds introduction
  64.      * in the {@link UTCScale UTC} time scale.</p>
  65.      * @param hour hour number from 0 to 23
  66.      * @param minute minute number from 0 to 59
  67.      * @param second second number from 0.0 to 61.0 (excluded)
  68.      * @exception IllegalArgumentException if inconsistent arguments
  69.      * are given (parameters out of range)
  70.      */
  71.     public TimeComponents(final int hour, final int minute, final double second)
  72.         throws IllegalArgumentException {
  73.         this(hour, minute, second, 0);
  74.     }

  75.     /** Build a time from its clock elements.
  76.      * <p>Note that seconds between 60.0 (inclusive) and 61.0 (exclusive) are allowed
  77.      * in this method, since they do occur during leap seconds introduction
  78.      * in the {@link UTCScale UTC} time scale.</p>
  79.      * @param hour hour number from 0 to 23
  80.      * @param minute minute number from 0 to 59
  81.      * @param second second number from 0.0 to 61.0 (excluded)
  82.      * @param minutesFromUTC offset between the specified date and UTC, as an
  83.      * integral number of minutes, as per ISO-8601 standard
  84.      * @exception IllegalArgumentException if inconsistent arguments
  85.      * are given (parameters out of range)
  86.      * @since 7.2
  87.      */
  88.     public TimeComponents(final int hour, final int minute, final double second,
  89.                           final int minutesFromUTC)
  90.         throws IllegalArgumentException {

  91.         // range check
  92.         if ((hour   < 0) || (hour   >  23) ||
  93.             (minute < 0) || (minute >  59) ||
  94.             (second < 0) || (second >= 61.0)) {
  95.             throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_HMS_TIME,
  96.                                                      hour, minute, second);
  97.         }

  98.         this.hour           = hour;
  99.         this.minute         = minute;
  100.         this.second         = second;
  101.         this.minutesFromUTC = minutesFromUTC;

  102.     }

  103.     /** Build a time from the second number within the day.
  104.      * <p>
  105.      * This constructor is always in UTC (i.e. {@link #getMinutesFromUTC() will return 0}).
  106.      * </p>
  107.      * @param secondInDay second number from 0.0 to {@link
  108.      * org.orekit.utils.Constants#JULIAN_DAY} (excluded)
  109.      * @exception OrekitIllegalArgumentException if seconds number is out of range
  110.      */
  111.     public TimeComponents(final double secondInDay)
  112.         throws OrekitIllegalArgumentException {
  113.         this(0, secondInDay);
  114.     }

  115.     /** Build a time from the second number within the day.
  116.      * <p>
  117.      * The second number is defined here as the sum
  118.      * {@code secondInDayA + secondInDayB} from 0.0 to {@link
  119.      * org.orekit.utils.Constants#JULIAN_DAY} (excluded). The two parameters
  120.      * are used for increased accuracy.
  121.      * </p>
  122.      * <p>
  123.      * This constructor is always in UTC (i.e. {@link #getMinutesFromUTC() will return 0}).
  124.      * </p>
  125.      * @param secondInDayA first part of the second number
  126.      * @param secondInDayB last part of the second number
  127.      * @exception OrekitIllegalArgumentException if seconds number is out of range
  128.      */
  129.     public TimeComponents(final int secondInDayA, final double secondInDayB)
  130.         throws OrekitIllegalArgumentException {

  131.         // split the numbers as a whole number of seconds
  132.         // and a fractional part between 0.0 (included) and 1.0 (excluded)
  133.         final int carry         = (int) FastMath.floor(secondInDayB);
  134.         int wholeSeconds        = secondInDayA + carry;
  135.         final double fractional = secondInDayB - carry;

  136.         // range check
  137.         if (wholeSeconds < 0 || wholeSeconds > 86400) {
  138.             // beware, 86400 must be allowed to cope with leap seconds introduction days
  139.             throw new OrekitIllegalArgumentException(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER,
  140.                                                      wholeSeconds + fractional);
  141.         }

  142.         // extract the time components
  143.         hour           = wholeSeconds / 3600;
  144.         wholeSeconds  -= 3600 * hour;
  145.         minute         = wholeSeconds / 60;
  146.         wholeSeconds  -= 60 * minute;
  147.         second         = wholeSeconds + fractional;
  148.         minutesFromUTC = 0;

  149.     }

  150.     /** Parse a string in ISO-8601 format to build a time.
  151.      * <p>The supported formats are:
  152.      * <ul>
  153.      *   <li>basic and extended format local time: hhmmss, hh:mm:ss (with optional decimals in seconds)</li>
  154.      *   <li>optional UTC time: hhmmssZ, hh:mm:ssZ</li>
  155.      *   <li>optional signed hours UTC offset: hhmmss+HH, hhmmss-HH, hh:mm:ss+HH, hh:mm:ss-HH</li>
  156.      *   <li>optional signed basic hours and minutes UTC offset: hhmmss+HHMM, hhmmss-HHMM, hh:mm:ss+HHMM, hh:mm:ss-HHMM</li>
  157.      *   <li>optional signed extended hours and minutes UTC offset: hhmmss+HH:MM, hhmmss-HH:MM, hh:mm:ss+HH:MM, hh:mm:ss-HH:MM</li>
  158.      * </ul>
  159.      *
  160.      * <p> As shown by the list above, only the complete representations defined in section 4.2
  161.      * of ISO-8601 standard are supported, neither expended representations nor representations
  162.      * with reduced accuracy are supported.
  163.      *
  164.      * @param string string to parse
  165.      * @return a parsed time
  166.      * @exception IllegalArgumentException if string cannot be parsed
  167.      */
  168.     public static TimeComponents parseTime(final String string) {

  169.         // is the date a calendar date ?
  170.         final Matcher timeMatcher = ISO8601_FORMATS.matcher(string);
  171.         if (timeMatcher.matches()) {
  172.             final int    hour      = Integer.parseInt(timeMatcher.group(1));
  173.             final int    minute    = Integer.parseInt(timeMatcher.group(2));
  174.             final double second    = timeMatcher.group(3) == null ? 0.0 : Double.parseDouble(timeMatcher.group(3).replace(',', '.'));
  175.             final String offset    = timeMatcher.group(4);
  176.             final int    minutesFromUTC;
  177.             if (offset == null) {
  178.                 // no offset from UTC is given
  179.                 minutesFromUTC = 0;
  180.             } else {
  181.                 // we need to parse an offset from UTC
  182.                 // the sign is mandatory and the ':' separator is optional
  183.                 // so we can have offsets given as -06:00 or +0100
  184.                 final int sign          = offset.codePointAt(0) == '-' ? -1 : +1;
  185.                 final int hourOffset    = Integer.parseInt(offset.substring(1, 3));
  186.                 final int minutesOffset = offset.length() <= 3 ? 0 : Integer.parseInt(offset.substring(offset.length() - 2));
  187.                 minutesFromUTC          = sign * (minutesOffset + 60 * hourOffset);
  188.             }
  189.             return new TimeComponents(hour, minute, second, minutesFromUTC);
  190.         }

  191.         throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_TIME, string);

  192.     }

  193.     /** Get the hour number.
  194.      * @return hour number from 0 to 23
  195.      */
  196.     public int getHour() {
  197.         return hour;
  198.     }

  199.     /** Get the minute number.
  200.      * @return minute minute number from 0 to 59
  201.      */
  202.     public int getMinute() {
  203.         return minute;
  204.     }

  205.     /** Get the seconds number.
  206.      * @return second second number from 0.0 to 60.0 (excluded)
  207.      */
  208.     public double getSecond() {
  209.         return second;
  210.     }

  211.     /** Get the offset between the specified date and UTC.
  212.      * <p>
  213.      * The offset is always an integral number of minutes, as per ISO-8601 standard.
  214.      * </p>
  215.      * @return offset in minutes between the specified date and UTC
  216.      * @since 7.2
  217.      */
  218.     public int getMinutesFromUTC() {
  219.         return minutesFromUTC;
  220.     }

  221.     /** Get the second number within the local day, <em>without</em> applying the {@link #getMinutesFromUTC() offset from UTC}.
  222.      * @return second number from 0.0 to Constants.JULIAN_DAY
  223.      * @see #getSecondsInUTCDay()
  224.      * @since 7.2
  225.      */
  226.     public double getSecondsInLocalDay() {
  227.         return second + 60 * minute + 3600 * hour;
  228.     }

  229.     /** Get the second number within the UTC day, applying the {@link #getMinutesFromUTC() offset from UTC}.
  230.      * @return second number from {@link #getMinutesFromUTC() -getMinutesFromUTC()}
  231.      * to Constants.JULIAN_DAY {@link #getMinutesFromUTC() + getMinutesFromUTC()}
  232.      * @see #getSecondsInLocalDay()
  233.      * @since 7.2
  234.      */
  235.     public double getSecondsInUTCDay() {
  236.         return second + 60 * (minute - minutesFromUTC) + 3600 * hour;
  237.     }

  238.     /** Get a string representation of the time.
  239.      * @return string representation of the time
  240.      */
  241.     public String toString() {
  242.         StringBuilder builder  = new StringBuilder().
  243.                                  append(TWO_DIGITS.format(hour)).append(':').
  244.                                  append(TWO_DIGITS.format(minute)).append(':').
  245.                                  append(SECONDS_FORMAT.format(second));
  246.         if (minutesFromUTC != 0) {
  247.             builder = builder.
  248.                       append(minutesFromUTC < 0 ? '-' : '+').
  249.                       append(TWO_DIGITS.format(FastMath.abs(minutesFromUTC) / 60)).append(':').
  250.                       append(TWO_DIGITS.format(FastMath.abs(minutesFromUTC) % 60));
  251.         }
  252.         return builder.toString();
  253.     }

  254.     /** {@inheritDoc} */
  255.     public int compareTo(final TimeComponents other) {
  256.         return Double.compare(getSecondsInUTCDay(), other.getSecondsInUTCDay());
  257.     }

  258.     /** {@inheritDoc} */
  259.     public boolean equals(final Object other) {
  260.         try {
  261.             final TimeComponents otherTime = (TimeComponents) other;
  262.             return otherTime != null &&
  263.                    hour           == otherTime.hour   &&
  264.                    minute         == otherTime.minute &&
  265.                    second         == otherTime.second &&
  266.                    minutesFromUTC == otherTime.minutesFromUTC;
  267.         } catch (ClassCastException cce) {
  268.             return false;
  269.         }
  270.     }

  271.     /** {@inheritDoc} */
  272.     public int hashCode() {
  273.         final long bits = Double.doubleToLongBits(second);
  274.         return ((hour << 16) ^ ((minute - minutesFromUTC) << 8)) ^ (int) (bits ^ (bits >>> 32));
  275.     }

  276. }