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 }