1   /* Copyright 2002-2024 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.Serializable;
20  import java.text.DecimalFormat;
21  import java.text.DecimalFormatSymbols;
22  import java.util.Locale;
23  import java.util.regex.Matcher;
24  import java.util.regex.Pattern;
25  
26  import org.orekit.errors.OrekitIllegalArgumentException;
27  import org.orekit.errors.OrekitMessages;
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 IRNSS weeks: 1999-08-22. */
74      public static final DateComponents IRNSS_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     /** Serializable UID. */
107     private static final long serialVersionUID = -2462694707837970938L;
108 
109     /** Factory for proleptic julian calendar (up to 0000-12-31). */
110     private static final YearFactory PROLEPTIC_JULIAN_FACTORY = new ProlepticJulianFactory();
111 
112     /** Factory for julian calendar (from 0001-01-01 to 1582-10-04). */
113     private static final YearFactory JULIAN_FACTORY           = new JulianFactory();
114 
115     /** Factory for gregorian calendar (from 1582-10-15). */
116     private static final YearFactory GREGORIAN_FACTORY        = new GregorianFactory();
117 
118     /** Factory for leap years. */
119     private static final MonthDayFactory LEAP_YEAR_FACTORY    = new LeapYearFactory();
120 
121     /** Factory for non-leap years. */
122     private static final MonthDayFactory COMMON_YEAR_FACTORY  = new CommonYearFactory();
123 
124     /** Formatting symbols used in {@link #toString()}. */
125     private static final DecimalFormatSymbols US_SYMBOLS = new DecimalFormatSymbols(Locale.US);
126 
127     /** Format for years. */
128     private static final DecimalFormat FOUR_DIGITS = new DecimalFormat("0000", US_SYMBOLS);
129 
130     /** Format for months and days. */
131     private static final DecimalFormat TWO_DIGITS  = new DecimalFormat("00", US_SYMBOLS);
132 
133     /** Offset between J2000 epoch and modified julian day epoch. */
134     private static final int MJD_TO_J2000 = 51544;
135 
136     /** Basic and extended format calendar date. */
137     private static final Pattern CALENDAR_FORMAT = Pattern.compile("^(-?\\d\\d\\d\\d)-?(\\d\\d)-?(\\d\\d)$");
138 
139     /** Basic and extended format ordinal date. */
140     private static final Pattern ORDINAL_FORMAT = Pattern.compile("^(-?\\d\\d\\d\\d)-?(\\d\\d\\d)$");
141 
142     /** Basic and extended format week date. */
143     private static final Pattern WEEK_FORMAT = Pattern.compile("^(-?\\d\\d\\d\\d)-?W(\\d\\d)-?(\\d)$");
144 
145     static {
146         // this static statement makes sure the reference epoch are initialized
147         // once AFTER the various factories have been set up
148         JULIAN_EPOCH          = new DateComponents(-4712,  1,  1);
149         MODIFIED_JULIAN_EPOCH = new DateComponents(1858, 11, 17);
150         FIFTIES_EPOCH         = new DateComponents(1950, 1, 1);
151         CCSDS_EPOCH           = new DateComponents(1958, 1, 1);
152         GALILEO_EPOCH         = new DateComponents(1999, 8, 22);
153         GPS_EPOCH             = new DateComponents(1980, 1, 6);
154         QZSS_EPOCH            = new DateComponents(1980, 1, 6);
155         IRNSS_EPOCH           = new DateComponents(1999, 8, 22);
156         BEIDOU_EPOCH          = new DateComponents(2006, 1, 1);
157         GLONASS_EPOCH         = new DateComponents(1996, 1, 1);
158         J2000_EPOCH           = new DateComponents(2000, 1, 1);
159         JAVA_EPOCH            = new DateComponents(1970, 1, 1);
160         MAX_EPOCH             = new DateComponents(Integer.MAX_VALUE);
161         MIN_EPOCH             = new DateComponents(Integer.MIN_VALUE);
162     }
163 
164     /** Year number. */
165     private final int year;
166 
167     /** Month number. */
168     private final int month;
169 
170     /** Day number. */
171     private final int day;
172 
173     /** Build a date from its components.
174      * @param year year number (may be 0 or negative for BC years)
175      * @param month month number from 1 to 12
176      * @param day day number from 1 to 31
177      * @exception IllegalArgumentException if inconsistent arguments
178      * are given (parameters out of range, february 29 for non-leap years,
179      * dates during the gregorian leap in 1582 ...)
180      */
181     public DateComponents(final int year, final int month, final int day)
182         throws IllegalArgumentException {
183 
184         // very rough range check
185         // (just to avoid ArrayOutOfboundException in MonthDayFactory later)
186         if (month < 1 || month > 12) {
187             throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_MONTH, month);
188         }
189 
190         // start by trusting the parameters
191         this.year  = year;
192         this.month = month;
193         this.day   = day;
194 
195         // build a check date from the J2000 day
196         final DateComponents check = new DateComponents(getJ2000Day());
197 
198         // check the parameters for mismatch
199         // (i.e. invalid date components, like 29 february on non-leap years)
200         if (year != check.year || month != check.month || day != check.day) {
201             throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_YEAR_MONTH_DAY,
202                                                       year, month, day);
203         }
204 
205     }
206 
207     /** Build a date from its components.
208      * @param year year number (may be 0 or negative for BC years)
209      * @param month month enumerate
210      * @param day day number from 1 to 31
211      * @exception IllegalArgumentException if inconsistent arguments
212      * are given (parameters out of range, february 29 for non-leap years,
213      * dates during the gregorian leap in 1582 ...)
214      */
215     public DateComponents(final int year, final Month month, final int day)
216         throws IllegalArgumentException {
217         this(year, month.getNumber(), day);
218     }
219 
220     /** Build a date from a year and day number.
221      * @param year year number (may be 0 or negative for BC years)
222      * @param dayNumber day number in the year from 1 to 366
223      * @exception IllegalArgumentException if dayNumber is out of range
224      * with respect to year
225      */
226     public DateComponents(final int year, final int dayNumber)
227         throws IllegalArgumentException {
228         this(J2000_EPOCH, new DateComponents(year - 1, 12, 31).getJ2000Day() + dayNumber);
229         if (dayNumber != getDayOfYear()) {
230             throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_DAY_NUMBER_IN_YEAR,
231                                                      dayNumber, year);
232         }
233     }
234 
235     /** Build a date from its offset with respect to a {@link #J2000_EPOCH}.
236      * @param offset offset with respect to a {@link #J2000_EPOCH}
237      * @see #getJ2000Day()
238      */
239     public DateComponents(final int offset) {
240 
241         // we follow the astronomical convention for calendars:
242         // we consider a year zero and 10 days are missing in 1582
243         // from 1582-10-15: gregorian calendar
244         // from 0001-01-01 to 1582-10-04: julian calendar
245         // up to 0000-12-31 : proleptic julian calendar
246         YearFactory yFactory = GREGORIAN_FACTORY;
247         if (offset < -152384) {
248             if (offset > -730122) {
249                 yFactory = JULIAN_FACTORY;
250             } else {
251                 yFactory = PROLEPTIC_JULIAN_FACTORY;
252             }
253         }
254         year = yFactory.getYear(offset);
255         final int dayInYear = offset - yFactory.getLastJ2000DayOfYear(year - 1);
256 
257         // handle month/day according to the year being a common or leap year
258         final MonthDayFactory mdFactory =
259             yFactory.isLeap(year) ? LEAP_YEAR_FACTORY : COMMON_YEAR_FACTORY;
260         month = mdFactory.getMonth(dayInYear);
261         day   = mdFactory.getDay(dayInYear, month);
262 
263     }
264 
265     /** Build a date from its offset with respect to a reference epoch.
266      * <p>This constructor is mainly useful to build a date from a modified
267      * julian day (using {@link #MODIFIED_JULIAN_EPOCH}) or a GPS week number
268      * (using {@link #GPS_EPOCH}).</p>
269      * @param epoch reference epoch
270      * @param offset offset with respect to a reference epoch
271      * @see #DateComponents(int)
272      * @see #getMJD()
273      */
274     public DateComponents(final DateComponents epoch, final int offset) {
275         this(epoch.getJ2000Day() + offset);
276     }
277 
278     /** Build a date from week components.
279      * <p>The calendar week number is a number between 1 and 52 or 53 depending
280      * on the year. Week 1 is defined by ISO as the one that includes the first
281      * Thursday of a year. Week 1 may therefore start the previous year and week
282      * 52 or 53 may end in the next year. As an example calendar date 1995-01-01
283      * corresponds to week date 1994-W52-7 (i.e. Sunday in the last week of 1994
284      * is in fact the first day of year 1995). This date would beAnother example is calendar date
285      * 1996-12-31 which corresponds to week date 1997-W01-2 (i.e. Tuesday in the
286      * first week of 1997 is in fact the last day of year 1996).</p>
287      * @param wYear year associated to week numbering
288      * @param week week number in year, from 1 to 52 or 53
289      * @param dayOfWeek day of week, from 1 (Monday) to 7 (Sunday)
290      * @return a builded date
291      * @exception IllegalArgumentException if inconsistent arguments
292      * are given (parameters out of range, week 53 on a 52 weeks year ...)
293      */
294     public static DateComponents createFromWeekComponents(final int wYear, final int week, final int dayOfWeek)
295         throws IllegalArgumentException {
296 
297         final DateComponents firstWeekMonday = new DateComponents(getFirstWeekMonday(wYear));
298         final DateComponents d = new DateComponents(firstWeekMonday, 7 * week + dayOfWeek - 8);
299 
300         // check the parameters for invalid date components
301         if (week != d.getCalendarWeek() || dayOfWeek != d.getDayOfWeek()) {
302             throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_WEEK_DATE,
303                                                      wYear, week, dayOfWeek);
304         }
305 
306         return d;
307 
308     }
309 
310     /** Parse a string in ISO-8601 format to build a date.
311      * <p>The supported formats are:
312      * <ul>
313      *   <li>basic format calendar date: YYYYMMDD</li>
314      *   <li>extended format calendar date: YYYY-MM-DD</li>
315      *   <li>basic format ordinal date: YYYYDDD</li>
316      *   <li>extended format ordinal date: YYYY-DDD</li>
317      *   <li>basic format week date: YYYYWwwD</li>
318      *   <li>extended format week date: YYYY-Www-D</li>
319      * </ul>
320      *
321      * <p> As shown by the list above, only the complete representations defined in section 4.1
322      * of ISO-8601 standard are supported, neither expended representations nor representations
323      * with reduced accuracy are supported.
324      *
325      * <p>
326      * Parsing a single integer as a julian day is <em>not</em> supported as it may be ambiguous
327      * with either the basic format calendar date or the basic format ordinal date depending
328      * on the number of digits.
329      * </p>
330      * @param string string to parse
331      * @return a parsed date
332      * @exception IllegalArgumentException if string cannot be parsed
333      */
334     public static  DateComponents parseDate(final String string) {
335 
336         // is the date a calendar date ?
337         final Matcher calendarMatcher = CALENDAR_FORMAT.matcher(string);
338         if (calendarMatcher.matches()) {
339             return new DateComponents(Integer.parseInt(calendarMatcher.group(1)),
340                                       Integer.parseInt(calendarMatcher.group(2)),
341                                       Integer.parseInt(calendarMatcher.group(3)));
342         }
343 
344         // is the date an ordinal date ?
345         final Matcher ordinalMatcher = ORDINAL_FORMAT.matcher(string);
346         if (ordinalMatcher.matches()) {
347             return new DateComponents(Integer.parseInt(ordinalMatcher.group(1)),
348                                       Integer.parseInt(ordinalMatcher.group(2)));
349         }
350 
351         // is the date a week date ?
352         final Matcher weekMatcher = WEEK_FORMAT.matcher(string);
353         if (weekMatcher.matches()) {
354             return createFromWeekComponents(Integer.parseInt(weekMatcher.group(1)),
355                                             Integer.parseInt(weekMatcher.group(2)),
356                                             Integer.parseInt(weekMatcher.group(3)));
357         }
358 
359         throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_DATE, string);
360 
361     }
362 
363     /** Get the year number.
364      * @return year number (may be 0 or negative for BC years)
365      */
366     public int getYear() {
367         return year;
368     }
369 
370     /** Get the month.
371      * @return month number from 1 to 12
372      */
373     public int getMonth() {
374         return month;
375     }
376 
377     /** Get the month as an enumerate.
378      * @return month as an enumerate
379      */
380     public Month getMonthEnum() {
381         return Month.getMonth(month);
382     }
383 
384     /** Get the day.
385      * @return day number from 1 to 31
386      */
387     public int getDay() {
388         return day;
389     }
390 
391     /** Get the day number with respect to J2000 epoch.
392      * @return day number with respect to J2000 epoch
393      */
394     public int getJ2000Day() {
395         YearFactory yFactory = GREGORIAN_FACTORY;
396         if (year < 1583) {
397             if (year < 1) {
398                 yFactory = PROLEPTIC_JULIAN_FACTORY;
399             } else if (year < 1582 || month < 10 || month < 11 && day < 5) {
400                 yFactory = JULIAN_FACTORY;
401             }
402         }
403         final MonthDayFactory mdFactory =
404             yFactory.isLeap(year) ? LEAP_YEAR_FACTORY : COMMON_YEAR_FACTORY;
405         return yFactory.getLastJ2000DayOfYear(year - 1) +
406                mdFactory.getDayInYear(month, day);
407     }
408 
409     /** Get the modified julian day.
410      * @return modified julian day
411      */
412     public int getMJD() {
413         return MJD_TO_J2000 + getJ2000Day();
414     }
415 
416     /** Get the calendar week number.
417      * <p>The calendar week number is a number between 1 and 52 or 53 depending
418      * on the year. Week 1 is defined by ISO as the one that includes the first
419      * Thursday of a year. Week 1 may therefore start the previous year and week
420      * 52 or 53 may end in the next year. As an example calendar date 1995-01-01
421      * corresponds to week date 1994-W52-7 (i.e. Sunday in the last week of 1994
422      * is in fact the first day of year 1995). Another example is calendar date
423      * 1996-12-31 which corresponds to week date 1997-W01-2 (i.e. Tuesday in the
424      * first week of 1997 is in fact the last day of year 1996).</p>
425      * @return calendar week number
426      */
427     public int getCalendarWeek() {
428         final int firstWeekMonday = getFirstWeekMonday(year);
429         int daysSincefirstMonday = getJ2000Day() - firstWeekMonday;
430         if (daysSincefirstMonday < 0) {
431             // we are still in a week from previous year
432             daysSincefirstMonday += firstWeekMonday - getFirstWeekMonday(year - 1);
433         } else if (daysSincefirstMonday > 363) {
434             // up to three days at end of year may belong to first week of next year
435             // (by chance, there is no need for a specific check in year 1582 ...)
436             final int weekYearLength = getFirstWeekMonday(year + 1) - firstWeekMonday;
437             if (daysSincefirstMonday >= weekYearLength) {
438                 daysSincefirstMonday -= weekYearLength;
439             }
440         }
441         return 1 + daysSincefirstMonday / 7;
442     }
443 
444     /** Get the monday of a year first week.
445      * @param year year to consider
446      * @return day of the monday of the first weak of year
447      */
448     private static int getFirstWeekMonday(final int year) {
449         final int yearFirst = new DateComponents(year, 1, 1).getJ2000Day();
450         final int offsetToMonday = 4 - (yearFirst + 2) % 7;
451         return yearFirst + offsetToMonday + ((offsetToMonday > 3) ? -7 : 0);
452     }
453 
454     /** Get the day of week.
455      * <p>Day of week is a number between 1 (Monday) and 7 (Sunday).</p>
456      * @return day of week
457      */
458     public int getDayOfWeek() {
459         final int dow = (getJ2000Day() + 6) % 7; // result is between -6 and +6
460         return (dow < 1) ? (dow + 7) : dow;
461     }
462 
463     /** Get the day number in year.
464      * <p>Day number in year is between 1 (January 1st) and either 365 or
465      * 366 inclusive depending on year.</p>
466      * @return day number in year
467      */
468     public int getDayOfYear() {
469         return getJ2000Day() - new DateComponents(year - 1, 12, 31).getJ2000Day();
470     }
471 
472     /** Get a string representation (ISO-8601) of the date.
473      * @return string representation of the date.
474      */
475     public String toString() {
476         return new StringBuilder().
477                append(FOUR_DIGITS.format(year)).append('-').
478                append(TWO_DIGITS.format(month)).append('-').
479                append(TWO_DIGITS.format(day)).
480                toString();
481     }
482 
483     /** {@inheritDoc} */
484     public int compareTo(final DateComponents other) {
485         final int j2000Day = getJ2000Day();
486         final int otherJ2000Day = other.getJ2000Day();
487         if (j2000Day < otherJ2000Day) {
488             return -1;
489         } else if (j2000Day > otherJ2000Day) {
490             return 1;
491         }
492         return 0;
493     }
494 
495     /** {@inheritDoc} */
496     public boolean equals(final Object other) {
497         try {
498             final DateComponents otherDate = (DateComponents) other;
499             return otherDate != null && year == otherDate.year &&
500                    month == otherDate.month && day == otherDate.day;
501         } catch (ClassCastException cce) {
502             return false;
503         }
504     }
505 
506     /** {@inheritDoc} */
507     public int hashCode() {
508         return (year << 16) ^ (month << 8) ^ day;
509     }
510 
511     /** Interface for dealing with years sequences according to some calendar. */
512     private interface YearFactory {
513 
514         /** Get the year number for a given day number with respect to J2000 epoch.
515          * @param j2000Day day number with respect to J2000 epoch
516          * @return year number
517          */
518         int getYear(int j2000Day);
519 
520         /** Get the day number with respect to J2000 epoch for new year's Eve.
521          * @param year year number
522          * @return day number with respect to J2000 epoch for new year's Eve
523          */
524         int getLastJ2000DayOfYear(int year);
525 
526         /** Check if a year is a leap or common year.
527          * @param year year number
528          * @return true if year is a leap year
529          */
530         boolean isLeap(int year);
531 
532     }
533 
534     /** Class providing a years sequence compliant with the proleptic Julian calendar. */
535     private static class ProlepticJulianFactory implements YearFactory {
536 
537         /** {@inheritDoc} */
538         public int getYear(final int j2000Day) {
539             return  (int) -((-4l * j2000Day - 2920488l) / 1461l);
540         }
541 
542         /** {@inheritDoc} */
543         public int getLastJ2000DayOfYear(final int year) {
544             return 365 * year + (year + 1) / 4 - 730123;
545         }
546 
547         /** {@inheritDoc} */
548         public boolean isLeap(final int year) {
549             return (year % 4) == 0;
550         }
551 
552     }
553 
554     /** Class providing a years sequence compliant with the Julian calendar. */
555     private static class JulianFactory implements YearFactory {
556 
557         /** {@inheritDoc} */
558         public int getYear(final int j2000Day) {
559             return  (int) ((4l * j2000Day + 2921948l) / 1461l);
560         }
561 
562         /** {@inheritDoc} */
563         public int getLastJ2000DayOfYear(final int year) {
564             return 365 * year + year / 4 - 730122;
565         }
566 
567         /** {@inheritDoc} */
568         public boolean isLeap(final int year) {
569             return (year % 4) == 0;
570         }
571 
572     }
573 
574     /** Class providing a years sequence compliant with the Gregorian calendar. */
575     private static class GregorianFactory implements YearFactory {
576 
577         /** {@inheritDoc} */
578         public int getYear(final int j2000Day) {
579 
580             // year estimate
581             int year = (int) ((400l * j2000Day + 292194288l) / 146097l);
582 
583             // the previous estimate is one unit too high in some rare cases
584             // (240 days in the 400 years gregorian cycle, about 0.16%)
585             if (j2000Day <= getLastJ2000DayOfYear(year - 1)) {
586                 --year;
587             }
588 
589             // exact year
590             return year;
591 
592         }
593 
594         /** {@inheritDoc} */
595         public int getLastJ2000DayOfYear(final int year) {
596             return 365 * year + year / 4 - year / 100 + year / 400 - 730120;
597         }
598 
599         /** {@inheritDoc} */
600         public boolean isLeap(final int year) {
601             return (year % 4) == 0 && ((year % 400) == 0 || (year % 100) != 0);
602         }
603 
604     }
605 
606     /** Interface for dealing with months sequences according to leap/common years. */
607     private interface MonthDayFactory {
608 
609         /** Get the month number for a given day number within year.
610          * @param dayInYear day number within year
611          * @return month number
612          */
613         int getMonth(int dayInYear);
614 
615         /** Get the day number for given month and day number within year.
616          * @param dayInYear day number within year
617          * @param month month number
618          * @return day number
619          */
620         int getDay(int dayInYear, int month);
621 
622         /** Get the day number within year for given month and day numbers.
623          * @param month month number
624          * @param day day number
625          * @return day number within year
626          */
627         int getDayInYear(int month, int day);
628 
629     }
630 
631     /** Class providing the months sequence for leap years. */
632     private static class LeapYearFactory implements MonthDayFactory {
633 
634         /** Months succession definition. */
635         private static final int[] PREVIOUS_MONTH_END_DAY = {
636             0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335
637         };
638 
639         /** {@inheritDoc} */
640         public int getMonth(final int dayInYear) {
641             return (dayInYear < 32) ? 1 : (10 * dayInYear + 313) / 306;
642         }
643 
644         /** {@inheritDoc} */
645         public int getDay(final int dayInYear, final int month) {
646             return dayInYear - PREVIOUS_MONTH_END_DAY[month];
647         }
648 
649         /** {@inheritDoc} */
650         public int getDayInYear(final int month, final int day) {
651             return day + PREVIOUS_MONTH_END_DAY[month];
652         }
653 
654     }
655 
656     /** Class providing the months sequence for common years. */
657     private static class CommonYearFactory implements MonthDayFactory {
658 
659         /** Months succession definition. */
660         private static final int[] PREVIOUS_MONTH_END_DAY = {
661             0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
662         };
663 
664         /** {@inheritDoc} */
665         public int getMonth(final int dayInYear) {
666             return (dayInYear < 32) ? 1 : (10 * dayInYear + 323) / 306;
667         }
668 
669         /** {@inheritDoc} */
670         public int getDay(final int dayInYear, final int month) {
671             return dayInYear - PREVIOUS_MONTH_END_DAY[month];
672         }
673 
674         /** {@inheritDoc} */
675         public int getDayInYear(final int month, final int day) {
676             return day + PREVIOUS_MONTH_END_DAY[month];
677         }
678 
679     }
680 
681 }