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 }