diff --git a/base/src/main/java/net/time4j/calendar/hindu/HinduCalendar.java b/base/src/main/java/net/time4j/calendar/hindu/HinduCalendar.java index bfa72db5d..f399ddf7c 100644 --- a/base/src/main/java/net/time4j/calendar/hindu/HinduCalendar.java +++ b/base/src/main/java/net/time4j/calendar/hindu/HinduCalendar.java @@ -28,6 +28,7 @@ import net.time4j.base.TimeSource; import net.time4j.calendar.CommonElements; import net.time4j.calendar.IndianCalendar; +import net.time4j.calendar.IndianMonth; import net.time4j.calendar.StdCalendarElement; import net.time4j.calendar.astro.GeoLocation; import net.time4j.calendar.astro.SolarTime; @@ -45,6 +46,7 @@ import net.time4j.engine.ChronoDisplay; import net.time4j.engine.ChronoElement; import net.time4j.engine.ChronoEntity; +import net.time4j.engine.ChronoException; import net.time4j.engine.ChronoMerger; import net.time4j.engine.Chronology; import net.time4j.engine.DisplayStyle; @@ -53,16 +55,27 @@ import net.time4j.engine.IntElementRule; import net.time4j.engine.StartOfDay; import net.time4j.engine.ValidationElement; +import net.time4j.engine.VariantSource; import net.time4j.format.Attributes; +import net.time4j.format.CalendarText; import net.time4j.format.CalendarType; +import net.time4j.format.DisplayElement; import net.time4j.format.LocalizedPatternSupport; +import net.time4j.format.NumberSystem; +import net.time4j.format.OutputContext; +import net.time4j.format.TextAccessor; +import net.time4j.format.TextElement; +import net.time4j.format.TextWidth; +import net.time4j.format.internal.DualFormatElement; import net.time4j.tz.TZID; import net.time4j.tz.ZonalOffset; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; +import java.io.ObjectStreamException; import java.math.BigDecimal; +import java.text.ParsePosition; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -129,6 +142,24 @@ public final class HinduCalendar MAX_YEAR + 1, 'y'); + /** + *
Represents the Hindu month.
+ */ + /*[deutsch] + *Repräsentiert den Hindu-Monat.
+ */ + @FormattableElement(format = "M") + public static final TextElementRepresents the Hindu day of month.
+ */ + /*[deutsch] + *Repräsentiert den Hindu-Tag des Monats.
+ */ + @FormattableElement(format = "d") + public static final TextElementRepresents the Hindu day of year.
*/ @@ -187,12 +218,12 @@ public final class HinduCalendar .appendElement( YEAR_OF_ERA, new IntegerRule(YEAR_INDEX)) -// .appendElement( -// MONTH_OF_YEAR, -// new MonthRule()) -// .appendElement( -// DAY_OF_MONTH, -// new IntegerRule(DAY_OF_MONTH_INDEX)) + .appendElement( + MONTH_OF_YEAR, + MonthElement.SINGLETON) + .appendElement( + DAY_OF_MONTH, + DayOfMonthElement.SINGLETON) .appendElement( DAY_OF_YEAR, new IntegerRule(DAY_OF_YEAR_INDEX)) @@ -640,7 +671,7 @@ int getExpiredYearOfKaliYuga() { return this.kyYear; } - private HinduCalendar withNewYear() { + private HinduCalendar withNewYear() { // TODO: ashadha / kartika as begin of year??? Purnimanta??? HinduMonth first = (this.variant.isSolar() ? HinduMonth.ofSolar(1) : HinduMonth.ofLunisolar(1)); HinduCS calsys = this.variant.getCalendarSystem(); HinduCalendar date = calsys.create(this.kyYear, first, HinduDay.valueOf(15)); @@ -705,6 +736,116 @@ private HinduCalendar withAdjustedDayInMonth(HinduDay desired) { return calsys.create(this.kyYear, this.month, dom); } + private static int parseLeadingLeapInfo( + CharSequence text, + int pos, + int len, + boolean caseInsensitive, + String adhika, + char indicator, + Locale loc + ) { + boolean leap = false; + int pp = pos; + int end = pp + adhika.length(); + + if (end < len) { + String s1 = adhika; + String s2 = text.subSequence(pp, end).toString(); + + if (caseInsensitive) { + s1 = s1.toUpperCase(loc); + s2 = s2.toUpperCase(loc); + } + + if (s1.equals(s2)) { + leap = true; + pp = end; + + if ((pp < len) && (text.charAt(pp) == ' ')) { + pp++; + } + } + } + + if (!leap) { + return parseLeapIndicator(text, pos, caseInsensitive, indicator); + } + + return pp; + } + + private static int parseTrailingLeapInfo( + CharSequence text, + int pos, + int len, + boolean caseInsensitive, + String adhika, + char indicator, + Locale loc + ) { + boolean leap = false; + int pp = pos; + int end = pp + adhika.length(); + + if ((end < len) && (text.charAt(pp) == ' ')) { + pp++; + end++; + } + + if (end < len) { + String s1 = adhika; + String s2 = text.subSequence(pp, end).toString(); + + if (caseInsensitive) { + s1 = s1.toUpperCase(loc); + s2 = s2.toUpperCase(loc); + } + + if (s1.equals(s2)) { + leap = true; + pp = end; + } + } + + if (!leap) { + return parseLeapIndicator(text, pos, caseInsensitive, indicator); + } + + return pp; + } + + private static int parseLeapIndicator( + CharSequence text, + int pos, + boolean caseInsensitive, + char indicator + ) { + char c1 = text.charAt(pos); + char c2 = indicator; + + if (caseInsensitive) { + c1 = Character.toUpperCase(c1); + c2 = Character.toUpperCase(c2); + } + + return (c1 == c2) ? pos + 1 : -1; + } + + private static HinduVariant getVariant( + ChronoDisplay context, + AttributeQuery attributes + ) { + if (context instanceof VariantSource) { + return HinduVariant.from(((VariantSource) context).getVariant()); + } else if (attributes.contains(Attributes.CALENDAR_VARIANT)) { + return HinduVariant.from(attributes.get(Attributes.CALENDAR_VARIANT)); + } else { + String s = (context == null) ? "Determines if this day of month is in leap state (intercalated day).
* + *A leap day follows after an ordinary day while a leap month is before the ordinary month.
+ * * @return boolean */ /*[deutsch] *Bestimmt, ob dieser Tag des Monats ein Schalttag ist, also ein eingeschobener Tag mit gleicher Nummer.
* + *Ein Schalttag folgt nach einem normalen Tag gleicher Nummer während ein Schaltmonat dem normalen + * Monat vorausgeht.
+ * * @return boolean */ + @Override public boolean isLeap() { return this.leap; @@ -181,4 +190,42 @@ public String toString() { } + /** + *Uses the comparing order of the lunisolar calendar.
+ * + *Leap days are sorted after days with same number.
+ * + * @param other another month to be compared with + * @return comparing result + */ + /*[deutsch] + *Verwendet die Anordnung der Monate im lunisolaren Kalender.
+ * + *Schalttage werden nach Tagen mit gleicher Nummer einsortiert.
+ * + * @param other another month to be compared with + * @return comparing result + */ + @Override + public int compareTo(HinduDay other) { + + int result = this.value - other.value; + + if (result == 0) { + if (this.leap) { + result = (other.leap ? 0 : 1); + } else { + result = (other.leap ? -1 : 0); + } + } + + return result; + + } + + @Override + public boolean test(HinduCalendar context) { + return this.equals(context.getDayOfMonth()); + } + } diff --git a/base/src/main/java/net/time4j/calendar/hindu/HinduMonth.java b/base/src/main/java/net/time4j/calendar/hindu/HinduMonth.java index 2e4e6c306..f3af8f469 100644 --- a/base/src/main/java/net/time4j/calendar/hindu/HinduMonth.java +++ b/base/src/main/java/net/time4j/calendar/hindu/HinduMonth.java @@ -22,7 +22,12 @@ package net.time4j.calendar.hindu; import net.time4j.calendar.IndianMonth; +import net.time4j.engine.AttributeKey; +import net.time4j.engine.ChronoCondition; +import net.time4j.format.Attributes; import net.time4j.format.CalendarText; +import net.time4j.format.OutputContext; +import net.time4j.format.TextWidth; import java.io.Serializable; import java.util.Locale; @@ -42,7 +47,31 @@ * @since 5.6 */ public final class HinduMonth - implements Serializable { + extends HinduPrimitive + implements ComparableFormat attribute which controls if Rasi names or traditional lunisolar names are used + * for Hindu months in the solar calendar.
+ * + *The default is defined by the Hindu variant in question. For example, Kerala prefers Rasi names + * while most other parts of India use the lunisolar forms.
+ * + * @see net.time4j.format.expert.ChronoFormatter#with(AttributeKey, boolean) + */ + /*[deutsch] + *Formatattribut, das angibt, ob Rasi-Namen oder traditionelle lunisolare Monatsnamen + * im Sonnenkalender verwendet werden sollen.
+ * + *Standard ist, was die jeweilige Hindu-Variante vorgibt. Kerala bevorzugt im Sonnenkalender + * Rasi-Namen, während in den meisten Teilen Indiens lunisolare Namen gebräuchlich sind.
+ * + * @see net.time4j.format.expert.ChronoFormatter#with(AttributeKey, boolean) + */ + public static final AttributeKeyObtains the localized text of solar month corresponding to the Hindu zodiac (Rasi).
* *In many cases however, the lunisolar name is still used and can be obtained by - * {@code getValue().getDisplayName(locale)}.
+ * {@code getDisplayName(locale)}. If this month is in leap status then the localized word + * for "adhika" will be inserted before the name. * * @return String * @see #getRasi() - * @see IndianMonth#getDisplayName(Locale) + * @see #getDisplayName(Locale) */ /*[deutsch] *Liefert den lokalisierten Namen des solaren Monats entsprechend dem Hindu-Tierkreiszeichen (Rasi).
* *In vielen Fällen wird jedoch der lunisolare Name noch verwendet und kann mittels - * {@code getValue().getDisplayName(locale)} ermittelt werden.
+ * {@code getDisplayName(locale)} ermittelt werden. Wenn dieser Monat ein eingeschobener Monat ist + * (Schaltmonat), dann wird das sprachabhängige Wort für "adhika" vor den Namen + * gesetzt. * * @return String * @see #getRasi() - * @see IndianMonth#getDisplayName(Locale) + * @see #getDisplayName(Locale) */ public String getRasi(Locale locale) { CalendarText names = CalendarText.getInstance("extra/hindu", locale); - return names.getTextForms("R", IndianMonth.class).print(IndianMonth.valueOf(this.getRasi())); + String rasi = names.getTextForms("R", IndianMonth.class).print(IndianMonth.valueOf(this.getRasi())); + + if (this.leap) { + rasi = getAdhika(locale) + rasi; + } + + return rasi; + + } + + /** + *Equivalent to the expression + * {@code getDisplayName(locale, TextWidth.WIDE, OutputContext.FORMAT)}.
+ * + *If this month is in leap status then the localized word + * for "adhika" will be inserted before the name.
+ * + * @param locale language setting + * @return descriptive text (long form, never {@code null}) + * @see #getDisplayName(Locale, TextWidth, OutputContext) + */ + /*[deutsch] + *Entspricht dem Ausdruck + * {@code getDisplayName(locale, TextWidth.WIDE, OutputContext.FORMAT)}.
+ * + *Wenn dieser Monat ein eingeschobener Monat ist (Schaltmonat), dann wird das + * sprachabhängige Wort für "adhika" vor den Namen gesetzt.
+ * + * @param locale language setting + * @return descriptive text (long form, never {@code null}) + * @see #getDisplayName(Locale, TextWidth, OutputContext) + */ + public String getDisplayName(Locale locale) { + + return this.getDisplayName(locale, TextWidth.WIDE, OutputContext.FORMAT); + + } + + /** + *Gets the description text dependent on the locale and style parameters.
+ * + *The second argument controls the width of description while the + * third argument is only relevant for languages which make a difference + * between stand-alone forms and embedded text forms (does not matter in + * English).
+ * + *Note: Rasi names are not used by this method.
+ * + *If this month is in leap status then the localized word + * for "adhika" will be inserted before the name.
+ * + * @param locale language setting + * @param width text width + * @param context output context + * @return descriptive text for given locale and style (never {@code null}) + * @see #getRasi(Locale) + */ + /*[deutsch] + *Liefert den sprachabhängigen Beschreibungstext.
+ * + *Über das zweite Argument kann gesteuert werden, ob eine kurze + * oder eine lange Form des Beschreibungstexts ausgegeben werden soll. Das + * ist besonders sinnvoll in Benutzeroberflächen, wo zwischen der + * Beschriftung und der detaillierten Erläuterung einer graphischen + * Komponente unterschieden wird. Das dritte Argument ist in Sprachen von + * Belang, die verschiedene grammatikalische Formen für die Ausgabe + * als alleinstehend oder eingebettet in formatierten Text kennen.
+ * + *Hinweis: Rasi-Namen werden hier nicht verwendet.
+ * + *Wenn dieser Monat ein eingeschobener Monat ist (Schaltmonat), dann wird das + * sprachabhängige Wort für "adhika" vor den Namen gesetzt.
+ * + * @param locale language setting + * @param width text width + * @param context output context + * @return descriptive text for given locale and style (never {@code null}) + * @see #getRasi(Locale) + */ + public String getDisplayName( + Locale locale, + TextWidth width, + OutputContext context + ) { + + String displayName = CalendarText.getInstance("indian", locale).getStdMonths(width, context).print(this.value); + + if (this.leap) { + displayName = getAdhika(locale) + displayName; + } + + return displayName; } /** *Determines if this month is in leap state (intercalated month).
* + *A leap month is followed by a normal month with same number.
+ * * @return boolean */ /*[deutsch] *Bestimmt, ob dieser Monat ein Schaltmonat ist, also ein eingeschobener Monat.
* + *Ein Schaltmonat liegt direkt vor einem normalen Monat mit gleicher Nummer.
+ * * @return boolean */ + @Override public boolean isLeap() { return this.leap; @@ -288,4 +416,46 @@ public String toString() { } + /** + *Uses the comparing order of the lunisolar calendar.
+ * + *Leap months are sorted before months with same number.
+ * + * @param other another month to be compared with + * @return comparing result + */ + /*[deutsch] + *Verwendet die Anordnung der Monate im lunisolaren Kalender.
+ * + *Schaltmonate werden vor Monaten mit gleicher Nummer einsortiert.
+ * + * @param other another month to be compared with + * @return comparing result + */ + @Override + public int compareTo(HinduMonth other) { + + int result = this.value.compareTo(other.value); + + if (result == 0) { + if (this.leap) { + result = (other.leap ? 0 : -1); + } else { + result = (other.leap ? 1 : 0); + } + } + + return result; + + } + + @Override + public boolean test(HinduCalendar context) { + return this.equals(context.getMonth()); + } + + private static String getAdhika(Locale locale) { + return CalendarText.getInstance("extra/hindu", locale).getTextForms().get("adhika") + " "; + } + } diff --git a/base/src/main/java/net/time4j/calendar/hindu/HinduPrimitive.java b/base/src/main/java/net/time4j/calendar/hindu/HinduPrimitive.java new file mode 100644 index 000000000..57de2728f --- /dev/null +++ b/base/src/main/java/net/time4j/calendar/hindu/HinduPrimitive.java @@ -0,0 +1,104 @@ +/* + * ----------------------------------------------------------------------- + * Copyright © 2013-2020 Meno Hochschild,Abstract super class of Hindu months or days which can carry a leap status in lunisolar calendar.
+ * + * @author Meno Hochschild + * @since 5.6 + */ +/*[deutsch] + *Abstrakte Superklasse von Hindumonaten oder Hindutagen, die im lunisolaren Kalender + * im Schaltzustand vorliegen können.
+ * + * @author Meno Hochschild + * @since 5.6 + */ +public abstract class HinduPrimitive { + + //~ Statische Felder/Initialisierungen -------------------------------- + + /** + *Format attribute which defines a symbol character for leap months or leap days.
+ * + *This format attribute is mainly used in numerical formatting or when months shall be printed + * in an abbreviated style. Otherwise the localized word for "adhika" will be printed.
+ * + * @see net.time4j.format.expert.ChronoFormatter#with(AttributeKey, char) + * @see #ADHIKA_IS_TRAILING + */ + /*[deutsch] + *Formatattribut, das ein Symbolzeichen für Schaltmonate oder Schalttage definiert.
+ * + *Dieses Formatattribut wird vorwiegend bei numerischer Formatierung oder dann verwendet, wenn + * Monate in abgekürzter Form ausgegeben werden sollen. Sonst wird das lokalisierte Wort für + * "adhika" ausgegeben.
+ * + * @see net.time4j.format.expert.ChronoFormatter#with(AttributeKey, char) + * @see #ADHIKA_IS_TRAILING + */ + public static final AttributeKeyFormat attribute which defines if the symbol character for leap months or leap days should be printed + * after the element (default is {@code false} for most languages).
+ * + * @see net.time4j.format.expert.ChronoFormatter#with(AttributeKey, boolean) + * @see #ADHIKA_INDICATOR + */ + /*[deutsch] + *Formatattribut, das angibt, ob das Symbolzeichen für Schaltmonate oder Schalttage nach dem + * Element angezeigt werden soll (Standard ist für die meisten Sprachen {@code false}).
+ * + * @see net.time4j.format.expert.ChronoFormatter#with(AttributeKey, boolean) + * @see #ADHIKA_INDICATOR + */ + public static final AttributeKeyDetermines if this value primitive is in leap state (intercalated).
+ * + * @return boolean + */ + /*[deutsch] + *Bestimmt, ob dieser Wert im Schaltzustand ist, also ein eingeschobener Monat oder Tag.
+ * + * @return boolean + */ + public abstract boolean isLeap(); + +} diff --git a/base/src/main/java/net/time4j/calendar/hindu/HinduVariant.java b/base/src/main/java/net/time4j/calendar/hindu/HinduVariant.java index b487edda9..662985bb2 100644 --- a/base/src/main/java/net/time4j/calendar/hindu/HinduVariant.java +++ b/base/src/main/java/net/time4j/calendar/hindu/HinduVariant.java @@ -326,6 +326,11 @@ public boolean isOld() { return (this.type < 0); } + // Kerala region only + boolean prefersRasiNames() { + return (this.type == HinduRule.MADRAS.ordinal()) || (this.type == HinduRule.MALAYALI.ordinal()); + } + /** *Does this variant use elapsed years?
*