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  
19  import java.io.Serializable;
20  
21  import org.hipparchus.util.FastMath;
22  import org.orekit.utils.Constants;
23  
24  /** Holder for date and time components.
25   * <p>This class is a simple holder with no processing methods.</p>
26   * <p>Instance of this class are guaranteed to be immutable.</p>
27   * @see AbsoluteDate
28   * @see DateComponents
29   * @see TimeComponents
30   * @author Luc Maisonobe
31   */
32  public class DateTimeComponents implements Serializable, Comparable<DateTimeComponents> {
33  
34      /** Serializable UID. */
35      private static final long serialVersionUID = 5061129505488924484L;
36  
37      /** Date component. */
38      private final DateComponents date;
39  
40      /** Time component. */
41      private final TimeComponents time;
42  
43      /** Build a new instance from its components.
44       * @param date date component
45       * @param time time component
46       */
47      public DateTimeComponents(final DateComponents date, final TimeComponents time) {
48          this.date = date;
49          this.time = time;
50      }
51  
52      /** Build an instance from raw level components.
53       * @param year year number (may be 0 or negative for BC years)
54       * @param month month number from 1 to 12
55       * @param day day number from 1 to 31
56       * @param hour hour number from 0 to 23
57       * @param minute minute number from 0 to 59
58       * @param second second number from 0.0 to 60.0 (excluded)
59       * @exception IllegalArgumentException if inconsistent arguments
60       * are given (parameters out of range, february 29 for non-leap years,
61       * dates during the gregorian leap in 1582 ...)
62       */
63      public DateTimeComponents(final int year, final int month, final int day,
64                                final int hour, final int minute, final double second)
65          throws IllegalArgumentException {
66          this.date = new DateComponents(year, month, day);
67          this.time = new TimeComponents(hour, minute, second);
68      }
69  
70      /** Build an instance from raw level components.
71       * @param year year number (may be 0 or negative for BC years)
72       * @param month month enumerate
73       * @param day day number from 1 to 31
74       * @param hour hour number from 0 to 23
75       * @param minute minute number from 0 to 59
76       * @param second second number from 0.0 to 60.0 (excluded)
77       * @exception IllegalArgumentException if inconsistent arguments
78       * are given (parameters out of range, february 29 for non-leap years,
79       * dates during the gregorian leap in 1582 ...)
80       */
81      public DateTimeComponents(final int year, final Month month, final int day,
82                                final int hour, final int minute, final double second)
83          throws IllegalArgumentException {
84          this.date = new DateComponents(year, month, day);
85          this.time = new TimeComponents(hour, minute, second);
86      }
87  
88      /** Build an instance from raw level components.
89       * <p>The hour is set to 00:00:00.000.</p>
90       * @param year year number (may be 0 or negative for BC years)
91       * @param month month number from 1 to 12
92       * @param day day number from 1 to 31
93       * @exception IllegalArgumentException if inconsistent arguments
94       * are given (parameters out of range, february 29 for non-leap years,
95       * dates during the gregorian leap in 1582 ...)
96       */
97      public DateTimeComponents(final int year, final int month, final int day)
98          throws IllegalArgumentException {
99          this.date = new DateComponents(year, month, day);
100         this.time = TimeComponents.H00;
101     }
102 
103     /** Build an instance from raw level components.
104      * <p>The hour is set to 00:00:00.000.</p>
105      * @param year year number (may be 0 or negative for BC years)
106      * @param month month enumerate
107      * @param day day number from 1 to 31
108      * @exception IllegalArgumentException if inconsistent arguments
109      * are given (parameters out of range, february 29 for non-leap years,
110      * dates during the gregorian leap in 1582 ...)
111      */
112     public DateTimeComponents(final int year, final Month month, final int day)
113         throws IllegalArgumentException {
114         this.date = new DateComponents(year, month, day);
115         this.time = TimeComponents.H00;
116     }
117 
118     /** Build an instance from a seconds offset with respect to another one.
119      * @param reference reference date/time
120      * @param offset offset from the reference in seconds
121      * @see #offsetFrom(DateTimeComponents)
122      */
123     public DateTimeComponents(final DateTimeComponents reference,
124                               final double offset) {
125 
126         // extract linear data from reference date/time
127         int    day     = reference.getDate().getJ2000Day();
128         double seconds = reference.getTime().getSecondsInLocalDay();
129 
130         // apply offset
131         seconds += offset;
132 
133         // fix range
134         final int dayShift = (int) FastMath.floor(seconds / Constants.JULIAN_DAY);
135         seconds -= Constants.JULIAN_DAY * dayShift;
136         day     += dayShift;
137         final TimeComponents tmpTime = new TimeComponents(seconds);
138 
139         // set up components
140         this.date = new DateComponents(day);
141         this.time = new TimeComponents(tmpTime.getHour(), tmpTime.getMinute(), tmpTime.getSecond(),
142                                        reference.getTime().getMinutesFromUTC());
143 
144     }
145 
146     /** Parse a string in ISO-8601 format to build a date/time.
147      * <p>The supported formats are all date formats supported by {@link DateComponents#parseDate(String)}
148      * and all time formats supported by {@link TimeComponents#parseTime(String)} separated
149      * by the standard time separator 'T', or date components only (in which case a 00:00:00 hour is
150      * implied). Typical examples are 2000-01-01T12:00:00Z or 1976W186T210000.
151      * </p>
152      * @param string string to parse
153      * @return a parsed date/time
154      * @exception IllegalArgumentException if string cannot be parsed
155      */
156     public static DateTimeComponents parseDateTime(final String string) {
157 
158         // is there a time ?
159         final int tIndex = string.indexOf('T');
160         if (tIndex > 0) {
161             return new DateTimeComponents(DateComponents.parseDate(string.substring(0, tIndex)),
162                                           TimeComponents.parseTime(string.substring(tIndex + 1)));
163         }
164 
165         return new DateTimeComponents(DateComponents.parseDate(string), TimeComponents.H00);
166 
167     }
168 
169     /** Compute the seconds offset between two instances.
170      * @param dateTime dateTime to subtract from the instance
171      * @return offset in seconds between the two instants
172      * (positive if the instance is posterior to the argument)
173      * @see #DateTimeComponents(DateTimeComponents, double)
174      */
175     public double offsetFrom(final DateTimeComponents dateTime) {
176         final int dateOffset = date.getJ2000Day() - dateTime.date.getJ2000Day();
177         final double timeOffset = time.getSecondsInUTCDay() - dateTime.time.getSecondsInUTCDay();
178         return Constants.JULIAN_DAY * dateOffset + timeOffset;
179     }
180 
181     /** Get the date component.
182      * @return date component
183      */
184     public DateComponents getDate() {
185         return date;
186     }
187 
188     /** Get the time component.
189      * @return time component
190      */
191     public TimeComponents getTime() {
192         return time;
193     }
194 
195     /** {@inheritDoc} */
196     public int compareTo(final DateTimeComponents other) {
197         final int dateComparison = date.compareTo(other.date);
198         if (dateComparison < 0) {
199             return -1;
200         } else if (dateComparison > 0) {
201             return 1;
202         }
203         return time.compareTo(other.time);
204     }
205 
206     /** {@inheritDoc} */
207     public boolean equals(final Object other) {
208         try {
209             final DateTimeComponents otherDateTime = (DateTimeComponents) other;
210             return (otherDateTime != null) &&
211                    date.equals(otherDateTime.date) && time.equals(otherDateTime.time);
212         } catch (ClassCastException cce) {
213             return false;
214         }
215     }
216 
217     /** {@inheritDoc} */
218     public int hashCode() {
219         return (date.hashCode() << 16) ^ time.hashCode();
220     }
221 
222     /** Return a string representation of this pair.
223      * <p>The format used is ISO8601.</p>
224      * @return string representation of this pair
225      */
226     public String toString() {
227         return toString(60);
228     }
229 
230     /** Return a string representation of this pair.
231      * <p>The format used is ISO8601.</p>
232      * @param minuteDuration 60 or 61 depending on the date being
233      * close to a leap second introduction
234      * @return string representation of this pair
235      */
236     public String toString(final int minuteDuration) {
237         double second = time.getSecond();
238         final double wrap = minuteDuration - 0.0005;
239         if (second >= wrap) {
240             // we should wrap around next millisecond
241             int minute = time.getMinute();
242             int hour   = time.getHour();
243             int j2000  = date.getJ2000Day();
244             second = 0;
245             ++minute;
246             if (minute > 59) {
247                 minute = 0;
248                 ++hour;
249                 if (hour > 23) {
250                     hour = 0;
251                     ++j2000;
252                 }
253             }
254             return new DateComponents(j2000).toString() + 'T' + new TimeComponents(hour, minute, second).toString();
255         }
256         return date.toString() + 'T' + time.toString();
257     }
258 
259 }
260