org.threeten.extra.chrono.PaxDate Maven / Gradle / Ivy
Show all versions of threeten-extra Show documentation
/*
* Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of JSR-310 nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.threeten.extra.chrono;
import static java.time.temporal.ChronoField.DAY_OF_MONTH;
import static java.time.temporal.ChronoField.DAY_OF_YEAR;
import static java.time.temporal.ChronoField.EPOCH_DAY;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
import static java.time.temporal.ChronoField.YEAR;
import static org.threeten.extra.chrono.PaxChronology.DAYS_IN_MONTH;
import static org.threeten.extra.chrono.PaxChronology.DAYS_IN_WEEK;
import static org.threeten.extra.chrono.PaxChronology.DAYS_IN_YEAR;
import static org.threeten.extra.chrono.PaxChronology.MONTHS_IN_YEAR;
import static org.threeten.extra.chrono.PaxChronology.WEEKS_IN_LEAP_MONTH;
import static org.threeten.extra.chrono.PaxChronology.WEEKS_IN_MONTH;
import static org.threeten.extra.chrono.PaxChronology.WEEKS_IN_YEAR;
import java.io.Serializable;
import java.time.Clock;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoPeriod;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAmount;
import java.time.temporal.TemporalField;
import java.time.temporal.TemporalQuery;
import java.time.temporal.TemporalUnit;
import java.time.temporal.ValueRange;
/**
* A date in the Pax calendar system.
*
* This date operates using the {@linkplain PaxChronology Pax calendar}.
* This calendar system is a proposed reform calendar system, and is not in common use.
* The Pax differs from the Gregorian in terms of month count and length, and the leap year rule.
* Dates are aligned such that {@code 0001-01-01 (Pax)} is {@code 0000-12-31 (ISO)}.
*
* More information is available in the Pax Calendar Wikipedia article.
*
*
Implementation Requirements
* This class is immutable and thread-safe.
*
* This class must be treated as a value type. Do not synchronize, rely on the
* identity hash code or use the distinction between equals() and ==.
*/
public final class PaxDate
extends AbstractDate
implements ChronoLocalDate, Serializable {
/**
* Serialization version.
*/
private static final long serialVersionUID = -2229133057743750072L;
/**
* The difference between the ISO and Pax epoch day count (Pax 0001-01-01 to ISO 1970-01-01).
*/
private static final int PAX_0001_TO_ISO_1970 = 719163;
/**
* The days per 400 year cycle.
*/
private static final int DAYS_PER_LONG_CYCLE = (DAYS_IN_YEAR * 400) + (DAYS_IN_WEEK * 71);
/**
* The days per 100 year cycle.
*/
private static final int DAYS_PER_CYCLE = (DAYS_IN_YEAR * 100) + (DAYS_IN_WEEK * 18);
/**
* The days per 6 year cycle.
*/
private static final int DAYS_PER_SIX_CYCLE = (DAYS_IN_YEAR * 6) + (DAYS_IN_WEEK * 1);
/**
* Number of years in a decade.
*/
private static final int YEARS_IN_DECADE = 10;
/**
* Number of years in a century.
*/
private static final int YEARS_IN_CENTURY = 100;
/**
* Number of years in a millennium.
*/
private static final int YEARS_IN_MILLENNIUM = 1000;
/**
* The proleptic year.
*/
private final int prolepticYear;
/**
* The month.
*/
private final short month;
/**
* The day.
*/
private final short day;
//-----------------------------------------------------------------------
/**
* Obtains the current {@code PaxDate} from the system clock in the default time-zone.
*
* This will query the {@link Clock#systemDefaultZone() system clock} in the default
* time-zone to obtain the current date.
*
* Using this method will prevent the ability to use an alternate clock for testing
* because the clock is hard-coded.
*
* @return the current date using the system clock and default time-zone, not null
*/
public static PaxDate now() {
return now(Clock.systemDefaultZone());
}
/**
* Obtains the current {@code PaxDate} from the system clock in the specified time-zone.
*
* This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date.
* Specifying the time-zone avoids dependence on the default time-zone.
*
* Using this method will prevent the ability to use an alternate clock for testing
* because the clock is hard-coded.
*
* @param zone the zone ID to use, not null
* @return the current date using the system clock, not null
*/
public static PaxDate now(ZoneId zone) {
return now(Clock.system(zone));
}
/**
* Obtains the current {@code PaxDate} from the specified clock.
*
* This will query the specified clock to obtain the current date - today.
* Using this method allows the use of an alternate clock for testing.
* The alternate clock may be introduced using {@linkplain Clock dependency injection}.
*
* @param clock the clock to use, not null
* @return the current date, not null
* @throws DateTimeException if the current date cannot be obtained
*/
public static PaxDate now(Clock clock) {
LocalDate now = LocalDate.now(clock);
return PaxDate.ofEpochDay(now.toEpochDay());
}
/**
* Obtains a {@code PaxDate} representing a date in the Pax calendar
* system from the proleptic-year, month-of-year and day-of-month fields.
*
* This returns a {@code PaxDate} with the specified fields.
* The day must be valid for the year and month, otherwise an exception will be thrown.
*
* @param prolepticYear the Pax proleptic-year
* @param month the Pax month-of-year, from 1 to 14
* @param dayOfMonth the Pax day-of-month, from 1 to 28
* @return the date in Pax calendar system, not null
* @throws DateTimeException if the value of any field is out of range,
* or if the day-of-month is invalid for the month-year
*/
public static PaxDate of(int prolepticYear, int month, int dayOfMonth) {
YEAR.checkValidValue(prolepticYear);
PaxChronology.MONTH_OF_YEAR_RANGE.checkValidValue(month, MONTH_OF_YEAR);
PaxChronology.DAY_OF_MONTH_RANGE.checkValidValue(dayOfMonth, DAY_OF_MONTH);
if (month == MONTHS_IN_YEAR + 1 && !PaxChronology.INSTANCE.isLeapYear(prolepticYear)) {
throw new DateTimeException("Invalid month 14 as " + prolepticYear + "is not a leap year");
}
if (dayOfMonth > DAYS_IN_WEEK && month == MONTHS_IN_YEAR && PaxChronology.INSTANCE.isLeapYear(prolepticYear)) {
throw new DateTimeException("Invalid date during Pax as " + prolepticYear + " is a leap year");
}
return new PaxDate(prolepticYear, month, dayOfMonth);
}
/**
* Obtains a {@code PaxDate} from a temporal object.
*
* This obtains a date in the Pax calendar system based on the specified temporal.
* A {@code TemporalAccessor} represents an arbitrary set of date and time information,
* which this factory converts to an instance of {@code PaxDate}.
*
* The conversion typically uses the {@link ChronoField#EPOCH_DAY EPOCH_DAY}
* field, which is standardized across calendar systems.
*
* This method matches the signature of the functional interface {@link TemporalQuery}
* allowing it to be used as a query via method reference, {@code PaxDate::from}.
*
* @param temporal the temporal object to convert, not null
* @return the date in the Pax calendar system, not null
* @throws DateTimeException if unable to convert to a {@code PaxDate}
*/
public static PaxDate from(TemporalAccessor temporal) {
if (temporal instanceof PaxDate) {
return (PaxDate) temporal;
}
return PaxDate.ofEpochDay(temporal.getLong(EPOCH_DAY));
}
//-----------------------------------------------------------------------
/**
* Obtains a {@code PaxDate} representing a date in the Pax calendar
* system from the proleptic-year and day-of-year fields.
*
* This returns a {@code PaxDate} with the specified fields.
* The day must be valid for the year, otherwise an exception will be thrown.
*
* @param prolepticYear the Pax proleptic-year
* @param dayOfYear the Pax day-of-year, from 1 to 371
* @return the date in Pax calendar system, not null
* @throws DateTimeException if the value of any field is out of range,
* or if the day-of-year is invalid for the year
*/
static PaxDate ofYearDay(int prolepticYear, int dayOfYear) {
YEAR.checkValidValue(prolepticYear);
PaxChronology.DAY_OF_YEAR_RANGE.checkValidValue(dayOfYear, DAY_OF_YEAR);
boolean leap = PaxChronology.INSTANCE.isLeapYear(prolepticYear);
if (dayOfYear > DAYS_IN_YEAR && !leap) {
throw new DateTimeException("Invalid date 'DayOfYear " + dayOfYear + "' as '" + prolepticYear + "' is not a leap year");
}
int month = ((dayOfYear - 1) / DAYS_IN_MONTH) + 1;
// In leap years, the leap-month is shorter than the following month, so needs to be adjusted.
if (leap && month == MONTHS_IN_YEAR && dayOfYear >= (DAYS_IN_YEAR + DAYS_IN_WEEK) - DAYS_IN_MONTH + 1) {
month++;
}
// Subtract days-at-start-of-month from days in year
int dayOfMonth = dayOfYear - (month - 1) * DAYS_IN_MONTH;
// Adjust for shorter inserted leap-month.
if (month == MONTHS_IN_YEAR + 1) {
dayOfMonth += (DAYS_IN_MONTH - DAYS_IN_WEEK);
}
return of(prolepticYear, month, dayOfMonth);
}
/**
* Obtains a {@code PaxDate} representing a date in the Pax calendar
* system from the epoch-day.
*
* @param epochDay the epoch day to convert based on 1970-01-01 (ISO)
* @return the date in Pax calendar system, not null
* @throws DateTimeException if the epoch-day is out of range
*/
static PaxDate ofEpochDay(long epochDay) {
EPOCH_DAY.range().checkValidValue(epochDay, EPOCH_DAY);
// use of Pax 0001 makes non-leap century at end of (long) cycle.
long paxEpochDay = epochDay + PAX_0001_TO_ISO_1970;
int longCycle = (int) Math.floorDiv(paxEpochDay, DAYS_PER_LONG_CYCLE);
int cycle = (int) (paxEpochDay - longCycle * DAYS_PER_LONG_CYCLE) / DAYS_PER_CYCLE;
int dayOfCycle = (int) Math.floorMod(paxEpochDay - longCycle * DAYS_PER_LONG_CYCLE, DAYS_PER_CYCLE);
if (dayOfCycle >= DAYS_PER_CYCLE - DAYS_IN_YEAR - DAYS_IN_WEEK) {
// Is in the century year
int dayOfYear = dayOfCycle - (DAYS_PER_CYCLE - DAYS_IN_YEAR - DAYS_IN_WEEK) + 1;
return ofYearDay(longCycle * (4 * YEARS_IN_CENTURY) + cycle * YEARS_IN_CENTURY + YEARS_IN_CENTURY, dayOfYear);
}
// For negative years, the cycle of leap years runs the other direction for 99s and 6s.
if (paxEpochDay >= 0) {
if (dayOfCycle >= DAYS_PER_CYCLE - 2 * DAYS_IN_YEAR - 2 * DAYS_IN_WEEK) {
// Is in the '99 year
int dayOfYear = dayOfCycle - (DAYS_PER_CYCLE - 2 * DAYS_IN_YEAR - 2 * DAYS_IN_WEEK) + 1;
return ofYearDay(longCycle * (4 * YEARS_IN_CENTURY) + cycle * YEARS_IN_CENTURY + (YEARS_IN_CENTURY - 1), dayOfYear);
}
// Otherwise, part of the regular 6-year cycle.
int sixCycle = dayOfCycle / DAYS_PER_SIX_CYCLE;
int dayOfSixCycle = dayOfCycle % DAYS_PER_SIX_CYCLE;
int year = dayOfSixCycle / DAYS_IN_YEAR + 1;
int dayOfYear = dayOfSixCycle % DAYS_IN_YEAR + 1;
if (year == 7) {
year--;
dayOfYear += DAYS_IN_YEAR;
}
return ofYearDay(longCycle * (4 * YEARS_IN_CENTURY) + cycle * YEARS_IN_CENTURY + sixCycle * 6 + year, dayOfYear);
} else {
if (dayOfCycle < DAYS_IN_YEAR + DAYS_IN_WEEK) {
// -'99 year is at _start_ of cycle (first year encountered).
return ofYearDay(longCycle * (4 * YEARS_IN_CENTURY) + cycle * YEARS_IN_CENTURY + 1, dayOfCycle + 1);
}
// Otherwise, part of the regular 6-year cycle, but offset -'96 to be end of six-year-cycle first.
int offsetCycle = dayOfCycle + 2 * DAYS_IN_YEAR - DAYS_IN_WEEK;
int sixCycle = offsetCycle / DAYS_PER_SIX_CYCLE;
int dayOfSixCycle = offsetCycle % DAYS_PER_SIX_CYCLE;
int year = dayOfSixCycle / DAYS_IN_YEAR + 1;
int dayOfYear = dayOfSixCycle % DAYS_IN_YEAR + 1;
if (year == 7) {
year--;
dayOfYear += DAYS_IN_YEAR;
}
return ofYearDay(longCycle * (4 * YEARS_IN_CENTURY) + cycle * YEARS_IN_CENTURY - 2 + (sixCycle * 6 + year), dayOfYear);
}
}
private static PaxDate resolvePreviousValid(int prolepticYear, int month, int day) {
int monthR = Math.min(month, MONTHS_IN_YEAR + (PaxChronology.INSTANCE.isLeapYear(prolepticYear) ? 1 : 0));
int dayR = Math.min(day, month == MONTHS_IN_YEAR && PaxChronology.INSTANCE.isLeapYear(prolepticYear) ? DAYS_IN_WEEK : DAYS_IN_MONTH);
return PaxDate.of(prolepticYear, monthR, dayR);
}
/**
* Get the count of leap months since proleptic month 0.
*
* This number is negative if the month is prior to Pax year 0.
**
* Remember that if using this for things like turning months into days, you must first subtract this number from the proleptic month count.
*
* @param prolepticMonth The month.
* @return The number of leap months since proleptic month 0.
*/
private static long getLeapMonthsBefore(long prolepticMonth) {
long offsetMonth = prolepticMonth - (prolepticMonth <= 0 ? 13 : 13 - 1);
// First, see getLeapYearsBefore(...) for explanations.
// return 18 * Math.floorDiv(offsetMonth, 100 * MONTHS_IN_YEAR + (getLeapYearsBefore(100) + 1))
// - Math.floorDiv(offsetMonth, 400 * MONTHS_IN_YEAR + (getLeapYearsBefore(400) + 1))
// + (((Math.floorMod(offsetMonth, 100 * MONTHS_IN_YEAR + (getLeapYearsBefore(100) + 1)) - (offsetMonth <= 0 ? 100 * MONTHS_IN_YEAR + getLeapYearsBefore(100) : 0))
// / (99 * MONTHS_IN_YEAR + (getLeapYearsBefore(99) + 1))) + (offsetMonth <= 0 ? 1 : 0))
// + (Math.floorMod(offsetMonth, 100 * MONTHS_IN_YEAR + (getLeapYearsBefore(100) + 1)) + (offsetMonth <= 0 ? 2 * MONTHS_IN_YEAR - 1 : 0)) / (6 * MONTHS_IN_YEAR + 1);
return 18L * Math.floorDiv(offsetMonth, 1318)
- Math.floorDiv(offsetMonth, 5272)
+ (((Math.floorMod(offsetMonth, 1318) - (offsetMonth <= 0 ? 1317 : 0)) / 1304) + (offsetMonth <= 0 ? 1 : 0))
+ (Math.floorMod(offsetMonth, 1318) + (offsetMonth <= 0 ? 25 : 0)) / 79;
}
/**
* Get the count of leap years since Pax year 0.
*
* This number is negative if the year is prior to Pax year 0.
*
* @param prolepticYear The year.
* @return The number of leap years since Pax year 0.
*/
private static long getLeapYearsBefore(long prolepticYear) {
// Some explanations are in order:
// - First, because this calculates "leap years from 0 to the start of the year",
// The 'current' year must be counted when the year is negative (hence Math.floorDiv(...)).
// - However, this means that there's a negative count which must be offset for the in-century years,
// which still occur at the "last two digits divisible by 6" (or 99) point.
// - Math.floorMod(...) returns a nicely positive result for negative years, counting 'down', but
// thus needs to be offset to make sure the first leap year is -6, and not -4...
// The second line, which calculates the '99 occurrences, runs "backwards", so must first be reversed, then the results flipped.
return 18L * Math.floorDiv(prolepticYear - 1, YEARS_IN_CENTURY) - Math.floorDiv(prolepticYear - 1, 4 * YEARS_IN_CENTURY) +
(Math.floorMod(prolepticYear - 1, 100) - (prolepticYear <= 0 ? 99 : 0)) / 99 + (prolepticYear <= 0 ? 1 : 0) +
((Math.floorMod(prolepticYear - 1, 100) + (prolepticYear <= 0 ? 2 : 0)) / 6);
}
//-----------------------------------------------------------------------
/**
* Creates an instance from validated data.
*
* @param prolepticYear the Pax proleptic-year
* @param month the Pax month, from 1 to 14
* @param dayOfMonth the Pax day-of-month, from 1 to 28
*/
private PaxDate(int prolepticYear, int month, int dayOfMonth) {
this.prolepticYear = prolepticYear;
this.month = (short) month;
this.day = (short) dayOfMonth;
}
/**
* Validates the object.
*
* @return the resolved date, not null
*/
private Object readResolve() {
return PaxDate.of(prolepticYear, month, day);
}
//-----------------------------------------------------------------------
@Override
int getProlepticYear() {
return prolepticYear;
}
@Override
int getMonth() {
return month;
}
@Override
int getDayOfMonth() {
return day;
}
@Override
int getDayOfYear() {
return (month - 1) * DAYS_IN_MONTH
- (month == MONTHS_IN_YEAR + 1 ? DAYS_IN_MONTH - DAYS_IN_WEEK : 0) + getDayOfMonth();
}
@Override
int lengthOfYearInMonths() {
return MONTHS_IN_YEAR + (isLeapYear() ? 1 : 0);
}
@Override
ValueRange rangeAlignedWeekOfMonth() {
return ValueRange.of(1, month == MONTHS_IN_YEAR && isLeapYear() ? WEEKS_IN_LEAP_MONTH : WEEKS_IN_MONTH);
}
@Override
PaxDate resolvePrevious(int newYear, int newMonth, int dayOfMonth) {
return resolvePreviousValid(newYear, newMonth, dayOfMonth);
}
@Override
public ValueRange range(TemporalField field) {
if (field == ChronoField.ALIGNED_WEEK_OF_YEAR) {
return ValueRange.of(1, WEEKS_IN_YEAR + (isLeapYear() ? 1 : 0));
} else if (field == ChronoField.MONTH_OF_YEAR) {
return ValueRange.of(1, MONTHS_IN_YEAR + (isLeapYear() ? 1 : 0));
}
return super.range(field);
}
/**
* Get the proleptic month from the start of the epoch (Pax 0000).
*
* @return The proleptic month.
*/
@Override
long getProlepticMonth() {
return ((long) getProlepticYear()) * MONTHS_IN_YEAR + getLeapYearsBefore(getProlepticYear()) + month - 1;
}
//-----------------------------------------------------------------------
/**
* Gets the chronology of this date, which is the Pax calendar system.
*
* The {@code Chronology} represents the calendar system in use.
* The era and other fields in {@link ChronoField} are defined by the chronology.
*
* @return the Pax chronology, not null
*/
@Override
public PaxChronology getChronology() {
return PaxChronology.INSTANCE;
}
/**
* Gets the era applicable at this date.
*
* The Pax calendar system has two eras, 'CE' and 'BCE',
* defined by {@link PaxEra}.
*
* @return the era applicable at this date, not null
*/
@Override
public PaxEra getEra() {
return (prolepticYear >= 1 ? PaxEra.CE : PaxEra.BCE);
}
/**
* Returns the length of the month represented by this date.
*
* This returns the length of the month in days.
* Month lengths do not match those of the ISO calendar system.
*
* @return the length of the month in days, from 7 to 28
*/
@Override
public int lengthOfMonth() {
switch (month) {
case 13:
return (isLeapYear() ? DAYS_IN_WEEK : DAYS_IN_MONTH);
default:
return DAYS_IN_MONTH;
}
}
@Override
public int lengthOfYear() {
return DAYS_IN_YEAR + (isLeapYear() ? DAYS_IN_WEEK : 0);
}
//-------------------------------------------------------------------------
@Override
public PaxDate with(TemporalAdjuster adjuster) {
return (PaxDate) adjuster.adjustInto(this);
}
@Override
public PaxDate with(TemporalField field, long newValue) {
// Evaluate years as a special case, to deal with inserted leap months.
if (field == ChronoField.YEAR) {
return plusYears(Math.subtractExact(newValue, getProlepticYear()));
}
return (PaxDate) super.with(field, newValue);
}
//-----------------------------------------------------------------------
@Override
public PaxDate plus(TemporalAmount amount) {
return (PaxDate) amount.addTo(this);
}
@Override
public PaxDate plus(long amountToAdd, TemporalUnit unit) {
return (PaxDate) super.plus(amountToAdd, unit);
}
/**
* Returns a copy of this {@code PaxDate} with the specified period in years added.
*
* This method adds the specified amount to the years field in two steps:
*
* - Add the input years to the year field
* - If necessary, shift the index to account for the inserted/deleted leap-month.
*
*
* In the Pax Calendar, the month of December is 13 in non-leap-years, and 14 in leap years.
* Shifting the index of the month thus means the month would still be the same.
*
* In the case of moving from the inserted leap-month (destination year is non-leap), the month index is retained.
* This has the effect of retaining the same day-of-year.
*
* This instance is immutable and unaffected by this method call.
*
* @param yearsToAdd the years to add, may be negative
* @return a {@code PaxDate} based on this date with the years added, not null
* @throws DateTimeException if the result exceeds the supported date range
*/
@Override
PaxDate plusYears(long yearsToAdd) {
if (yearsToAdd == 0) {
return this;
}
int newYear = YEAR.checkValidIntValue(getProlepticYear() + yearsToAdd);
// Retain actual month (not index) in the case where a leap month is to be inserted.
if (month == MONTHS_IN_YEAR && !isLeapYear() && PaxChronology.INSTANCE.isLeapYear(newYear)) {
return of(newYear, MONTHS_IN_YEAR + 1, getDayOfMonth());
}
// Otherwise, one of the following is true:
// 1 - Before the leap month, nothing to do (most common)
// 2 - Both source and destination in leap-month, nothing to do
// 3 - Both source and destination after leap month in leap year, nothing to do
// 4 - Source in leap month, but destination year not leap. Retain month index, preserving day-of-year.
// 5 - Source after leap month, but destination year not leap. Move month index back.
return resolvePreviousValid(newYear, month, day);
}
/**
* Returns a copy of this {@code PaxDate} with the specified period in months added.
*
* This method adds the specified amount to the months field in three steps:
*
* - Add the input months to the month-of-year field
* - Check if the resulting date would be invalid
* - Adjust the day-of-month to the last valid day if necessary
*
*
* For example, 2006-12-13 plus one month would result in the invalid date 2006-13-13.
* Instead of returning an invalid result, the last valid day of the month, 2006-13-07, is selected instead.
*
* This instance is immutable and unaffected by this method call.
*
* @param monthsToAdd the months to add, may be negative
* @return a {@code PaxDate} based on this date with the months added, not null
* @throws DateTimeException if the result exceeds the supported date range
*/
@Override
PaxDate plusMonths(long monthsToAdd) {
if (monthsToAdd == 0) {
return this;
}
long calcMonths = Math.addExact(getProlepticMonth(), monthsToAdd);
// "Regularize" the month count, as if years were all 13 months long.
long monthsRegularized = calcMonths - getLeapMonthsBefore(calcMonths);
int newYear = YEAR.checkValidIntValue(Math.floorDiv(monthsRegularized, MONTHS_IN_YEAR));
int newMonth = Math.toIntExact(calcMonths - ((long) newYear * MONTHS_IN_YEAR + getLeapYearsBefore(newYear)) + 1);
return resolvePreviousValid(newYear, newMonth, getDayOfMonth());
}
@Override
public PaxDate minus(TemporalAmount amount) {
return (PaxDate) amount.subtractFrom(this);
}
@Override
public PaxDate minus(long amountToSubtract, TemporalUnit unit) {
return (amountToSubtract == Long.MIN_VALUE ? plus(Long.MAX_VALUE, unit).plus(1, unit) : plus(-amountToSubtract, unit));
}
//-------------------------------------------------------------------------
@Override // for covariant return type
@SuppressWarnings("unchecked")
public ChronoLocalDateTime atTime(LocalTime localTime) {
return (ChronoLocalDateTime) super.atTime(localTime);
}
@Override
public long until(Temporal endExclusive, TemporalUnit unit) {
return until(PaxDate.from(endExclusive), unit);
}
@Override
long until(AbstractDate end, TemporalUnit unit) {
if (unit instanceof ChronoUnit) {
PaxDate paxEnd = PaxDate.from(end);
switch ((ChronoUnit) unit) {
case YEARS:
return yearsUntil(paxEnd);
case DECADES:
return yearsUntil(paxEnd) / YEARS_IN_DECADE;
case CENTURIES:
return yearsUntil(paxEnd) / YEARS_IN_CENTURY;
case MILLENNIA:
return yearsUntil(paxEnd) / YEARS_IN_MILLENNIUM;
default:
break;
}
}
return super.until(end, unit);
}
/**
* Get the number of years from this date to the given day.
*
* @param end The end date.
* @return The number of years from this date to the given day.
*/
long yearsUntil(PaxDate end) {
// If either date is after the inserted leap month, and the other year isn't leap, simulate the effect of the inserted month.
long startYear = getProlepticYear() * 512L + getDayOfYear() + (this.month == MONTHS_IN_YEAR && !this.isLeapYear() && end.isLeapYear() ? DAYS_IN_WEEK : 0);
long endYear = end.getProlepticYear() * 512L + end.getDayOfYear() + (end.month == MONTHS_IN_YEAR && !end.isLeapYear() && this.isLeapYear() ? DAYS_IN_WEEK : 0);
return (endYear - startYear) / 512L;
}
@Override
public ChronoPeriod until(ChronoLocalDate endDateExclusive) {
PaxDate end = PaxDate.from(endDateExclusive);
int years = Math.toIntExact(yearsUntil(end));
// Get to the same "whole" year.
PaxDate sameYearEnd = this.plusYears(years);
int months = (int) sameYearEnd.monthsUntil(end);
int days = (int) sameYearEnd.plusMonths(months).daysUntil(end);
return getChronology().period(years, months, days);
}
//-----------------------------------------------------------------------
@Override
public long toEpochDay() {
long paxEpochDay = ((long) getProlepticYear() - 1) * DAYS_IN_YEAR + getLeapYearsBefore(getProlepticYear()) * DAYS_IN_WEEK + getDayOfYear() - 1;
return paxEpochDay - PAX_0001_TO_ISO_1970;
}
}