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