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