DateComponents.java

  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. import java.io.IOException;
  19. import java.io.Serializable;
  20. import java.util.regex.Matcher;
  21. import java.util.regex.Pattern;

  22. import org.orekit.errors.OrekitIllegalArgumentException;
  23. import org.orekit.errors.OrekitInternalError;
  24. import org.orekit.errors.OrekitMessages;
  25. import org.orekit.utils.formatting.FastLongFormatter;

  26. /** Class representing a date broken up as year, month and day components.
  27.  * <p>This class uses the astronomical convention for calendars,
  28.  * which is also the convention used by <code>java.util.Date</code>:
  29.  * a year zero is present between years -1 and +1, and 10 days are
  30.  * missing in 1582. The calendar used around these special dates are:</p>
  31.  * <ul>
  32.  *   <li>up to 0000-12-31 : proleptic julian calendar</li>
  33.  *   <li>from 0001-01-01 to 1582-10-04: julian calendar</li>
  34.  *   <li>from 1582-10-15: gregorian calendar</li>
  35.  * </ul>
  36.  * <p>Instances of this class are guaranteed to be immutable.</p>
  37.  * @see TimeComponents
  38.  * @see DateTimeComponents
  39.  * @author Luc Maisonobe
  40.  */
  41. public class DateComponents implements Serializable, Comparable<DateComponents> {

  42.     /** Reference epoch for julian dates: -4712-01-01.
  43.      * <p>Both <code>java.util.Date</code> and {@link DateComponents} classes
  44.      * follow the astronomical conventions and consider a year 0 between
  45.      * years -1 and +1, hence this reference date lies in year -4712 and not
  46.      * in year -4713 as can be seen in other documents or programs that obey
  47.      * a different convention (for example the <code>convcal</code> utility).</p>
  48.      */
  49.     public static final DateComponents JULIAN_EPOCH;

  50.     /** Reference epoch for modified julian dates: 1858-11-17. */
  51.     public static final DateComponents MODIFIED_JULIAN_EPOCH;

  52.     /** Reference epoch for 1950 dates: 1950-01-01. */
  53.     public static final DateComponents FIFTIES_EPOCH;

  54.     /** Reference epoch for CCSDS Time Code Format (CCSDS 301.0-B-4): 1958-01-01. */
  55.     public static final DateComponents CCSDS_EPOCH;

  56.     /** Reference epoch for Galileo System Time: 1999-08-22. */
  57.     public static final DateComponents GALILEO_EPOCH;

  58.     /** Reference epoch for GPS weeks: 1980-01-06. */
  59.     public static final DateComponents GPS_EPOCH;

  60.     /** Reference epoch for QZSS weeks: 1980-01-06. */
  61.     public static final DateComponents QZSS_EPOCH;

  62.     /** Reference epoch for NavIC weeks: 1999-08-22. */
  63.     public static final DateComponents NAVIC_EPOCH;

  64.     /** Reference epoch for BeiDou weeks: 2006-01-01. */
  65.     public static final DateComponents BEIDOU_EPOCH;

  66.     /** Reference epoch for GLONASS four-year interval number: 1996-01-01. */
  67.     public static final DateComponents GLONASS_EPOCH;

  68.     /** J2000.0 Reference epoch: 2000-01-01. */
  69.     public static final DateComponents J2000_EPOCH;

  70.     /** Java Reference epoch: 1970-01-01. */
  71.     public static final DateComponents JAVA_EPOCH;

  72.     /** Maximum supported date.
  73.      * <p>
  74.      * This is date 5881610-07-11 which corresponds to {@code Integer.MAX_VALUE}
  75.      * days after {@link #J2000_EPOCH}.
  76.      * </p>
  77.      * @since 9.0
  78.      */
  79.     public static final DateComponents MAX_EPOCH;

  80.     /** Maximum supported date.
  81.      * <p>
  82.      * This is date -5877490-03-03, which corresponds to {@code Integer.MIN_VALUE}
  83.      * days before {@link #J2000_EPOCH}.
  84.      * </p>
  85.      * @since 9.0
  86.      */
  87.     public static final DateComponents MIN_EPOCH;

  88.     /** Offset between julian day epoch and modified julian day epoch. */
  89.     public static final double JD_TO_MJD = 2400000.5;

  90.     /** Format for one 4 digits integer field.
  91.      * @since 13.0.3
  92.      */
  93.     private static final FastLongFormatter PADDED_FOUR_DIGITS_INTEGER = new FastLongFormatter(4, true);

  94.     /** Format for one 2 digits integer field.
  95.      * @since 13.0.3
  96.      */
  97.     private static final FastLongFormatter PADDED_TWO_DIGITS_INTEGER = new FastLongFormatter(2, true);

  98.     /** Serializable UID. */
  99.     private static final long serialVersionUID = -2462694707837970938L;

  100.     /** Factory for proleptic julian calendar (up to 0000-12-31). */
  101.     private static final YearFactory PROLEPTIC_JULIAN_FACTORY = new ProlepticJulianFactory();

  102.     /** Factory for julian calendar (from 0001-01-01 to 1582-10-04). */
  103.     private static final YearFactory JULIAN_FACTORY           = new JulianFactory();

  104.     /** Factory for gregorian calendar (from 1582-10-15). */
  105.     private static final YearFactory GREGORIAN_FACTORY        = new GregorianFactory();

  106.     /** Factory for leap years. */
  107.     private static final MonthDayFactory LEAP_YEAR_FACTORY    = new LeapYearFactory();

  108.     /** Factory for non-leap years. */
  109.     private static final MonthDayFactory COMMON_YEAR_FACTORY  = new CommonYearFactory();

  110.     /** Offset between J2000 epoch and modified julian day epoch. */
  111.     private static final int MJD_TO_J2000 = 51544;


  112.     /** Basic and extended format calendar date. */
  113.     private static final Pattern CALENDAR_FORMAT = Pattern.compile("^(-?\\d\\d\\d\\d)-?(\\d\\d)-?(\\d\\d)$");

  114.     /** Basic and extended format ordinal date. */
  115.     private static final Pattern ORDINAL_FORMAT = Pattern.compile("^(-?\\d\\d\\d\\d)-?(\\d\\d\\d)$");

  116.     /** Basic and extended format week date. */
  117.     private static final Pattern WEEK_FORMAT = Pattern.compile("^(-?\\d\\d\\d\\d)-?W(\\d\\d)-?(\\d)$");

  118.     static {
  119.         // this static statement makes sure the reference epoch are initialized
  120.         // once AFTER the various factories have been set up
  121.         JULIAN_EPOCH          = new DateComponents(-4712,  1,  1);
  122.         MODIFIED_JULIAN_EPOCH = new DateComponents(1858, 11, 17);
  123.         FIFTIES_EPOCH         = new DateComponents(1950, 1, 1);
  124.         CCSDS_EPOCH           = new DateComponents(1958, 1, 1);
  125.         GALILEO_EPOCH         = new DateComponents(1999, 8, 22);
  126.         GPS_EPOCH             = new DateComponents(1980, 1, 6);
  127.         QZSS_EPOCH            = new DateComponents(1980, 1, 6);
  128.         NAVIC_EPOCH           = new DateComponents(1999, 8, 22);
  129.         BEIDOU_EPOCH          = new DateComponents(2006, 1, 1);
  130.         GLONASS_EPOCH         = new DateComponents(1996, 1, 1);
  131.         J2000_EPOCH           = new DateComponents(2000, 1, 1);
  132.         JAVA_EPOCH            = new DateComponents(1970, 1, 1);
  133.         MAX_EPOCH             = new DateComponents(Integer.MAX_VALUE);
  134.         MIN_EPOCH             = new DateComponents(Integer.MIN_VALUE);
  135.     }

  136.     /** Year number. */
  137.     private final int year;

  138.     /** Month number. */
  139.     private final int month;

  140.     /** Day number. */
  141.     private final int day;

  142.     /** Build a date from its components.
  143.      * @param year year number (may be 0 or negative for BC years)
  144.      * @param month month number from 1 to 12
  145.      * @param day day number from 1 to 31
  146.      * @exception IllegalArgumentException if inconsistent arguments
  147.      * are given (parameters out of range, february 29 for non-leap years,
  148.      * dates during the gregorian leap in 1582 ...)
  149.      */
  150.     public DateComponents(final int year, final int month, final int day)
  151.         throws IllegalArgumentException {

  152.         // very rough range check
  153.         // (just to avoid ArrayOutOfboundException in MonthDayFactory later)
  154.         if (month < 1 || month > 12) {
  155.             throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_MONTH, month);
  156.         }

  157.         // start by trusting the parameters
  158.         this.year  = year;
  159.         this.month = month;
  160.         this.day   = day;

  161.         // build a check date from the J2000 day
  162.         final DateComponents check = new DateComponents(getJ2000Day());

  163.         // check the parameters for mismatch
  164.         // (i.e. invalid date components, like 29 february on non-leap years)
  165.         if (year != check.year || month != check.month || day != check.day) {
  166.             throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_YEAR_MONTH_DAY,
  167.                                                       year, month, day);
  168.         }

  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 enumerate
  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 Month month, final int day)
  179.         throws IllegalArgumentException {
  180.         this(year, month.getNumber(), day);
  181.     }

  182.     /** Build a date from a year and day number.
  183.      * @param year year number (may be 0 or negative for BC years)
  184.      * @param dayNumber day number in the year from 1 to 366
  185.      * @exception IllegalArgumentException if dayNumber is out of range
  186.      * with respect to year
  187.      */
  188.     public DateComponents(final int year, final int dayNumber)
  189.         throws IllegalArgumentException {
  190.         this(J2000_EPOCH, new DateComponents(year - 1, 12, 31).getJ2000Day() + dayNumber);
  191.         if (dayNumber != getDayOfYear()) {
  192.             throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_DAY_NUMBER_IN_YEAR,
  193.                                                      dayNumber, year);
  194.         }
  195.     }

  196.     /** Build a date from its offset with respect to a {@link #J2000_EPOCH}.
  197.      * @param offset offset with respect to a {@link #J2000_EPOCH}
  198.      * @see #getJ2000Day()
  199.      */
  200.     public DateComponents(final int offset) {

  201.         // we follow the astronomical convention for calendars:
  202.         // we consider a year zero and 10 days are missing in 1582
  203.         // from 1582-10-15: gregorian calendar
  204.         // from 0001-01-01 to 1582-10-04: julian calendar
  205.         // up to 0000-12-31 : proleptic julian calendar
  206.         YearFactory yFactory = GREGORIAN_FACTORY;
  207.         if (offset < -152384) {
  208.             if (offset > -730122) {
  209.                 yFactory = JULIAN_FACTORY;
  210.             } else {
  211.                 yFactory = PROLEPTIC_JULIAN_FACTORY;
  212.             }
  213.         }
  214.         year = yFactory.getYear(offset);
  215.         final int dayInYear = offset - yFactory.getLastJ2000DayOfYear(year - 1);

  216.         // handle month/day according to the year being a common or leap year
  217.         final MonthDayFactory mdFactory =
  218.             yFactory.isLeap(year) ? LEAP_YEAR_FACTORY : COMMON_YEAR_FACTORY;
  219.         month = mdFactory.getMonth(dayInYear);
  220.         day   = mdFactory.getDay(dayInYear, month);

  221.     }

  222.     /** Build a date from its offset with respect to a reference epoch.
  223.      * <p>This constructor is mainly useful to build a date from a modified
  224.      * julian day (using {@link #MODIFIED_JULIAN_EPOCH}) or a GPS week number
  225.      * (using {@link #GPS_EPOCH}).</p>
  226.      * @param epoch reference epoch
  227.      * @param offset offset with respect to a reference epoch
  228.      * @see #DateComponents(int)
  229.      * @see #getMJD()
  230.      */
  231.     public DateComponents(final DateComponents epoch, final int offset) {
  232.         this(epoch.getJ2000Day() + offset);
  233.     }

  234.     /** Build a date from week components.
  235.      * <p>The calendar week number is a number between 1 and 52 or 53 depending
  236.      * on the year. Week 1 is defined by ISO as the one that includes the first
  237.      * Thursday of a year. Week 1 may therefore start the previous year and week
  238.      * 52 or 53 may end in the next year. As an example calendar date 1995-01-01
  239.      * corresponds to week date 1994-W52-7 (i.e. Sunday in the last week of 1994
  240.      * is in fact the first day of year 1995). This date would beAnother example is calendar date
  241.      * 1996-12-31 which corresponds to week date 1997-W01-2 (i.e. Tuesday in the
  242.      * first week of 1997 is in fact the last day of year 1996).</p>
  243.      * @param wYear year associated to week numbering
  244.      * @param week week number in year, from 1 to 52 or 53
  245.      * @param dayOfWeek day of week, from 1 (Monday) to 7 (Sunday)
  246.      * @return a builded date
  247.      * @exception IllegalArgumentException if inconsistent arguments
  248.      * are given (parameters out of range, week 53 on a 52 weeks year ...)
  249.      */
  250.     public static DateComponents createFromWeekComponents(final int wYear, final int week, final int dayOfWeek)
  251.         throws IllegalArgumentException {

  252.         final DateComponents firstWeekMonday = new DateComponents(getFirstWeekMonday(wYear));
  253.         final DateComponents d = new DateComponents(firstWeekMonday, 7 * week + dayOfWeek - 8);

  254.         // check the parameters for invalid date components
  255.         if (week != d.getCalendarWeek() || dayOfWeek != d.getDayOfWeek()) {
  256.             throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_WEEK_DATE,
  257.                                                      wYear, week, dayOfWeek);
  258.         }

  259.         return d;

  260.     }

  261.     /** Parse a string in ISO-8601 format to build a date.
  262.      * <p>The supported formats are:
  263.      * <ul>
  264.      *   <li>basic format calendar date: YYYYMMDD</li>
  265.      *   <li>extended format calendar date: YYYY-MM-DD</li>
  266.      *   <li>basic format ordinal date: YYYYDDD</li>
  267.      *   <li>extended format ordinal date: YYYY-DDD</li>
  268.      *   <li>basic format week date: YYYYWwwD</li>
  269.      *   <li>extended format week date: YYYY-Www-D</li>
  270.      * </ul>
  271.      *
  272.      * <p> As shown by the list above, only the complete representations defined in section 4.1
  273.      * of ISO-8601 standard are supported, neither expended representations nor representations
  274.      * with reduced accuracy are supported.
  275.      *
  276.      * <p>
  277.      * Parsing a single integer as a julian day is <em>not</em> supported as it may be ambiguous
  278.      * with either the basic format calendar date or the basic format ordinal date depending
  279.      * on the number of digits.
  280.      * </p>
  281.      * @param string string to parse
  282.      * @return a parsed date
  283.      * @exception IllegalArgumentException if string cannot be parsed
  284.      */
  285.     public static  DateComponents parseDate(final String string) {

  286.         // is the date a calendar date ?
  287.         final Matcher calendarMatcher = CALENDAR_FORMAT.matcher(string);
  288.         if (calendarMatcher.matches()) {
  289.             return new DateComponents(Integer.parseInt(calendarMatcher.group(1)),
  290.                                       Integer.parseInt(calendarMatcher.group(2)),
  291.                                       Integer.parseInt(calendarMatcher.group(3)));
  292.         }

  293.         // is the date an ordinal date ?
  294.         final Matcher ordinalMatcher = ORDINAL_FORMAT.matcher(string);
  295.         if (ordinalMatcher.matches()) {
  296.             return new DateComponents(Integer.parseInt(ordinalMatcher.group(1)),
  297.                                       Integer.parseInt(ordinalMatcher.group(2)));
  298.         }

  299.         // is the date a week date ?
  300.         final Matcher weekMatcher = WEEK_FORMAT.matcher(string);
  301.         if (weekMatcher.matches()) {
  302.             return createFromWeekComponents(Integer.parseInt(weekMatcher.group(1)),
  303.                                             Integer.parseInt(weekMatcher.group(2)),
  304.                                             Integer.parseInt(weekMatcher.group(3)));
  305.         }

  306.         throw new OrekitIllegalArgumentException(OrekitMessages.NON_EXISTENT_DATE, string);

  307.     }

  308.     /** Get the year number.
  309.      * @return year number (may be 0 or negative for BC years)
  310.      */
  311.     public int getYear() {
  312.         return year;
  313.     }

  314.     /** Get the month.
  315.      * @return month number from 1 to 12
  316.      */
  317.     public int getMonth() {
  318.         return month;
  319.     }

  320.     /** Get the month as an enumerate.
  321.      * @return month as an enumerate
  322.      */
  323.     public Month getMonthEnum() {
  324.         return Month.getMonth(month);
  325.     }

  326.     /** Get the day.
  327.      * @return day number from 1 to 31
  328.      */
  329.     public int getDay() {
  330.         return day;
  331.     }

  332.     /** Get the day number with respect to J2000 epoch.
  333.      * @return day number with respect to J2000 epoch
  334.      */
  335.     public int getJ2000Day() {
  336.         YearFactory yFactory = GREGORIAN_FACTORY;
  337.         if (year < 1583) {
  338.             if (year < 1) {
  339.                 yFactory = PROLEPTIC_JULIAN_FACTORY;
  340.             } else if (year < 1582 || month < 10 || month < 11 && day < 5) {
  341.                 yFactory = JULIAN_FACTORY;
  342.             }
  343.         }
  344.         final MonthDayFactory mdFactory =
  345.             yFactory.isLeap(year) ? LEAP_YEAR_FACTORY : COMMON_YEAR_FACTORY;
  346.         return yFactory.getLastJ2000DayOfYear(year - 1) +
  347.                mdFactory.getDayInYear(month, day);
  348.     }

  349.     /** Get the modified julian day.
  350.      * @return modified julian day
  351.      */
  352.     public int getMJD() {
  353.         return MJD_TO_J2000 + getJ2000Day();
  354.     }

  355.     /** Get the calendar week number.
  356.      * <p>The calendar week number is a number between 1 and 52 or 53 depending
  357.      * on the year. Week 1 is defined by ISO as the one that includes the first
  358.      * Thursday of a year. Week 1 may therefore start the previous year and week
  359.      * 52 or 53 may end in the next year. As an example calendar date 1995-01-01
  360.      * corresponds to week date 1994-W52-7 (i.e. Sunday in the last week of 1994
  361.      * is in fact the first day of year 1995). Another example is calendar date
  362.      * 1996-12-31 which corresponds to week date 1997-W01-2 (i.e. Tuesday in the
  363.      * first week of 1997 is in fact the last day of year 1996).</p>
  364.      * @return calendar week number
  365.      */
  366.     public int getCalendarWeek() {
  367.         final int firstWeekMonday = getFirstWeekMonday(year);
  368.         int daysSincefirstMonday = getJ2000Day() - firstWeekMonday;
  369.         if (daysSincefirstMonday < 0) {
  370.             // we are still in a week from previous year
  371.             daysSincefirstMonday += firstWeekMonday - getFirstWeekMonday(year - 1);
  372.         } else if (daysSincefirstMonday > 363) {
  373.             // up to three days at end of year may belong to first week of next year
  374.             // (by chance, there is no need for a specific check in year 1582 ...)
  375.             final int weekYearLength = getFirstWeekMonday(year + 1) - firstWeekMonday;
  376.             if (daysSincefirstMonday >= weekYearLength) {
  377.                 daysSincefirstMonday -= weekYearLength;
  378.             }
  379.         }
  380.         return 1 + daysSincefirstMonday / 7;
  381.     }

  382.     /** Get the monday of a year first week.
  383.      * @param year year to consider
  384.      * @return day of the monday of the first weak of year
  385.      */
  386.     private static int getFirstWeekMonday(final int year) {
  387.         final int yearFirst = new DateComponents(year, 1, 1).getJ2000Day();
  388.         final int offsetToMonday = 4 - (yearFirst + 2) % 7;
  389.         return yearFirst + offsetToMonday + ((offsetToMonday > 3) ? -7 : 0);
  390.     }

  391.     /** Get the day of week.
  392.      * <p>Day of week is a number between 1 (Monday) and 7 (Sunday).</p>
  393.      * @return day of week
  394.      */
  395.     public int getDayOfWeek() {
  396.         final int dow = (getJ2000Day() + 6) % 7; // result is between -6 and +6
  397.         return (dow < 1) ? (dow + 7) : dow;
  398.     }

  399.     /** Get the day number in year.
  400.      * <p>Day number in year is between 1 (January 1st) and either 365 or
  401.      * 366 inclusive depending on year.</p>
  402.      * @return day number in year
  403.      */
  404.     public int getDayOfYear() {
  405.         return getJ2000Day() - new DateComponents(year - 1, 12, 31).getJ2000Day();
  406.     }

  407.     /** Get a string representation (ISO-8601) of the date.
  408.      * @return string representation of the date.
  409.      */
  410.     public String toString() {
  411.         try {
  412.             final StringBuilder builder = new StringBuilder();
  413.             PADDED_FOUR_DIGITS_INTEGER.appendTo(builder, year);
  414.             builder.append('-');
  415.             PADDED_TWO_DIGITS_INTEGER.appendTo(builder, month);
  416.             builder.append('-');
  417.             PADDED_TWO_DIGITS_INTEGER.appendTo(builder, day);
  418.             return builder.toString();
  419.         } catch (IOException ioe) {
  420.             // this should never happen
  421.             throw new OrekitInternalError(ioe);
  422.         }
  423.     }

  424.     /** {@inheritDoc} */
  425.     public int compareTo(final DateComponents other) {
  426.         final int j2000Day = getJ2000Day();
  427.         final int otherJ2000Day = other.getJ2000Day();
  428.         if (j2000Day < otherJ2000Day) {
  429.             return -1;
  430.         } else if (j2000Day > otherJ2000Day) {
  431.             return 1;
  432.         }
  433.         return 0;
  434.     }

  435.     /** {@inheritDoc} */
  436.     public boolean equals(final Object other) {
  437.         try {
  438.             final DateComponents otherDate = (DateComponents) other;
  439.             return otherDate != null && year == otherDate.year &&
  440.                    month == otherDate.month && day == otherDate.day;
  441.         } catch (ClassCastException cce) {
  442.             return false;
  443.         }
  444.     }

  445.     /** {@inheritDoc} */
  446.     public int hashCode() {
  447.         return (year << 16) ^ (month << 8) ^ day;
  448.     }

  449.     /** Interface for dealing with years sequences according to some calendar. */
  450.     private interface YearFactory {

  451.         /** Get the year number for a given day number with respect to J2000 epoch.
  452.          * @param j2000Day day number with respect to J2000 epoch
  453.          * @return year number
  454.          */
  455.         int getYear(int j2000Day);

  456.         /** Get the day number with respect to J2000 epoch for new year's Eve.
  457.          * @param year year number
  458.          * @return day number with respect to J2000 epoch for new year's Eve
  459.          */
  460.         int getLastJ2000DayOfYear(int year);

  461.         /** Check if a year is a leap or common year.
  462.          * @param year year number
  463.          * @return true if year is a leap year
  464.          */
  465.         boolean isLeap(int year);

  466.     }

  467.     /** Class providing a years sequence compliant with the proleptic Julian calendar. */
  468.     private static class ProlepticJulianFactory implements YearFactory {

  469.         /** {@inheritDoc} */
  470.         public int getYear(final int j2000Day) {
  471.             return  (int) -((-4L * j2000Day - 2920488L) / 1461L);
  472.         }

  473.         /** {@inheritDoc} */
  474.         public int getLastJ2000DayOfYear(final int year) {
  475.             return 365 * year + (year + 1) / 4 - 730123;
  476.         }

  477.         /** {@inheritDoc} */
  478.         public boolean isLeap(final int year) {
  479.             return (year % 4) == 0;
  480.         }

  481.     }

  482.     /** Class providing a years sequence compliant with the Julian calendar. */
  483.     private static class JulianFactory implements YearFactory {

  484.         /** {@inheritDoc} */
  485.         public int getYear(final int j2000Day) {
  486.             return  (int) ((4L * j2000Day + 2921948L) / 1461L);
  487.         }

  488.         /** {@inheritDoc} */
  489.         public int getLastJ2000DayOfYear(final int year) {
  490.             return 365 * year + year / 4 - 730122;
  491.         }

  492.         /** {@inheritDoc} */
  493.         public boolean isLeap(final int year) {
  494.             return (year % 4) == 0;
  495.         }

  496.     }

  497.     /** Class providing a years sequence compliant with the Gregorian calendar. */
  498.     private static class GregorianFactory implements YearFactory {

  499.         /** {@inheritDoc} */
  500.         public int getYear(final int j2000Day) {

  501.             // year estimate
  502.             int year = (int) ((400L * j2000Day + 292194288L) / 146097L);

  503.             // the previous estimate is one unit too high in some rare cases
  504.             // (240 days in the 400 years gregorian cycle, about 0.16%)
  505.             if (j2000Day <= getLastJ2000DayOfYear(year - 1)) {
  506.                 --year;
  507.             }

  508.             // exact year
  509.             return year;

  510.         }

  511.         /** {@inheritDoc} */
  512.         public int getLastJ2000DayOfYear(final int year) {
  513.             return 365 * year + year / 4 - year / 100 + year / 400 - 730120;
  514.         }

  515.         /** {@inheritDoc} */
  516.         public boolean isLeap(final int year) {
  517.             return (year % 4) == 0 && ((year % 400) == 0 || (year % 100) != 0);
  518.         }

  519.     }

  520.     /** Interface for dealing with months sequences according to leap/common years. */
  521.     private interface MonthDayFactory {

  522.         /** Get the month number for a given day number within year.
  523.          * @param dayInYear day number within year
  524.          * @return month number
  525.          */
  526.         int getMonth(int dayInYear);

  527.         /** Get the day number for given month and day number within year.
  528.          * @param dayInYear day number within year
  529.          * @param month month number
  530.          * @return day number
  531.          */
  532.         int getDay(int dayInYear, int month);

  533.         /** Get the day number within year for given month and day numbers.
  534.          * @param month month number
  535.          * @param day day number
  536.          * @return day number within year
  537.          */
  538.         int getDayInYear(int month, int day);

  539.     }

  540.     /** Class providing the months sequence for leap years. */
  541.     private static class LeapYearFactory implements MonthDayFactory {

  542.         /** Months succession definition. */
  543.         private static final int[] PREVIOUS_MONTH_END_DAY = {
  544.             0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335
  545.         };

  546.         /** {@inheritDoc} */
  547.         public int getMonth(final int dayInYear) {
  548.             return (dayInYear < 32) ? 1 : (10 * dayInYear + 313) / 306;
  549.         }

  550.         /** {@inheritDoc} */
  551.         public int getDay(final int dayInYear, final int month) {
  552.             return dayInYear - PREVIOUS_MONTH_END_DAY[month];
  553.         }

  554.         /** {@inheritDoc} */
  555.         public int getDayInYear(final int month, final int day) {
  556.             return day + PREVIOUS_MONTH_END_DAY[month];
  557.         }

  558.     }

  559.     /** Class providing the months sequence for common years. */
  560.     private static class CommonYearFactory implements MonthDayFactory {

  561.         /** Months succession definition. */
  562.         private static final int[] PREVIOUS_MONTH_END_DAY = {
  563.             0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
  564.         };

  565.         /** {@inheritDoc} */
  566.         public int getMonth(final int dayInYear) {
  567.             return (dayInYear < 32) ? 1 : (10 * dayInYear + 323) / 306;
  568.         }

  569.         /** {@inheritDoc} */
  570.         public int getDay(final int dayInYear, final int month) {
  571.             return dayInYear - PREVIOUS_MONTH_END_DAY[month];
  572.         }

  573.         /** {@inheritDoc} */
  574.         public int getDayInYear(final int month, final int day) {
  575.             return day + PREVIOUS_MONTH_END_DAY[month];
  576.         }

  577.     }

  578. }