1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (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.IOException;
20  import java.io.Serializable;
21  import java.util.regex.Matcher;
22  import java.util.regex.Pattern;
23  
24  import org.orekit.errors.OrekitIllegalArgumentException;
25  import org.orekit.errors.OrekitInternalError;
26  import org.orekit.errors.OrekitMessages;
27  import org.orekit.utils.formatting.FastLongFormatter;
28  
29  /** Class representing a date broken up as year, month and day components.
30   * <p>This class uses the astronomical convention for calendars,
31   * which is also the convention used by <code>java.util.Date</code>:
32   * a year zero is present between years -1 and +1, and 10 days are
33   * missing in 1582. The calendar used around these special dates are:</p>
34   * <ul>
35   *   <li>up to 0000-12-31 : proleptic julian calendar</li>
36   *   <li>from 0001-01-01 to 1582-10-04: julian calendar</li>
37   *   <li>from 1582-10-15: gregorian calendar</li>
38   * </ul>
39   * <p>Instances of this class are guaranteed to be immutable.</p>
40   * @see TimeComponents
41   * @see DateTimeComponents
42   * @author Luc Maisonobe
43   */
44  public class DateComponents implements Serializable, Comparable<DateComponents> {
45  
46      /** Reference epoch for julian dates: -4712-01-01.
47       * <p>Both <code>java.util.Date</code> and {@link DateComponents} classes
48       * follow the astronomical conventions and consider a year 0 between
49       * years -1 and +1, hence this reference date lies in year -4712 and not
50       * in year -4713 as can be seen in other documents or programs that obey
51       * a different convention (for example the <code>convcal</code> utility).</p>
52       */
53      public static final DateComponents JULIAN_EPOCH;
54  
55      /** Reference epoch for modified julian dates: 1858-11-17. */
56      public static final DateComponents MODIFIED_JULIAN_EPOCH;
57  
58      /** Reference epoch for 1950 dates: 1950-01-01. */
59      public static final DateComponents FIFTIES_EPOCH;
60  
61      /** Reference epoch for CCSDS Time Code Format (CCSDS 301.0-B-4): 1958-01-01. */
62      public static final DateComponents CCSDS_EPOCH;
63  
64      /** Reference epoch for Galileo System Time: 1999-08-22. */
65      public static final DateComponents GALILEO_EPOCH;
66  
67      /** Reference epoch for GPS weeks: 1980-01-06. */
68      public static final DateComponents GPS_EPOCH;
69  
70      /** Reference epoch for QZSS weeks: 1980-01-06. */
71      public static final DateComponents QZSS_EPOCH;
72  
73      /** Reference epoch for NavIC weeks: 1999-08-22. */
74      public static final DateComponents NAVIC_EPOCH;
75  
76      /** Reference epoch for BeiDou weeks: 2006-01-01. */
77      public static final DateComponents BEIDOU_EPOCH;
78  
79      /** Reference epoch for GLONASS four-year interval number: 1996-01-01. */
80      public static final DateComponents GLONASS_EPOCH;
81  
82      /** J2000.0 Reference epoch: 2000-01-01. */
83      public static final DateComponents J2000_EPOCH;
84  
85      /** Java Reference epoch: 1970-01-01. */
86      public static final DateComponents JAVA_EPOCH;
87  
88      /** Maximum supported date.
89       * <p>
90       * This is date 5881610-07-11 which corresponds to {@code Integer.MAX_VALUE}
91       * days after {@link #J2000_EPOCH}.
92       * </p>
93       * @since 9.0
94       */
95      public static final DateComponents MAX_EPOCH;
96  
97      /** Maximum supported date.
98       * <p>
99       * This is date -5877490-03-03, which corresponds to {@code Integer.MIN_VALUE}
100      * days before {@link #J2000_EPOCH}.
101      * </p>
102      * @since 9.0
103      */
104     public static final DateComponents MIN_EPOCH;
105 
106     /** Offset between julian day epoch and modified julian day epoch. */
107     public static final double JD_TO_MJD = 2400000.5;
108 
109     /** Format for one 4 digits integer field.
110      * @since 13.0.3
111      */
112     private static final FastLongFormatter PADDED_FOUR_DIGITS_INTEGER = new FastLongFormatter(4, true);
113 
114     /** Format for one 2 digits integer field.
115      * @since 13.0.3
116      */
117     private static final FastLongFormatter PADDED_TWO_DIGITS_INTEGER = new FastLongFormatter(2, true);
118 
119     /** Serializable UID. */
120     private static final long serialVersionUID = -2462694707837970938L;
121 
122     /** Factory for proleptic julian calendar (up to 0000-12-31). */
123     private static final YearFactory PROLEPTIC_JULIAN_FACTORY = new ProlepticJulianFactory();
124 
125     /** Factory for julian calendar (from 0001-01-01 to 1582-10-04). */
126     private static final YearFactory JULIAN_FACTORY           = new JulianFactory();
127 
128     /** Factory for gregorian calendar (from 1582-10-15). */
129     private static final YearFactory GREGORIAN_FACTORY        = new GregorianFactory();
130 
131     /** Factory for leap years. */
132     private static final MonthDayFactory LEAP_YEAR_FACTORY    = new LeapYearFactory();
133 
134     /** Factory for non-leap years. */
135     private static final MonthDayFactory COMMON_YEAR_FACTORY  = new CommonYearFactory();
136 
137     /** Offset between J2000 epoch and modified julian day epoch. */
138     private static final int MJD_TO_J2000 = 51544;
139 
140     /** Partial pattern for date start.
141      * @since 13.1.6
142      */
143     private static final String START = "^";
144 
145     /** Partial pattern for date end.
146      * @since 13.1.6
147      */
148     private static final String END = "$";
149 
150     /** Partial pattern for signed number with 4 digits.
151      * @since 13.1.6
152      */
153     private static final String SIGNED_4 = "(-?\\d\\d\\d\\d)";
154 
155     /** Partial pattern for signed number with any number of digits.
156      * @since 13.1.6
157      */
158     private static final String SIGNED_ANY = "(-?\\d+)";
159 
160     /** Partial pattern for positive number with 3 digits.
161      * @since 13.1.6
162      */
163     private static final String POSITIVE_3 = "(\\d\\d\\d)";
164 
165     /** Partial pattern for positive number with 2 digits.
166      * @since 13.1.6
167      */
168     private static final String POSITIVE_2 = "(\\d\\d)";
169 
170     /** Partial pattern for positive number with 1 digit.
171      * @since 13.1.6
172      */
173     private static final String POSITIVE_1 = "(\\d)";
174 
175     /** Partial pattern for dash.
176      * @since 13.1.6
177      */
178     private static final String DASH = "-";
179 
180     /** Partial pattern for week marker.
181      * @since 13.1.6
182      */
183     private static final String WEEK = "W";
184 
185     /** Pattern for basic and extended calendar date with dashes (allows any number of digits in years).
186      * @since 13.1.6
187      */
188     private static final Pattern CALENDAR_WITH_DASHES =
189         Pattern.compile(START + SIGNED_ANY + DASH + POSITIVE_2 + DASH + POSITIVE_2 + END);
190 
191     /** Pattern for basic and extended calendar date without dashes (mandates 4 digits years).
192      * @since 13.1.6
193      */
194     private static final Pattern CALENDAR_WITHOUT_DASHES =
195         Pattern.compile(START + SIGNED_4 + POSITIVE_2 + POSITIVE_2 + END);
196 
197     /** Pattern for ordinal date with dashes (allows any number of digits in years).
198      * @since 13.1.6
199      */
200     private static final Pattern ORDINAL_WITH_DASHES =
201         Pattern.compile(START + SIGNED_ANY + DASH + POSITIVE_3 + END);
202 
203     /** Pattern for ordinal date without dashes (mandates 4 digits years).
204      * @since 13.1.6
205      */
206     private static final Pattern ORDINAL_WITHOUT_DASHES =
207         Pattern.compile(START + SIGNED_4 + POSITIVE_3 + END);
208 
209     /** Pattern for extended format week date wit dashes (allows any number of digits in years).
210      * @since 13.1.6
211      */
212     private static final Pattern WEEK_WITH_DASHES =
213         Pattern.compile(START + SIGNED_ANY + DASH + WEEK + POSITIVE_2 + DASH + POSITIVE_1 + END);
214 
215     /** Pattern for extended format week date without dashes (mandates 4 digits years). */
216     private static final Pattern WEEK_WITHOUT_DASHES =
217         Pattern.compile(START + SIGNED_4 + WEEK + POSITIVE_2 + POSITIVE_1 + END);
218 
219     static {
220         // this static statement makes sure the reference epoch are initialized
221         // once AFTER the various factories have been set up
222         JULIAN_EPOCH          = new DateComponents(-4712,  1,  1);
223         MODIFIED_JULIAN_EPOCH = new DateComponents(1858, 11, 17);
224         FIFTIES_EPOCH         = new DateComponents(1950, 1, 1);
225         CCSDS_EPOCH           = new DateComponents(1958, 1, 1);
226         GALILEO_EPOCH         = new DateComponents(1999, 8, 22);
227         GPS_EPOCH             = new DateComponents(1980, 1, 6);
228         QZSS_EPOCH            = new DateComponents(1980, 1, 6);
229         NAVIC_EPOCH           = new DateComponents(1999, 8, 22);
230         BEIDOU_EPOCH          = new DateComponents(2006, 1, 1);
231         GLONASS_EPOCH         = new DateComponents(1996, 1, 1);
232         J2000_EPOCH           = new DateComponents(2000, 1, 1);
233         JAVA_EPOCH            = new DateComponents(1970, 1, 1);
234         MAX_EPOCH             = new DateComponents(Integer.MAX_VALUE);
235         MIN_EPOCH             = new DateComponents(Integer.MIN_VALUE);
236     }
237 
238     /** Year number. */
239     private final int year;
240 
241     /** Month number. */
242     private final int month;
243 
244     /** Day number. */
245     private final int day;
246 
247     /** Build a date from its components.
248      * @param year year number (may be 0 or negative for BC years)
249      * @param month month number from 1 to 12
250      * @param day day number from 1 to 31
251      * @exception IllegalArgumentException if inconsistent arguments
252      * are given (parameters out of range, february 29 for non-leap years,
253      * dates during the gregorian leap in 1582 ...)
254      */
255     public DateComponents(final int year, final int month, final int day)
256         throws IllegalArgumentException {
257 
258         // very rough range check
259         // (just to avoid ArrayOutOfboundException in MonthDayFactory later)
260         if (month < 1 || month > 12) {
261             throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_MONTH, month);
262         }
263 
264         // start by trusting the parameters
265         this.year  = year;
266         this.month = month;
267         this.day   = day;
268 
269         // build a check date from the J2000 day
270         final DateComponents check = new DateComponents(getJ2000Day());
271 
272         // check the parameters for mismatch
273         // (i.e. invalid date components, like 29 february on non-leap years)
274         if (year != check.year || month != check.month || day != check.day) {
275             throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_YEAR_MONTH_DAY,
276                                                       year, month, day);
277         }
278 
279     }
280 
281     /** Build a date from its components.
282      * @param year year number (may be 0 or negative for BC years)
283      * @param month month enumerate
284      * @param day day number from 1 to 31
285      * @exception IllegalArgumentException if inconsistent arguments
286      * are given (parameters out of range, february 29 for non-leap years,
287      * dates during the gregorian leap in 1582 ...)
288      */
289     public DateComponents(final int year, final Month month, final int day)
290         throws IllegalArgumentException {
291         this(year, month.getNumber(), day);
292     }
293 
294     /** Build a date from a year and day number.
295      * @param year year number (may be 0 or negative for BC years)
296      * @param dayNumber day number in the year from 1 to 366
297      * @exception IllegalArgumentException if dayNumber is out of range
298      * with respect to year
299      */
300     public DateComponents(final int year, final int dayNumber)
301         throws IllegalArgumentException {
302         this(J2000_EPOCH, new DateComponents(year - 1, 12, 31).getJ2000Day() + dayNumber);
303         if (dayNumber != getDayOfYear()) {
304             throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_DAY_NUMBER_IN_YEAR,
305                                                      dayNumber, year);
306         }
307     }
308 
309     /** Build a date from its offset with respect to a {@link #J2000_EPOCH}.
310      * @param offset offset with respect to a {@link #J2000_EPOCH}
311      * @see #getJ2000Day()
312      */
313     public DateComponents(final int offset) {
314 
315         // we follow the astronomical convention for calendars:
316         // we consider a year zero and 10 days are missing in 1582
317         // from 1582-10-15: gregorian calendar
318         // from 0001-01-01 to 1582-10-04: julian calendar
319         // up to 0000-12-31 : proleptic julian calendar
320         YearFactory yFactory = GREGORIAN_FACTORY;
321         if (offset < -152384) {
322             if (offset > -730122) {
323                 yFactory = JULIAN_FACTORY;
324             } else {
325                 yFactory = PROLEPTIC_JULIAN_FACTORY;
326             }
327         }
328         year = yFactory.getYear(offset);
329         final int dayInYear = offset - yFactory.getLastJ2000DayOfYear(year - 1);
330 
331         // handle month/day according to the year being a common or leap year
332         final MonthDayFactory mdFactory =
333             yFactory.isLeap(year) ? LEAP_YEAR_FACTORY : COMMON_YEAR_FACTORY;
334         month = mdFactory.getMonth(dayInYear);
335         day   = mdFactory.getDay(dayInYear, month);
336 
337     }
338 
339     /** Build a date from its offset with respect to a reference epoch.
340      * <p>This constructor is mainly useful to build a date from a modified
341      * julian day (using {@link #MODIFIED_JULIAN_EPOCH}) or a GPS week number
342      * (using {@link #GPS_EPOCH}).</p>
343      * @param epoch reference epoch
344      * @param offset offset with respect to a reference epoch
345      * @see #DateComponents(int)
346      * @see #getMJD()
347      */
348     public DateComponents(final DateComponents epoch, final int offset) {
349         this(epoch.getJ2000Day() + offset);
350     }
351 
352     /** Build a date from week components.
353      * <p>The calendar week number is a number between 1 and 52 or 53 depending
354      * on the year. Week 1 is defined by ISO as the one that includes the first
355      * Thursday of a year. Week 1 may therefore start the previous year and week
356      * 52 or 53 may end in the next year. As an example calendar date 1995-01-01
357      * corresponds to week date 1994-W52-7 (i.e. Sunday in the last week of 1994
358      * is in fact the first day of year 1995). This date would beAnother example is calendar date
359      * 1996-12-31 which corresponds to week date 1997-W01-2 (i.e. Tuesday in the
360      * first week of 1997 is in fact the last day of year 1996).</p>
361      * @param wYear year associated to week numbering
362      * @param week week number in year, from 1 to 52 or 53
363      * @param dayOfWeek day of week, from 1 (Monday) to 7 (Sunday)
364      * @return a builded date
365      * @exception IllegalArgumentException if inconsistent arguments
366      * are given (parameters out of range, week 53 on a 52 weeks year ...)
367      */
368     public static DateComponents createFromWeekComponents(final int wYear, final int week, final int dayOfWeek)
369         throws IllegalArgumentException {
370 
371         final DateComponents firstWeekMonday = new DateComponents(getFirstWeekMonday(wYear));
372         final DateComponents d = new DateComponents(firstWeekMonday, 7 * week + dayOfWeek - 8);
373 
374         // check the parameters for invalid date components
375         if (week != d.getCalendarWeek() || dayOfWeek != d.getDayOfWeek()) {
376             throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_WEEK_DATE,
377                                                      wYear, week, dayOfWeek);
378         }
379 
380         return d;
381 
382     }
383 
384     /** Parse a string in ISO-8601 format to build a date.
385      * <p>The supported formats are:
386      * <ul>
387      *   <li>basic format calendar date: YYYYMMDD</li>
388      *   <li>extended format calendar date: YYYY-MM-DD</li>
389      *   <li>basic format ordinal date: YYYYDDD</li>
390      *   <li>extended format ordinal date: YYYY-DDD</li>
391      *   <li>basic format week date: YYYYWwwD</li>
392      *   <li>extended format week date: YYYY-Www-D</li>
393      * </ul>
394      *
395      * <p> As shown by the list above, only the complete representations defined in section 4.1
396      * of ISO-8601 standard are supported, neither expended representations nor representations
397      * with reduced accuracy are supported.
398      *
399      * <p>
400      * Parsing a single integer as a julian day is <em>not</em> supported as it may be ambiguous
401      * with either the basic format calendar date or the basic format ordinal date depending
402      * on the number of digits.
403      * </p>
404      * @param string string to parse
405      * @return a parsed date
406      * @exception IllegalArgumentException if string cannot be parsed
407      */
408     public static  DateComponents parseDate(final String string) {
409 
410         // is the date a calendar date ?
411         final Matcher calendarMatcherWith = CALENDAR_WITH_DASHES.matcher(string);
412         if (calendarMatcherWith.matches()) {
413             return new DateComponents(Integer.parseInt(calendarMatcherWith.group(1)),
414                                       Integer.parseInt(calendarMatcherWith.group(2)),
415                                       Integer.parseInt(calendarMatcherWith.group(3)));
416         }
417         final Matcher calendarMatcherWithout = CALENDAR_WITHOUT_DASHES.matcher(string);
418         if (calendarMatcherWithout.matches()) {
419             return new DateComponents(Integer.parseInt(calendarMatcherWithout.group(1)),
420                                       Integer.parseInt(calendarMatcherWithout.group(2)),
421                                       Integer.parseInt(calendarMatcherWithout.group(3)));
422         }
423 
424         // is the date an ordinal date ?
425         final Matcher ordinalMatcherWith = ORDINAL_WITH_DASHES.matcher(string);
426         if (ordinalMatcherWith.matches()) {
427             return new DateComponents(Integer.parseInt(ordinalMatcherWith.group(1)),
428                                       Integer.parseInt(ordinalMatcherWith.group(2)));
429         }
430         final Matcher ordinalMatcherWithout = ORDINAL_WITHOUT_DASHES.matcher(string);
431         if (ordinalMatcherWithout.matches()) {
432             return new DateComponents(Integer.parseInt(ordinalMatcherWithout.group(1)),
433                                       Integer.parseInt(ordinalMatcherWithout.group(2)));
434         }
435 
436         // is the date a week date ?
437         final Matcher weekMatcherWith = WEEK_WITH_DASHES.matcher(string);
438         if (weekMatcherWith.matches()) {
439             return createFromWeekComponents(Integer.parseInt(weekMatcherWith.group(1)),
440                                             Integer.parseInt(weekMatcherWith.group(2)),
441                                             Integer.parseInt(weekMatcherWith.group(3)));
442         }
443         final Matcher weekMatcherWithout = WEEK_WITHOUT_DASHES.matcher(string);
444         if (weekMatcherWithout.matches()) {
445             return createFromWeekComponents(Integer.parseInt(weekMatcherWithout.group(1)),
446                                             Integer.parseInt(weekMatcherWithout.group(2)),
447                                             Integer.parseInt(weekMatcherWithout.group(3)));
448         }
449 
450         throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_DATE, string);
451 
452     }
453 
454     /** Get the year number.
455      * @return year number (may be 0 or negative for BC years)
456      */
457     public int getYear() {
458         return year;
459     }
460 
461     /** Get the month.
462      * @return month number from 1 to 12
463      */
464     public int getMonth() {
465         return month;
466     }
467 
468     /** Get the month as an enumerate.
469      * @return month as an enumerate
470      */
471     public Month getMonthEnum() {
472         return Month.getMonth(month);
473     }
474 
475     /** Get the day.
476      * @return day number from 1 to 31
477      */
478     public int getDay() {
479         return day;
480     }
481 
482     /** Get the day number with respect to J2000 epoch.
483      * @return day number with respect to J2000 epoch
484      */
485     public int getJ2000Day() {
486         YearFactory yFactory = GREGORIAN_FACTORY;
487         if (year < 1583) {
488             if (year < 1) {
489                 yFactory = PROLEPTIC_JULIAN_FACTORY;
490             } else if (year < 1582 || month < 10 || month < 11 && day < 5) {
491                 yFactory = JULIAN_FACTORY;
492             }
493         }
494         final MonthDayFactory mdFactory =
495             yFactory.isLeap(year) ? LEAP_YEAR_FACTORY : COMMON_YEAR_FACTORY;
496         return yFactory.getLastJ2000DayOfYear(year - 1) +
497                mdFactory.getDayInYear(month, day);
498     }
499 
500     /** Get the modified julian day.
501      * @return modified julian day
502      */
503     public int getMJD() {
504         return MJD_TO_J2000 + getJ2000Day();
505     }
506 
507     /** Get the calendar week number.
508      * <p>The calendar week number is a number between 1 and 52 or 53 depending
509      * on the year. Week 1 is defined by ISO as the one that includes the first
510      * Thursday of a year. Week 1 may therefore start the previous year and week
511      * 52 or 53 may end in the next year. As an example calendar date 1995-01-01
512      * corresponds to week date 1994-W52-7 (i.e. Sunday in the last week of 1994
513      * is in fact the first day of year 1995). Another example is calendar date
514      * 1996-12-31 which corresponds to week date 1997-W01-2 (i.e. Tuesday in the
515      * first week of 1997 is in fact the last day of year 1996).</p>
516      * @return calendar week number
517      */
518     public int getCalendarWeek() {
519         final int firstWeekMonday = getFirstWeekMonday(year);
520         int daysSincefirstMonday = getJ2000Day() - firstWeekMonday;
521         if (daysSincefirstMonday < 0) {
522             // we are still in a week from previous year
523             daysSincefirstMonday += firstWeekMonday - getFirstWeekMonday(year - 1);
524         } else if (daysSincefirstMonday > 363) {
525             // up to three days at end of year may belong to first week of next year
526             // (by chance, there is no need for a specific check in year 1582 ...)
527             final int weekYearLength = getFirstWeekMonday(year + 1) - firstWeekMonday;
528             if (daysSincefirstMonday >= weekYearLength) {
529                 daysSincefirstMonday -= weekYearLength;
530             }
531         }
532         return 1 + daysSincefirstMonday / 7;
533     }
534 
535     /** Get the monday of a year first week.
536      * @param year year to consider
537      * @return day of the monday of the first weak of year
538      */
539     private static int getFirstWeekMonday(final int year) {
540         final int yearFirst = new DateComponents(year, 1, 1).getJ2000Day();
541         final int offsetToMonday = 4 - (yearFirst + 2) % 7;
542         return yearFirst + offsetToMonday + ((offsetToMonday > 3) ? -7 : 0);
543     }
544 
545     /** Get the day of week.
546      * <p>Day of week is a number between 1 (Monday) and 7 (Sunday).</p>
547      * @return day of week
548      */
549     public int getDayOfWeek() {
550         final int dow = (getJ2000Day() + 6) % 7; // result is between -6 and +6
551         return (dow < 1) ? (dow + 7) : dow;
552     }
553 
554     /** Get the day number in year.
555      * <p>Day number in year is between 1 (January 1st) and either 365 or
556      * 366 inclusive depending on year.</p>
557      * @return day number in year
558      */
559     public int getDayOfYear() {
560         return getJ2000Day() - new DateComponents(year - 1, 12, 31).getJ2000Day();
561     }
562 
563     /** Get a string representation (ISO-8601) of the date.
564      * @return string representation of the date.
565      */
566     public String toString() {
567         try {
568             final StringBuilder builder = new StringBuilder();
569             PADDED_FOUR_DIGITS_INTEGER.appendTo(builder, year);
570             builder.append('-');
571             PADDED_TWO_DIGITS_INTEGER.appendTo(builder, month);
572             builder.append('-');
573             PADDED_TWO_DIGITS_INTEGER.appendTo(builder, day);
574             return builder.toString();
575         } catch (IOException ioe) {
576             // this should never happen
577             throw new OrekitInternalError(ioe);
578         }
579     }
580 
581     /** {@inheritDoc} */
582     public int compareTo(final DateComponents other) {
583         final int j2000Day = getJ2000Day();
584         final int otherJ2000Day = other.getJ2000Day();
585         if (j2000Day < otherJ2000Day) {
586             return -1;
587         } else if (j2000Day > otherJ2000Day) {
588             return 1;
589         }
590         return 0;
591     }
592 
593     /** {@inheritDoc} */
594     public boolean equals(final Object other) {
595         try {
596             final DateComponents otherDate = (DateComponents) other;
597             return otherDate != null && year == otherDate.year &&
598                    month == otherDate.month && day == otherDate.day;
599         } catch (ClassCastException cce) {
600             return false;
601         }
602     }
603 
604     /** {@inheritDoc} */
605     public int hashCode() {
606         return (year << 16) ^ (month << 8) ^ day;
607     }
608 
609     /** Interface for dealing with years sequences according to some calendar. */
610     private interface YearFactory {
611 
612         /** Get the year number for a given day number with respect to J2000 epoch.
613          * @param j2000Day day number with respect to J2000 epoch
614          * @return year number
615          */
616         int getYear(int j2000Day);
617 
618         /** Get the day number with respect to J2000 epoch for new year's Eve.
619          * @param year year number
620          * @return day number with respect to J2000 epoch for new year's Eve
621          */
622         int getLastJ2000DayOfYear(int year);
623 
624         /** Check if a year is a leap or common year.
625          * @param year year number
626          * @return true if year is a leap year
627          */
628         boolean isLeap(int year);
629 
630     }
631 
632     /** Class providing a years sequence compliant with the proleptic Julian calendar. */
633     private static class ProlepticJulianFactory implements YearFactory {
634 
635         /** {@inheritDoc} */
636         public int getYear(final int j2000Day) {
637             return  (int) -((-4L * j2000Day - 2920488L) / 1461L);
638         }
639 
640         /** {@inheritDoc} */
641         public int getLastJ2000DayOfYear(final int year) {
642             return 365 * year + (year + 1) / 4 - 730123;
643         }
644 
645         /** {@inheritDoc} */
646         public boolean isLeap(final int year) {
647             return (year % 4) == 0;
648         }
649 
650     }
651 
652     /** Class providing a years sequence compliant with the Julian calendar. */
653     private static class JulianFactory implements YearFactory {
654 
655         /** {@inheritDoc} */
656         public int getYear(final int j2000Day) {
657             return  (int) ((4L * j2000Day + 2921948L) / 1461L);
658         }
659 
660         /** {@inheritDoc} */
661         public int getLastJ2000DayOfYear(final int year) {
662             return 365 * year + year / 4 - 730122;
663         }
664 
665         /** {@inheritDoc} */
666         public boolean isLeap(final int year) {
667             return (year % 4) == 0;
668         }
669 
670     }
671 
672     /** Class providing a years sequence compliant with the Gregorian calendar. */
673     private static class GregorianFactory implements YearFactory {
674 
675         /** {@inheritDoc} */
676         public int getYear(final int j2000Day) {
677 
678             // year estimate
679             int year = (int) ((400L * j2000Day + 292194288L) / 146097L);
680 
681             // the previous estimate is one unit too high in some rare cases
682             // (240 days in the 400 years gregorian cycle, about 0.16%)
683             if (j2000Day <= getLastJ2000DayOfYear(year - 1)) {
684                 --year;
685             }
686 
687             // exact year
688             return year;
689 
690         }
691 
692         /** {@inheritDoc} */
693         public int getLastJ2000DayOfYear(final int year) {
694             return 365 * year + year / 4 - year / 100 + year / 400 - 730120;
695         }
696 
697         /** {@inheritDoc} */
698         public boolean isLeap(final int year) {
699             return (year % 4) == 0 && ((year % 400) == 0 || (year % 100) != 0);
700         }
701 
702     }
703 
704     /** Interface for dealing with months sequences according to leap/common years. */
705     private interface MonthDayFactory {
706 
707         /** Get the month number for a given day number within year.
708          * @param dayInYear day number within year
709          * @return month number
710          */
711         int getMonth(int dayInYear);
712 
713         /** Get the day number for given month and day number within year.
714          * @param dayInYear day number within year
715          * @param month month number
716          * @return day number
717          */
718         int getDay(int dayInYear, int month);
719 
720         /** Get the day number within year for given month and day numbers.
721          * @param month month number
722          * @param day day number
723          * @return day number within year
724          */
725         int getDayInYear(int month, int day);
726 
727     }
728 
729     /** Class providing the months sequence for leap years. */
730     private static class LeapYearFactory implements MonthDayFactory {
731 
732         /** Months succession definition. */
733         private static final int[] PREVIOUS_MONTH_END_DAY = {
734             0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335
735         };
736 
737         /** {@inheritDoc} */
738         public int getMonth(final int dayInYear) {
739             return (dayInYear < 32) ? 1 : (10 * dayInYear + 313) / 306;
740         }
741 
742         /** {@inheritDoc} */
743         public int getDay(final int dayInYear, final int month) {
744             return dayInYear - PREVIOUS_MONTH_END_DAY[month];
745         }
746 
747         /** {@inheritDoc} */
748         public int getDayInYear(final int month, final int day) {
749             return day + PREVIOUS_MONTH_END_DAY[month];
750         }
751 
752     }
753 
754     /** Class providing the months sequence for common years. */
755     private static class CommonYearFactory implements MonthDayFactory {
756 
757         /** Months succession definition. */
758         private static final int[] PREVIOUS_MONTH_END_DAY = {
759             0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
760         };
761 
762         /** {@inheritDoc} */
763         public int getMonth(final int dayInYear) {
764             return (dayInYear < 32) ? 1 : (10 * dayInYear + 323) / 306;
765         }
766 
767         /** {@inheritDoc} */
768         public int getDay(final int dayInYear, final int month) {
769             return dayInYear - PREVIOUS_MONTH_END_DAY[month];
770         }
771 
772         /** {@inheritDoc} */
773         public int getDayInYear(final int month, final int day) {
774             return day + PREVIOUS_MONTH_END_DAY[month];
775         }
776 
777     }
778 
779 }