/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (AncientJulianLeapYears.java) is part of project Time4J.
*
* Time4J is free software: You can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* Time4J is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Time4J. If not, see <http://www.gnu.org/licenses/>.
* -----------------------------------------------------------------------
*/
package net.time4j.history;
import java.util.Arrays;
/**
* <p>Represents a historic leap year pattern for the early days of julian calendar before AD 8. </p>
*
* <p>The real historic leap years as triennal pattern before AD 8 are not yet known for certainty and are
* controversely debated among historicians. Anyway, all non-proleptic patterns start the julian calendar
* in 45 BC and are to be considered invalid before that year. These patterns mainly serve for comparison
* and must not be interpreted as absolute truth. </p>
*
* @author Meno Hochschild
* @since 3.11/4.8
*/
/*[deutsch]
* <p>Repräsentiert eine historische Schaltjahrsequenz für die ersten Jahre
* des julianischen Kalenders vor AD 8. </p>
*
* <p>Die realen historischen Schaltjahre vor AD 8 (im 3-Jahresrhythmus) sind noch nicht mit Sicherheit
* bekannt und unter Historikern umstritten. Wie auch immer, alle nicht-proleptischen Sequenzen lassen den
* julianischen Kalender erst im Jahre 45 BC beginnen und sind davor ungültig. Diese Sequenzen dienen
* hauptsächlich dem Vergleich und dürfen nicht als absolute Wahrheit interpretiert werden. </p>
*
* @author Meno Hochschild
* @since 3.11/4.8
*/
public final class AncientJulianLeapYears {
//~ Statische Felder/Initialisierungen --------------------------------
private static final int[] SEQUENCE_SCALIGER = {42, 39, 36, 33, 30, 27, 24, 21, 18, 15, 12, 9};
private static final HistoricDate AD8 = HistoricDate.of(HistoricEra.AD, 8, 1, 1);
private static final HistoricDate BC45 = HistoricDate.of(HistoricEra.BC, 45, 1, 1);
private static final long MJD_OF_AD8 = -676021L; // CalendarAlgorithm.JULIAN.toMJD(AD8);
/**
* <p>Proposed by Joseph Justus Scaliger in year 1583, the leap years are assumed to be
* 42, 39, 36, 33, 30, 27, 24, 21, 18, 15, 12, 9 (BC). </p>
*
* <p>This is the most widely used assumption among historicians. </p>
*/
/*[deutsch]
* <p>Von Joseph Justus Scaliger im Jahre 1583 vorgeschlagen: Die Schaltjahre vor AD 8 werden
* als 42, 39, 36, 33, 30, 27, 24, 21, 18, 15, 12, 9 (BC) angenommen. </p>
*
* <p>Das ist die am weitesten verbreitete Vermutung unter Historikern. </p>
*/
public static final AncientJulianLeapYears SCALIGER = new AncientJulianLeapYears(SEQUENCE_SCALIGER);
//~ Instanzvariablen --------------------------------------------------
private final int[] leaps;
private final Calculus calculus;
//~ Konstruktoren -----------------------------------------------------
private AncientJulianLeapYears(final int... values) {
super();
int[] buffer = new int[values.length];
for (int i = 0; i < values.length; i++) {
buffer[i] = 1 - values[i];
}
Arrays.sort(buffer);
this.leaps = buffer;
if (this.leaps.length == 0) {
throw new IllegalArgumentException("Missing leap years.");
} else if ((this.leaps[0] < -44) || (this.leaps[this.leaps.length - 1] >= 8)) {
throw new IllegalArgumentException("Out of range: " + Arrays.toString(values));
}
int previous = buffer[0];
for (int i = 1; i < values.length; i++) {
if (buffer[i] == previous) {
throw new IllegalArgumentException("Contains duplicates: " + Arrays.toString(values));
}
previous = buffer[i];
}
this.calculus =
new Calculus() {
@Override
public long toMJD(HistoricDate date) {
if (date.compareTo(AD8) >= 0) {
return CalendarAlgorithm.JULIAN.toMJD(date);
} else if (date.compareTo(BC45) < 0) {
throw new IllegalArgumentException("Not valid before 45 BC: " + date);
}
long mjd = MJD_OF_AD8;
int target = getProlepticYear(date);
for (int year = 7; year >= target; year--) {
if (isLeapYear(year)) {
mjd -= 366;
} else {
mjd -= 365;
}
}
for (int month = 1; month < date.getMonth(); month++) {
mjd += this.getMaximumDayOfMonth(target, month);
}
return mjd + date.getDayOfMonth() - 1;
}
@Override
public HistoricDate fromMJD(long mjd) {
if (mjd >= MJD_OF_AD8) {
return CalendarAlgorithm.JULIAN.fromMJD(mjd);
}
long test = MJD_OF_AD8;
for (int year = 7; year >= -44; year--) {
if (isLeapYear(year)) {
test -= 366;
} else {
test -= 365;
}
if (test <= mjd) {
for (int month = 1; month <= 12; month++) {
int len = this.getMaximumDayOfMonth(year, month);
if (test + len > mjd) {
HistoricEra era = ((year <= 0) ? HistoricEra.BC : HistoricEra.AD);
int yearOfEra = ((year <= 0) ? 1 - year : year);
return HistoricDate.of(era, yearOfEra, month, (int) (mjd - test + 1));
} else {
test += len;
}
}
}
}
throw new IllegalArgumentException("Not valid before 45 BC: " + mjd);
}
@Override
public boolean isValid(HistoricDate date) {
if (date != null) {
int y = getProlepticYear(date);
if (y >= -44) { // 45 BC
if (y >= 8) {
return CalendarAlgorithm.JULIAN.isValid(date);
} else {
return date.getDayOfMonth() <= this.getMaximumDayOfMonth(y, date.getMonth());
}
}
}
return false;
}
@Override
public int getMaximumDayOfMonth(HistoricDate date) {
if (date.compareTo(AD8) >= 0) {
return CalendarAlgorithm.JULIAN.getMaximumDayOfMonth(date);
} else if (date.compareTo(BC45) < 0) {
throw new IllegalArgumentException("Not valid before 45 BC: " + date);
} else {
return this.getMaximumDayOfMonth(getProlepticYear(date), date.getMonth());
}
}
private int getMaximumDayOfMonth(
int prolepticYear,
int month
) {
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
return 31;
case 4:
case 6:
case 9:
case 11:
return 30;
case 2:
return (this.isLeapYear(prolepticYear) ? 29 : 28);
default:
throw new IllegalArgumentException("Invalid month: " + month);
}
}
private int getProlepticYear(HistoricDate date) {
return date.getEra().annoDomini(date.getYearOfEra());
}
private boolean isLeapYear(int prolepticYear) {
return (Arrays.binarySearch(AncientJulianLeapYears.this.leaps, prolepticYear) >= 0);
}
};
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Creates a new sequence of historical julian leap years before AD 8. </p>
*
* <p>Example: In order to model the proposal of Matzat (1883), users can use the code
* {@code of(44, 41, 38, 35, 32, 29, 26, 23, 20, 17, 14, 11, -3)}. The last parameter {@code -3}
* stands here for the year AD 4 using the formula {@code 1 - year} which shall also be
* a leap year in the version of Matzat. For an overview about different proposals see
* <a href="http://en.wikipedia.org/wiki/Julian_calendar">Wikipedia</a>. </p>
*
* @param bcYears positive numbers for BC-years
* @return new instance
* @throws IllegalArgumentException if given years are missing or out of range {@code BC 45 <= bcYear < AD 8}
* @since 3.11/4.8
*/
/*[deutsch]
* <p>Erzeugt eine neue historische Sequenz von julianischen Schaltjahren vor dem Jahre AD 8. </p>
*
* <p>Beispiel: Um den Vorschlag von Matzat (1883) zu modellieren, können Anwender den Ausdruck
* {@code of(44, 41, 38, 35, 32, 29, 26, 23, 20, 17, 14, 11, -3)} verwenden. Der letzte Parameter
* {@code -3} steht hier für das Jahr AD 4 (Formel: {@code 1 - year}), das laut Matzat auch ein
* Schaltjahr gewesen sein soll. Eine Übersicht der verschiedenen Versionen und Vorschläge
* gibt es auf <a href="http://en.wikipedia.org/wiki/Julian_calendar">Wikipedia</a>. </p>
*
* @param bcYears positive numbers for BC-years
* @return new instance
* @throws IllegalArgumentException if given years are missing or out of range {@code BC 45 <= bcYear < AD 8}
* @since 3.11/4.8
*/
public static AncientJulianLeapYears of(int... bcYears) {
if (Arrays.equals(bcYears, SEQUENCE_SCALIGER)){
return SCALIGER;
}
return new AncientJulianLeapYears(bcYears);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof AncientJulianLeapYears) {
AncientJulianLeapYears that = (AncientJulianLeapYears) obj;
return this.leaps == that.leaps;
} else {
return false;
}
}
@Override
public int hashCode() {
return Arrays.hashCode(this.leaps);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < this.leaps.length; i++) {
if (i > 0) {
sb.append(", ");
}
int bcYear = 1 - this.leaps[i];
if (bcYear > 0) {
sb.append("BC ");
sb.append(bcYear);
} else {
sb.append("AD ");
sb.append(this.leaps[i]);
}
}
return sb.toString();
}
/**
* <p>Returns the leap years in ascending order (as extended years). </p>
*
* @return leap years in ascending order (usually negative numbers)
*/
int[] getPattern() {
return this.leaps;
}
/**
* <p>Creates a suitable calculation engine based on {@code CalendarAlgorithm.JULIAN}. </p>
*
* @return Calculus
*/
Calculus getCalculus() {
return this.calculus;
}
}