package com.andegna.chrono; import java.time.DateTimeException; import java.time.LocalDate; import java.time.chrono.AbstractChronology; import java.time.chrono.Era; import java.time.temporal.ChronoField; import static java.time.temporal.ChronoField.EPOCH_DAY; import java.time.temporal.TemporalAccessor; import java.time.temporal.ValueRange; import java.util.Arrays; import java.util.List; import org.ethiopic.EthiopicCalendar; public class EthiopianChronology extends AbstractChronology { public static final float VERSION = 0.1f; // The underlying calender converter with the worst API ever private static final EthiopicCalendar ETHIOPIC_CALENDAR = new EthiopicCalendar(); /** * Singleton instance EthiopianChronology */ public static final EthiopianChronology INSTANCE = new EthiopianChronology(); // prevent public instantiation private EthiopianChronology() { } /** * Returns unique identifier to {@link EthiopianChronology}. * * @return String */ @Override public String getId() { return "Ethiopian"; } /** * {@inheritDoc} * * @return String */ @Override public String getCalendarType() { /* FIXME: search for ethiopian calendar type identifier defined by the * CLDR and Unicode Locale Data Markup Language(LDML) specifications */ return null; } /** * {@inheritDoc} * * @param prolepticYear * @param month * @param dayOfMonth * @return {@link EthiopianDate} */ @Override public EthiopianDate date(int prolepticYear, int month, int dayOfMonth) { return new EthiopianDate(EthiopianEra.era(prolepticYear), Math.abs(prolepticYear), month, dayOfMonth); } /** * {@inheritDoc} <br /> * <hr /> * <b>Warning</b> Technical Detail * <hr /> * * All Ethiopian months have 30 days except pagume <br /> * if we just simply divide the month by 30 and add 1 we will get the month * <br /> * and the <code>dayOfMonth</code> is simply <code>dayOfYear</code> modular * 30 <br /> * <pre> * eg1:- Let assume Hidar 29 * dayOfYear = meskerem + tikimt + 29 ken * = 30 + 30 + 29 * = 89 * means hidar 29 is the 89th day of the year * eg2:- Let say some one pass 89 as the dayOfYear * month = 89 / 30 + 1 --> int division * = 2 + 1 month * = 3 => Hidar dayOfMonth = 89 % 30 = 29 * </pre> * * @param prolepticYear * @param dayOfYear * @return {@link EthiopianDate} */ @Override public EthiopianDate dateYearDay(int prolepticYear, int dayOfYear) { int month = dayOfYear / 30 + 1; int dayOfMonth = dayOfYear % 30; return date(prolepticYear, month, dayOfMonth); } /** * {@inheritDoc} * * @param epochDay * @return {@link EthiopianDate} */ @Override public EthiopianDate dateEpochDay(long epochDay) { // first get the IsoLocalDate from the epoch LocalDate date = LocalDate.ofEpochDay(epochDay); // second get the year, monthOfYear and dayOfYear int year = date.get(ChronoField.YEAR); int monthOfYear = date.get(ChronoField.MONTH_OF_YEAR); int dayOfYear = date.get(ChronoField.DAY_OF_MONTH); // third convert the iso date to ethiopic int[] ethiopianDate = ETHIOPIC_CALENDAR.gregorianToEthiopic( year, monthOfYear, dayOfYear); // fourth call the date method with the returned value // the returnd array hold 3 values in the year,month and day sequence return date(ethiopianDate[0], ethiopianDate[1], ethiopianDate[2]); } /** * {@inheritDoc} * * @param temporal * @return {@link EthiopianDate} */ @Override public EthiopianDate date(TemporalAccessor temporal) { /* if the temporal object is an instance of EthiopianDate, just cast and return it */ if (temporal instanceof EthiopianDate) { return (EthiopianDate) temporal; } /* else get the epoch day of the temporal class and create to return an EthiopianDate object */ return dateEpochDay(temporal.getLong(EPOCH_DAY)); } /** * {@inheritDoc} * * @param prolepticYear * @return */ @Override public boolean isLeapYear(long prolepticYear) { // true if the year is the last of the 4 year cycle // eg:- 1999 % 4 = 3 --> leap year return prolepticYear % 4 == 3; } @Override public int prolepticYear(Era era, int yearOfEra) { // check the era is for this chronology if (era instanceof EthiopianEra) { throw new ClassCastException("Era must be EthiopianEra"); } // if the era is AMETE_ALEM multiply the year by -1 return EthiopianEra.AMETE_ALEM.equals(era) ? -1 * yearOfEra : yearOfEra; } /** * {@inheritDoc} * * @param eraValue * @return */ @Override public EthiopianEra eraOf(int eraValue) { return EthiopianEra.eraOf(eraValue); } /** * {@inheritDoc} * * @return */ @Override public List<Era> eras() { return Arrays.<Era>asList(EthiopianEra.values()); } /** * {@inheritDoc} * * @param field * @return */ @Override public ValueRange range(ChronoField field) { if (field instanceof ChronoField) { ChronoField f = field; switch (f) { case DAY_OF_MONTH: return ValueRange.of(1, 5, 30); case MONTH_OF_YEAR: return ValueRange.of(1, 13); default: return field.range(); } } return field.range(); } /** * Convert {@link EthiopianDate} to {@link LocalDate} * * @param date * @return {@link LocalDate} */ public LocalDate toGregorian(EthiopianDate date) { int[] gregorian = ETHIOPIC_CALENDAR.ethiopicToGregorian( date.getYear(), date.getMonth(), date.getDay(), ((EthiopianEra) date.getEra()).getEpochOffset()); return LocalDate.of(gregorian[0], gregorian[1], gregorian[2]); } }