CopticDate.java

  1. /*
  2.  * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
  3.  *
  4.  * All rights reserved.
  5.  *
  6.  * Redistribution and use in source and binary forms, with or without
  7.  * modification, are permitted provided that the following conditions are met:
  8.  *
  9.  *  * Redistributions of source code must retain the above copyright notice,
  10.  *    this list of conditions and the following disclaimer.
  11.  *
  12.  *  * Redistributions in binary form must reproduce the above copyright notice,
  13.  *    this list of conditions and the following disclaimer in the documentation
  14.  *    and/or other materials provided with the distribution.
  15.  *
  16.  *  * Neither the name of JSR-310 nor the names of its contributors
  17.  *    may be used to endorse or promote products derived from this software
  18.  *    without specific prior written permission.
  19.  *
  20.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  24.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  25.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  26.  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  27.  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  28.  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  29.  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  30.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31.  */
  32. package org.threeten.extra.chrono;

  33. import static java.time.temporal.ChronoField.DAY_OF_MONTH;
  34. import static java.time.temporal.ChronoField.DAY_OF_YEAR;
  35. import static java.time.temporal.ChronoField.EPOCH_DAY;
  36. import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
  37. import static java.time.temporal.ChronoField.YEAR;

  38. import java.io.Serializable;
  39. import java.time.Clock;
  40. import java.time.DateTimeException;
  41. import java.time.LocalDate;
  42. import java.time.LocalTime;
  43. import java.time.ZoneId;
  44. import java.time.chrono.ChronoLocalDate;
  45. import java.time.chrono.ChronoLocalDateTime;
  46. import java.time.chrono.ChronoPeriod;
  47. import java.time.temporal.ChronoField;
  48. import java.time.temporal.Temporal;
  49. import java.time.temporal.TemporalAccessor;
  50. import java.time.temporal.TemporalAdjuster;
  51. import java.time.temporal.TemporalAmount;
  52. import java.time.temporal.TemporalField;
  53. import java.time.temporal.TemporalQuery;
  54. import java.time.temporal.TemporalUnit;

  55. /**
  56.  * A date in the Coptic calendar system.
  57.  * <p>
  58.  * This date operates using the {@linkplain CopticChronology Coptic calendar}.
  59.  * This calendar system is primarily used in Christian Egypt.
  60.  * Dates are aligned such that {@code 0001-01-01 (Coptic)} is {@code 0284-08-29 (ISO)}.
  61.  *
  62.  * <h3>Implementation Requirements</h3>
  63.  * This class is immutable and thread-safe.
  64.  * <p>
  65.  * This class must be treated as a value type. Do not synchronize, rely on the
  66.  * identity hash code or use the distinction between equals() and ==.
  67.  */
  68. public final class CopticDate
  69.         extends AbstractNileDate
  70.         implements ChronoLocalDate, Serializable {

  71.     /**
  72.      * Serialization version.
  73.      */
  74.     private static final long serialVersionUID = -7920528871688876868L;
  75.     /**
  76.      * The difference between the ISO and Coptic epoch day count.
  77.      */
  78.     private static final int EPOCH_DAY_DIFFERENCE = 574971 + 40587;  // MJD values

  79.     /**
  80.      * The proleptic year.
  81.      */
  82.     private final int prolepticYear;
  83.     /**
  84.      * The month.
  85.      */
  86.     private final short month;
  87.     /**
  88.      * The day.
  89.      */
  90.     private final short day;

  91.     //-----------------------------------------------------------------------
  92.     /**
  93.      * Obtains the current {@code CopticDate} from the system clock in the default time-zone.
  94.      * <p>
  95.      * This will query the {@link Clock#systemDefaultZone() system clock} in the default
  96.      * time-zone to obtain the current date.
  97.      * <p>
  98.      * Using this method will prevent the ability to use an alternate clock for testing
  99.      * because the clock is hard-coded.
  100.      *
  101.      * @return the current date using the system clock and default time-zone, not null
  102.      */
  103.     public static CopticDate now() {
  104.         return now(Clock.systemDefaultZone());
  105.     }

  106.     /**
  107.      * Obtains the current {@code CopticDate} from the system clock in the specified time-zone.
  108.      * <p>
  109.      * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date.
  110.      * Specifying the time-zone avoids dependence on the default time-zone.
  111.      * <p>
  112.      * Using this method will prevent the ability to use an alternate clock for testing
  113.      * because the clock is hard-coded.
  114.      *
  115.      * @param zone  the zone ID to use, not null
  116.      * @return the current date using the system clock, not null
  117.      */
  118.     public static CopticDate now(ZoneId zone) {
  119.         return now(Clock.system(zone));
  120.     }

  121.     /**
  122.      * Obtains the current {@code CopticDate} from the specified clock.
  123.      * <p>
  124.      * This will query the specified clock to obtain the current date - today.
  125.      * Using this method allows the use of an alternate clock for testing.
  126.      * The alternate clock may be introduced using {@linkplain Clock dependency injection}.
  127.      *
  128.      * @param clock  the clock to use, not null
  129.      * @return the current date, not null
  130.      * @throws DateTimeException if the current date cannot be obtained
  131.      */
  132.     public static CopticDate now(Clock clock) {
  133.         LocalDate now = LocalDate.now(clock);
  134.         return CopticDate.ofEpochDay(now.toEpochDay());
  135.     }

  136.     /**
  137.      * Obtains a {@code CopticDate} representing a date in the Coptic calendar
  138.      * system from the proleptic-year, month-of-year and day-of-month fields.
  139.      * <p>
  140.      * This returns a {@code CopticDate} with the specified fields.
  141.      * The day must be valid for the year and month, otherwise an exception will be thrown.
  142.      *
  143.      * @param prolepticYear  the Coptic proleptic-year
  144.      * @param month  the Coptic month-of-year, from 1 to 13
  145.      * @param dayOfMonth  the Coptic day-of-month, from 1 to 30
  146.      * @return the date in Coptic calendar system, not null
  147.      * @throws DateTimeException if the value of any field is out of range,
  148.      *  or if the day-of-month is invalid for the month-year
  149.      */
  150.     public static CopticDate of(int prolepticYear, int month, int dayOfMonth) {
  151.         return CopticDate.create(prolepticYear, month, dayOfMonth);
  152.     }

  153.     /**
  154.      * Obtains a {@code CopticDate} from a temporal object.
  155.      * <p>
  156.      * This obtains a date in the Coptic calendar system based on the specified temporal.
  157.      * A {@code TemporalAccessor} represents an arbitrary set of date and time information,
  158.      * which this factory converts to an instance of {@code CopticDate}.
  159.      * <p>
  160.      * The conversion typically uses the {@link ChronoField#EPOCH_DAY EPOCH_DAY}
  161.      * field, which is standardized across calendar systems.
  162.      * <p>
  163.      * This method matches the signature of the functional interface {@link TemporalQuery}
  164.      * allowing it to be used as a query via method reference, {@code CopticDate::from}.
  165.      *
  166.      * @param temporal  the temporal object to convert, not null
  167.      * @return the date in Coptic calendar system, not null
  168.      * @throws DateTimeException if unable to convert to a {@code CopticDate}
  169.      */
  170.     public static CopticDate from(TemporalAccessor temporal) {
  171.         if (temporal instanceof CopticDate) {
  172.             return (CopticDate) temporal;
  173.         }
  174.         return CopticDate.ofEpochDay(temporal.getLong(EPOCH_DAY));
  175.     }

  176.     //-----------------------------------------------------------------------
  177.     /**
  178.      * Obtains a {@code CopticDate} representing a date in the Coptic calendar
  179.      * system from the proleptic-year and day-of-year fields.
  180.      * <p>
  181.      * This returns a {@code CopticDate} with the specified fields.
  182.      * The day must be valid for the year, otherwise an exception will be thrown.
  183.      *
  184.      * @param prolepticYear  the Coptic proleptic-year
  185.      * @param dayOfYear  the Coptic day-of-year, from 1 to 366
  186.      * @return the date in Coptic calendar system, not null
  187.      * @throws DateTimeException if the value of any field is out of range,
  188.      *  or if the day-of-year is invalid for the year
  189.      */
  190.     static CopticDate ofYearDay(int prolepticYear, int dayOfYear) {
  191.         CopticChronology.YEAR_RANGE.checkValidValue(prolepticYear, YEAR);
  192.         DAY_OF_YEAR.range().checkValidValue(dayOfYear, DAY_OF_YEAR);
  193.         if (dayOfYear == 366 && CopticChronology.INSTANCE.isLeapYear(prolepticYear) == false) {
  194.             throw new DateTimeException("Invalid date 'Nasie 6' as '" + prolepticYear + "' is not a leap year");
  195.         }
  196.         return new CopticDate(prolepticYear, (dayOfYear - 1) / 30 + 1, (dayOfYear - 1) % 30 + 1);
  197.     }

  198.     /**
  199.      * Obtains a {@code CopticDate} representing a date in the Coptic calendar
  200.      * system from the epoch-day.
  201.      *
  202.      * @param epochDay  the epoch day to convert based on 1970-01-01 (ISO)
  203.      * @return the date in Coptic calendar system, not null
  204.      * @throws DateTimeException if the epoch-day is out of range
  205.      */
  206.     static CopticDate ofEpochDay(final long epochDay) {
  207.         EPOCH_DAY.range().checkValidValue(epochDay, EPOCH_DAY);  // validate outer bounds
  208.         long copticED = epochDay + EPOCH_DAY_DIFFERENCE;
  209.         int adjustment = 0;
  210.         if (copticED < 0) {
  211.             copticED = copticED + (1461L * (1_000_000L / 4));
  212.             adjustment = -1_000_000;
  213.         }
  214.         int prolepticYear = (int) (((copticED * 4) + 1463) / 1461);
  215.         int startYearEpochDay = (prolepticYear - 1) * 365 + (prolepticYear / 4);
  216.         int doy0 = (int) (copticED - startYearEpochDay);
  217.         int month = doy0 / 30 + 1;
  218.         int dom = doy0 % 30 + 1;
  219.         return new CopticDate(prolepticYear + adjustment, month, dom);
  220.     }

  221.     private static CopticDate resolvePreviousValid(int prolepticYear, int month, int day) {
  222.         if (month == 13 && day > 5) {
  223.             day = CopticChronology.INSTANCE.isLeapYear(prolepticYear) ? 6 : 5;
  224.         }
  225.         return new CopticDate(prolepticYear, month, day);
  226.     }

  227.     /**
  228.      * Creates a {@code CopticDate} validating the input.
  229.      *
  230.      * @param prolepticYear  the Coptic proleptic-year
  231.      * @param month  the Coptic month-of-year, from 1 to 13
  232.      * @param dayOfMonth  the Coptic day-of-month, from 1 to 30
  233.      * @return the date in Coptic calendar system, not null
  234.      * @throws DateTimeException if the value of any field is out of range,
  235.      *  or if the day-of-month is invalid for the month-year
  236.      */
  237.     static CopticDate create(int prolepticYear, int month, int dayOfMonth) {
  238.         CopticChronology.YEAR_RANGE.checkValidValue(prolepticYear, YEAR);
  239.         CopticChronology.MOY_RANGE.checkValidValue(month, MONTH_OF_YEAR);
  240.         CopticChronology.DOM_RANGE.checkValidValue(dayOfMonth, DAY_OF_MONTH);
  241.         if (month == 13 && dayOfMonth > 5) {
  242.             if (CopticChronology.INSTANCE.isLeapYear(prolepticYear)) {
  243.                 if (dayOfMonth > 6) {
  244.                     throw new DateTimeException("Invalid date 'Nasie " + dayOfMonth + "', valid range from 1 to 5, or 1 to 6 in a leap year");
  245.                 }
  246.             } else {
  247.                 if (dayOfMonth == 6) {
  248.                     throw new DateTimeException("Invalid date 'Nasie 6' as '" + prolepticYear + "' is not a leap year");
  249.                 } else {
  250.                     throw new DateTimeException("Invalid date 'Nasie " + dayOfMonth + "', valid range from 1 to 5, or 1 to 6 in a leap year");
  251.                 }
  252.             }
  253.         }
  254.         return new CopticDate(prolepticYear, month, dayOfMonth);
  255.     }

  256.     //-----------------------------------------------------------------------
  257.     /**
  258.      * Creates an instance from validated data.
  259.      *
  260.      * @param prolepticYear  the Coptic proleptic-year
  261.      * @param month  the Coptic month, from 1 to 13
  262.      * @param dayOfMonth  the Coptic day-of-month, from 1 to 30
  263.      */
  264.     private CopticDate(int prolepticYear, int month, int dayOfMonth) {
  265.         this.prolepticYear = prolepticYear;
  266.         this.month = (short) month;
  267.         this.day = (short) dayOfMonth;
  268.     }

  269.     /**
  270.      * Validates the object.
  271.      *
  272.      * @return the resolved date, not null
  273.      */
  274.     private Object readResolve() {
  275.         return CopticDate.create(prolepticYear, month, day);
  276.     }

  277.     //-----------------------------------------------------------------------
  278.     @Override
  279.     int getEpochDayDifference() {
  280.         return EPOCH_DAY_DIFFERENCE;
  281.     }

  282.     @Override
  283.     int getProlepticYear() {
  284.         return prolepticYear;
  285.     }

  286.     @Override
  287.     int getMonth() {
  288.         return month;
  289.     }

  290.     @Override
  291.     int getDayOfMonth() {
  292.         return day;
  293.     }

  294.     @Override
  295.     CopticDate resolvePrevious(int newYear, int newMonth, int dayOfMonth) {
  296.         return resolvePreviousValid(newYear, newMonth, dayOfMonth);
  297.     }

  298.     //-----------------------------------------------------------------------
  299.     /**
  300.      * Gets the chronology of this date, which is the Coptic calendar system.
  301.      * <p>
  302.      * The {@code Chronology} represents the calendar system in use.
  303.      * The era and other fields in {@link ChronoField} are defined by the chronology.
  304.      *
  305.      * @return the Coptic chronology, not null
  306.      */
  307.     @Override
  308.     public CopticChronology getChronology() {
  309.         return CopticChronology.INSTANCE;
  310.     }

  311.     /**
  312.      * Gets the era applicable at this date.
  313.      * <p>
  314.      * The Coptic calendar system has two eras, 'AM' and 'BEFORE_AM',
  315.      * defined by {@link CopticEra}.
  316.      *
  317.      * @return the era applicable at this date, not null
  318.      */
  319.     @Override
  320.     public CopticEra getEra() {
  321.         return (prolepticYear >= 1 ? CopticEra.AM : CopticEra.BEFORE_AM);
  322.     }

  323.     //-------------------------------------------------------------------------
  324.     @Override
  325.     public CopticDate with(TemporalAdjuster adjuster) {
  326.         return (CopticDate) adjuster.adjustInto(this);
  327.     }

  328.     @Override
  329.     public CopticDate with(TemporalField field, long newValue) {
  330.         return (CopticDate) super.with(field, newValue);
  331.     }

  332.     //-----------------------------------------------------------------------
  333.     @Override
  334.     public CopticDate plus(TemporalAmount amount) {
  335.         return (CopticDate) amount.addTo(this);
  336.     }

  337.     @Override
  338.     public CopticDate plus(long amountToAdd, TemporalUnit unit) {
  339.         return (CopticDate) super.plus(amountToAdd, unit);
  340.     }

  341.     @Override
  342.     public CopticDate minus(TemporalAmount amount) {
  343.         return (CopticDate) amount.subtractFrom(this);
  344.     }

  345.     @Override
  346.     public CopticDate minus(long amountToSubtract, TemporalUnit unit) {
  347.         return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit));
  348.     }

  349.     //-------------------------------------------------------------------------
  350.     @Override  // for covariant return type
  351.     @SuppressWarnings("unchecked")
  352.     public ChronoLocalDateTime<CopticDate> atTime(LocalTime localTime) {
  353.         return (ChronoLocalDateTime<CopticDate>) super.atTime(localTime);
  354.     }

  355.     @Override
  356.     public long until(Temporal endExclusive, TemporalUnit unit) {
  357.         return super.until(CopticDate.from(endExclusive), unit);
  358.     }

  359.     @Override
  360.     public ChronoPeriod until(ChronoLocalDate endDateExclusive) {
  361.         return super.doUntil(CopticDate.from(endDateExclusive));
  362.     }

  363. }