1   /* Copyright 2002-2015 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  
19  import java.io.Serializable;
20  import java.text.DecimalFormat;
21  import java.text.DecimalFormatSymbols;
22  import java.util.Locale;
23  import java.util.regex.Matcher;
24  import java.util.regex.Pattern;
25  
26  import org.apache.commons.math3.util.FastMath;
27  import org.orekit.errors.OrekitException;
28  import org.orekit.errors.OrekitMessages;
29  
30  
31  /** Class representing a time within the day broken up as hour,
32   * minute and second components.
33   * <p>Instances of this class are guaranteed to be immutable.</p>
34   * @see DateComponents
35   * @see DateTimeComponents
36   * @author Luc Maisonobe
37   */
38  public class TimeComponents implements Serializable, Comparable<TimeComponents> {
39  
40      /** Constant for commonly used hour 00:00:00. */
41      public static final TimeComponents H00   = new TimeComponents(0, 0, 0);
42  
43      /** Constant for commonly used hour 12:00:00. */
44      public static final TimeComponents H12 = new TimeComponents(12, 0, 0);
45  
46      /** Serializable UID. */
47      private static final long serialVersionUID = -8566834296299377436L;
48  
49      /** Format for hours and minutes. */
50      private static final DecimalFormat TWO_DIGITS = new DecimalFormat("00");
51  
52      /** Format for seconds. */
53      private static final DecimalFormat SECONDS_FORMAT =
54          new DecimalFormat("00.000", new DecimalFormatSymbols(Locale.US));
55  
56      /** Basic and extends formats for local time, UTC time (only 0 difference with UTC is supported). */
57      private static Pattern ISO8601_FORMATS = Pattern.compile("^(\\d\\d):?(\\d\\d):?(\\d\\d(?:[.,]\\d+)?)?(?:Z|[-+]00(?::00)?)?$");
58  
59      /** Hour number. */
60      private final int hour;
61  
62      /** Minute number. */
63      private final int minute;
64  
65      /** Second number. */
66      private final double second;
67  
68      /** Build a time from its clock elements.
69       * <p>Note that seconds between 60.0 (inclusive) and 61.0 (exclusive) are allowed
70       * in this method, since they do occur during leap seconds introduction
71       * in the {@link UTCScale UTC} time scale.</p>
72       * @param hour hour number from 0 to 23
73       * @param minute minute number from 0 to 59
74       * @param second second number from 0.0 to 61.0 (excluded)
75       * @exception IllegalArgumentException if inconsistent arguments
76       * are given (parameters out of range)
77       */
78      public TimeComponents(final int hour, final int minute, final double second)
79          throws IllegalArgumentException {
80  
81          // range check
82          if ((hour   < 0) || (hour   >  23) ||
83              (minute < 0) || (minute >  59) ||
84              (second < 0) || (second >= 61.0)) {
85              throw OrekitException.createIllegalArgumentException(OrekitMessages.NON_EXISTENT_HMS_TIME,
86                                                                   hour, minute, second);
87          }
88  
89          this.hour = hour;
90          this.minute = minute;
91          this.second = second;
92  
93      }
94  
95      /** Build a time from the second number within the day.
96       * @param secondInDay second number from 0.0 to {@link
97       * org.orekit.utils.Constants#JULIAN_DAY} (excluded)
98       * @exception IllegalArgumentException if seconds number is out of range
99       */
100     public TimeComponents(final double secondInDay) {
101         this(0, secondInDay);
102     }
103 
104     /** Build a time from the second number within the day.
105      * <p>
106      * The second number is defined here as the sum
107      * {@code secondInDayA + secondInDayB} from 0.0 to {@link
108      * org.orekit.utils.Constants#JULIAN_DAY} (excluded). The two parameters
109      * are used for increased accuracy.
110      * </p>
111      * @param secondInDayA first part of the second number
112      * @param secondInDayB last part of the second number
113      * @exception IllegalArgumentException if seconds number is out of range
114      */
115     public TimeComponents(final int secondInDayA, final double secondInDayB) {
116 
117         // split the numbers as a whole number of seconds
118         // and a fractional part between 0.0 (included) and 1.0 (excluded)
119         final int carry         = (int) FastMath.floor(secondInDayB);
120         int wholeSeconds        = secondInDayA + carry;
121         final double fractional = secondInDayB - carry;
122 
123         // range check
124         if (wholeSeconds < 0 || wholeSeconds > 86400) {
125             // beware, 86400 must be allowed to cope with leap seconds introduction days
126             throw OrekitException.createIllegalArgumentException(OrekitMessages.OUT_OF_RANGE_SECONDS_NUMBER,
127                                                                  wholeSeconds);
128         }
129 
130         // extract the time components
131         hour          = wholeSeconds / 3600;
132         wholeSeconds -= 3600 * hour;
133         minute        = wholeSeconds / 60;
134         wholeSeconds -= 60 * minute;
135         second        = wholeSeconds + fractional;
136 
137     }
138 
139     /** Parse a string in ISO-8601 format to build a time.
140      * <p>The supported formats are:
141      * <ul>
142      *   <li>basic format local time: hhmmss (with optional decimals in seconds)</li>
143      *   <li>extended format local time: hh:mm:ss (with optional decimals in seconds)</li>
144      *   <li>basic format UTC time: hhmmssZ (with optional decimals in seconds)</li>
145      *   <li>extended format UTC time: hh:mm:ssZ (with optional decimals in seconds)</li>
146      *   <li>basic format local time with 00h UTC offset: hhmmss+00 (with optional decimals in seconds)</li>
147      *   <li>extended format local time with 00h UTC offset: hhmmss+00 (with optional decimals in seconds)</li>
148      *   <li>basic format local time with 00h and 00m UTC offset: hhmmss+00:00 (with optional decimals in seconds)</li>
149      *   <li>extended format local time with 00h and 00m UTC offset: hhmmss+00:00 (with optional decimals in seconds)</li>
150      * </ul>
151      * As shown by the list above, only the complete representations defined in section 4.2
152      * of ISO-8601 standard are supported, neither expended representations nor representations
153      * with reduced accuracy are supported.
154      * </p>
155      * <p>As this class does not support time zones (because space flight dynamics uses {@link
156      * TimeScale time scales} with offsets from UTC having sub-second accuracy), only UTC is zone is
157      * supported (and in fact ignored). It is the responsibility of the {@link AbsoluteDate} class to
158      * handle time scales appropriately.</p>
159      * @param string string to parse
160      * @return a parsed time
161      * @exception IllegalArgumentException if string cannot be parsed
162      */
163     public static  TimeComponents parseTime(final String string) {
164 
165         // is the date a calendar date ?
166         final Matcher timeMatcher = ISO8601_FORMATS.matcher(string);
167         if (timeMatcher.matches()) {
168             return new TimeComponents(Integer.parseInt(timeMatcher.group(1)),
169                                       Integer.parseInt(timeMatcher.group(2)),
170                                       Double.parseDouble(timeMatcher.group(3).replace(',', '.')));
171         }
172 
173         throw OrekitException.createIllegalArgumentException(OrekitMessages.NON_EXISTENT_TIME, string);
174 
175     }
176 
177     /** Get the hour number.
178      * @return hour number from 0 to 23
179      */
180     public int getHour() {
181         return hour;
182     }
183 
184     /** Get the minute number.
185      * @return minute minute number from 0 to 59
186      */
187     public int getMinute() {
188         return minute;
189     }
190 
191     /** Get the seconds number.
192      * @return second second number from 0.0 to 60.0 (excluded)
193      */
194     public double getSecond() {
195         return second;
196     }
197 
198     /** Get the second number within the day.
199      * @return second number from 0.0 to Constants.JULIAN_DAY
200      */
201     public double getSecondsInDay() {
202         return second + 60 * minute + 3600 * hour;
203     }
204 
205     /** Get a string representation of the time.
206      * @return string representation of the time
207      */
208     public String toString() {
209         return new StringBuffer().
210         append(TWO_DIGITS.format(hour)).append(':').
211         append(TWO_DIGITS.format(minute)).append(':').
212         append(SECONDS_FORMAT.format(second)).
213         toString();
214     }
215 
216     /** {@inheritDoc} */
217     public int compareTo(final TimeComponents other) {
218         final double seconds = getSecondsInDay();
219         final double otherSeconds = other.getSecondsInDay();
220         if (seconds < otherSeconds) {
221             return -1;
222         } else if (seconds > otherSeconds) {
223             return 1;
224         }
225         return 0;
226     }
227 
228     /** {@inheritDoc} */
229     public boolean equals(final Object other) {
230         try {
231             final TimeComponents otherTime = (TimeComponents) other;
232             return (otherTime != null) && (hour == otherTime.hour) &&
233                    (minute == otherTime.minute) && (second == otherTime.second);
234         } catch (ClassCastException cce) {
235             return false;
236         }
237     }
238 
239     /** {@inheritDoc} */
240     public int hashCode() {
241         final long bits = Double.doubleToLongBits(second);
242         return ((hour << 16) ^ (minute << 8)) ^ (int) (bits ^ (bits >>> 32));
243     }
244 
245 }