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
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 }