TimeComponents.java

  1. /* Copyright 2002-2013 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.apache.commons.math3.util.FastMath;
  25. import org.orekit.errors.OrekitException;
  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 = -8566834296299377436L;

  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, UTC time (only 0 difference with UTC is supported). */
  47.     private static Pattern ISO8601_FORMATS = Pattern.compile("^(\\d\\d):?(\\d\\d):?(\\d\\d(?:[.,]\\d+)?)?(?:Z|[-+]00(?::00)?)?$");

  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.     /** Build a time from its clock elements.
  55.      * <p>Note that seconds between 60.0 (inclusive) and 61.0 (exclusive) are allowed
  56.      * in this method, since they do occur during leap seconds introduction
  57.      * in the {@link UTCScale UTC} time scale.</p>
  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 61.0 (excluded)
  61.      * @exception IllegalArgumentException if inconsistent arguments
  62.      * are given (parameters out of range)
  63.      */
  64.     public TimeComponents(final int hour, final int minute, final double second)
  65.         throws IllegalArgumentException {

  66.         // range check
  67.         if ((hour   < 0) || (hour   >  23) ||
  68.             (minute < 0) || (minute >  59) ||
  69.             (second < 0) || (second >= 61.0)) {
  70.             throw OrekitException.createIllegalArgumentException(OrekitMessages.NON_EXISTENT_HMS_TIME,
  71.                                                                  hour, minute, second);
  72.         }

  73.         this.hour = hour;
  74.         this.minute = minute;
  75.         this.second = second;

  76.     }

  77.     /** Build a time from the second number within the day.
  78.      * @param secondInDay second number from 0.0 to {@link
  79.      * org.orekit.utils.Constants#JULIAN_DAY} (excluded)
  80.      * @exception IllegalArgumentException if seconds number is out of range
  81.      */
  82.     public TimeComponents(final double secondInDay) {
  83.         this(0, secondInDay);
  84.     }

  85.     /** Build a time from the second number within the day.
  86.      * <p>
  87.      * The second number is defined here as the sum
  88.      * {@code secondInDayA + secondInDayB} from 0.0 to {@link
  89.      * org.orekit.utils.Constants#JULIAN_DAY} (excluded). The two parameters
  90.      * are used for increased accuracy.
  91.      * </p>
  92.      * @param secondInDayA first part of the second number
  93.      * @param secondInDayB last part of the second number
  94.      * @exception IllegalArgumentException if seconds number is out of range
  95.      */
  96.     public TimeComponents(final int secondInDayA, final double secondInDayB) {

  97.         // split the numbers as a whole number of seconds
  98.         // and a fractional part between 0.0 (included) and 1.0 (excluded)
  99.         final int carry         = (int) FastMath.floor(secondInDayB);
  100.         int wholeSeconds        = secondInDayA + carry;
  101.         final double fractional = secondInDayB - carry;

  102.         // range check
  103.         if (wholeSeconds < 0 || wholeSeconds > 86400) {
  104.             // beware, 86400 must be allowed to cope with leap seconds introduction days
  105.             throw OrekitException.createIllegalArgumentException(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER,
  106.                                                                  wholeSeconds);
  107.         }

  108.         // extract the time components
  109.         hour          = wholeSeconds / 3600;
  110.         wholeSeconds -= 3600 * hour;
  111.         minute        = wholeSeconds / 60;
  112.         wholeSeconds -= 60 * minute;
  113.         second        = wholeSeconds + fractional;

  114.     }

  115.     /** Parse a string in ISO-8601 format to build a time.
  116.      * <p>The supported formats are:
  117.      * <ul>
  118.      *   <li>basic format local time: hhmmss (with optional decimals in seconds)</li>
  119.      *   <li>extended format local time: hh:mm:ss (with optional decimals in seconds)</li>
  120.      *   <li>basic format UTC time: hhmmssZ (with optional decimals in seconds)</li>
  121.      *   <li>extended format UTC time: hh:mm:ssZ (with optional decimals in seconds)</li>
  122.      *   <li>basic format local time with 00h UTC offset: hhmmss+00 (with optional decimals in seconds)</li>
  123.      *   <li>extended format local time with 00h UTC offset: hhmmss+00 (with optional decimals in seconds)</li>
  124.      *   <li>basic format local time with 00h and 00m UTC offset: hhmmss+00:00 (with optional decimals in seconds)</li>
  125.      *   <li>extended format local time with 00h and 00m UTC offset: hhmmss+00:00 (with optional decimals in seconds)</li>
  126.      * </ul>
  127.      * As shown by the list above, only the complete representations defined in section 4.2
  128.      * of ISO-8601 standard are supported, neither expended representations nor representations
  129.      * with reduced accuracy are supported.
  130.      * </p>
  131.      * <p>As this class does not support time zones (because space flight dynamics uses {@link
  132.      * TimeScale time scales} with offsets from UTC having sub-second accuracy), only UTC is zone is
  133.      * supported (and in fact ignored). It is the responsibility of the {@link AbsoluteDate} class to
  134.      * handle time scales appropriately.</p>
  135.      * @param string string to parse
  136.      * @return a parsed time
  137.      * @exception IllegalArgumentException if string cannot be parsed
  138.      */
  139.     public static  TimeComponents parseTime(final String string) {

  140.         // is the date a calendar date ?
  141.         final Matcher timeMatcher = ISO8601_FORMATS.matcher(string);
  142.         if (timeMatcher.matches()) {
  143.             return new TimeComponents(Integer.parseInt(timeMatcher.group(1)),
  144.                                       Integer.parseInt(timeMatcher.group(2)),
  145.                                       Double.parseDouble(timeMatcher.group(3).replace(',', '.')));
  146.         }

  147.         throw OrekitException.createIllegalArgumentException(OrekitMessages.NON_EXISTENT_TIME, string);

  148.     }

  149.     /** Get the hour number.
  150.      * @return hour number from 0 to 23
  151.      */
  152.     public int getHour() {
  153.         return hour;
  154.     }

  155.     /** Get the minute number.
  156.      * @return minute minute number from 0 to 59
  157.      */
  158.     public int getMinute() {
  159.         return minute;
  160.     }

  161.     /** Get the seconds number.
  162.      * @return second second number from 0.0 to 60.0 (excluded)
  163.      */
  164.     public double getSecond() {
  165.         return second;
  166.     }

  167.     /** Get the second number within the day.
  168.      * @return second number from 0.0 to Constants.JULIAN_DAY
  169.      */
  170.     public double getSecondsInDay() {
  171.         return second + 60 * minute + 3600 * hour;
  172.     }

  173.     /** Get a string representation of the time.
  174.      * @return string representation of the time
  175.      */
  176.     public String toString() {
  177.         return new StringBuffer().
  178.         append(TWO_DIGITS.format(hour)).append(':').
  179.         append(TWO_DIGITS.format(minute)).append(':').
  180.         append(SECONDS_FORMAT.format(second)).
  181.         toString();
  182.     }

  183.     /** {@inheritDoc} */
  184.     public int compareTo(final TimeComponents other) {
  185.         final double seconds = getSecondsInDay();
  186.         final double otherSeconds = other.getSecondsInDay();
  187.         if (seconds < otherSeconds) {
  188.             return -1;
  189.         } else if (seconds > otherSeconds) {
  190.             return 1;
  191.         }
  192.         return 0;
  193.     }

  194.     /** {@inheritDoc} */
  195.     public boolean equals(final Object other) {
  196.         try {
  197.             final TimeComponents otherTime = (TimeComponents) other;
  198.             return (otherTime != null) && (hour == otherTime.hour) &&
  199.                    (minute == otherTime.minute) && (second == otherTime.second);
  200.         } catch (ClassCastException cce) {
  201.             return false;
  202.         }
  203.     }

  204.     /** {@inheritDoc} */
  205.     public int hashCode() {
  206.         final long bits = Double.doubleToLongBits(second);
  207.         return ((hour << 16) ^ (minute << 8)) ^ (int) (bits ^ (bits >>> 32));
  208.     }

  209. }