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 TextElement MONTH_OF_YEAR = MonthElement.SINGLETON; + + /** + *

Represents the Hindu day of month.

+ */ + /*[deutsch] + *

Repräsentiert den Hindu-Tag des Monats.

+ */ + @FormattableElement(format = "d") + public static final TextElement DAY_OF_MONTH = DayOfMonthElement.SINGLETON; + /** *

Represents 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) ? "" : context.toString(); + throw new IllegalArgumentException("Cannot infer Hindu calendar variant: " + s); + } + } + /** * @serialData Uses * a dedicated serialization form as proxy. The first byte contains @@ -946,7 +1087,7 @@ public HinduCalendar withValue( @Override public ChronoElement getChildAtFloor(HinduCalendar context) { if (this.index == YEAR_INDEX) { -// return MONTH_OF_YEAR; // TODO: activate + return MONTH_OF_YEAR; } return null; @@ -955,7 +1096,7 @@ public ChronoElement getChildAtFloor(HinduCalendar context) { @Override public ChronoElement getChildAtCeiling(HinduCalendar context) { if (this.index == YEAR_INDEX) { -// return MONTH_OF_YEAR; // TODO: activate + return MONTH_OF_YEAR; } return null; @@ -989,6 +1130,617 @@ private int getMax(HinduCalendar context) { } + private static class MonthElement + extends DisplayElement + implements TextElement, ElementRule { + + //~ Statische Felder/Initialisierungen ---------------------------- + + static final MonthElement SINGLETON = new MonthElement(); + + // TODO: private static final long serialVersionUID = -2978966174642315851L; + + //~ Konstruktoren ------------------------------------------------- + + private MonthElement() { + super("MONTH_OF_YEAR"); + } + + //~ Methoden ------------------------------------------------------ + + @Override + public HinduMonth getValue(HinduCalendar context) { + return context.month; + } + + @Override + public HinduMonth getMinimum(HinduCalendar context) { + return context.withNewYear().month; + } + + @Override + public HinduMonth getMaximum(HinduCalendar context) { + if (context.variant.isSolar()) { + return HinduMonth.ofSolar(12); + } else { + return context.withNewYear().previousMonth().month; + } + } + + @Override + public boolean isValid( + HinduCalendar context, + HinduMonth value + ) { + if ((value == null) || (value.isLeap() && context.variant.isSolar())) { + return false; + } + + if (value.isLeap()) { + HinduCalendar cal = context.withNewYear(); + int m = 0; // count of compared normal months + + while (!cal.month.equals(value)) { + if (!cal.month.isLeap()) { + m++; + if (m >= 12) { + return false; + } + } + cal = cal.nextMonth(); + } + } + + return true; + } + + @Override + public HinduCalendar withValue( + HinduCalendar context, + HinduMonth value, + boolean lenient + ) { + if ((value == null) || (value.isLeap() && context.variant.isSolar())) { + throw new IllegalArgumentException("Invalid month: " + value); + } + + HinduCalendar cal = context.withNewYear(); + int m = 0; // count of compared normal months + + while (!cal.month.equals(value)) { + if (!cal.month.isLeap()) { + m++; + if (m >= 12) { + throw new IllegalArgumentException("Invalid month: " + value); + } + } + cal = cal.nextMonth(); + } + + return cal.withAdjustedDayInMonth(context.dayOfMonth); + } + + @Override + public ChronoElement getChildAtFloor(HinduCalendar context) { + return DAY_OF_MONTH; + } + + @Override + public ChronoElement getChildAtCeiling(HinduCalendar context) { + return DAY_OF_MONTH; + } + + @Override + public Class getType() { + return HinduMonth.class; + } + + @Override + public char getSymbol() { + return 'M'; + } + + @Override + public HinduMonth getDefaultMinimum() { + return HinduMonth.ofLunisolar(1); + } + + @Override + public HinduMonth getDefaultMaximum() { + return HinduMonth.ofLunisolar(12); + } + + @Override + public boolean isDateElement() { + return true; + } + + @Override + public boolean isTimeElement() { + return false; + } + + @Override + protected boolean isSingleton() { + return true; + } + + /** + * @serialData Preserves the singleton semantic + * @return singleton instance + */ + protected Object readResolve() throws ObjectStreamException { + return SINGLETON; + } + + @Override + public void print( + ChronoDisplay context, + Appendable buffer, + AttributeQuery attributes + ) throws IOException, ChronoException { + HinduVariant v = getVariant(context, attributes); + Locale loc = attributes.get(Attributes.LANGUAGE, Locale.ROOT); + int count = attributes.get(DualFormatElement.COUNT_OF_PATTERN_SYMBOLS, Integer.valueOf(0)).intValue(); + HinduMonth month = context.get(MONTH_OF_YEAR); + boolean trailing = false; + char indicator = '*'; + String adhika = ""; + + if (month.isLeap()) { + Map textForms = CalendarText.getInstance("generic", loc).getTextForms(); + trailing = + attributes.get(HinduPrimitive.ADHIKA_IS_TRAILING, "R".equals(textForms.get("leap-alignment"))); + indicator = + attributes.get(HinduPrimitive.ADHIKA_INDICATOR, textForms.get("leap-indicator").charAt(0)); + adhika = CalendarText.getInstance("extra/hindu", loc).getTextForms().get("adhika"); + } + + if (count == 0) { + if (v.isSolar() && attributes.get(HinduMonth.RASI_NAMES, v.prefersRasiNames())) { + buffer.append(month.getRasi(loc)); + } else { + TextWidth tw = attributes.get(Attributes.TEXT_WIDTH, TextWidth.WIDE); + OutputContext oc = attributes.get(Attributes.OUTPUT_CONTEXT, OutputContext.FORMAT); + + if (month.isLeap() && !trailing) { + if (tw == TextWidth.WIDE) { + buffer.append(adhika); + buffer.append(' '); + } else { + buffer.append(indicator); + } + month = HinduMonth.of(month.getValue()); // reset leap status + } + + buffer.append(month.getDisplayName(loc, tw, oc)); + + if (trailing) { // only for leap months + if (tw == TextWidth.WIDE) { + buffer.append(' '); + buffer.append(adhika); + } else { + buffer.append(indicator); + } + } + } + } else { // numeric case + if (month.isLeap() && !trailing) { + buffer.append(indicator); + } + + int num = v.isSolar() ? month.getRasi() : month.getValue().getValue(); + NumberSystem numsys = attributes.get(Attributes.NUMBER_SYSTEM, NumberSystem.ARABIC); + char zeroDigit = attributes.get(Attributes.ZERO_DIGIT, numsys.getDigits().charAt(0)); + String s = DualFormatElement.toNumeral(numsys, zeroDigit, num); + + if (v.isSolar() && numsys.isDecimal()) { + int padding = count - s.length(); + while (padding > 0) { + buffer.append(zeroDigit); + padding--; + } + } + + buffer.append(s); + + if (trailing) { // only for leap months + buffer.append(indicator); + } + } + } + + @Override + public HinduMonth parse( + CharSequence text, + ParsePosition status, + AttributeQuery attributes + ) { + int start = status.getIndex(); + int len = text.length(); + int pos = start; + + if (pos >= len) { + status.setErrorIndex(start); + return null; + } + + HinduVariant v = getVariant(null, attributes); + Locale loc = attributes.get(Attributes.LANGUAGE, Locale.ROOT); + int count = attributes.get(DualFormatElement.COUNT_OF_PATTERN_SYMBOLS, Integer.valueOf(0)).intValue(); + boolean caseInsensitive = attributes.get(Attributes.PARSE_CASE_INSENSITIVE, Boolean.TRUE).booleanValue(); + + boolean trailing = false; + char indicator = '*'; + String adhika = ""; + boolean leap = false; + boolean solar = v.isSolar(); + + if (!solar) { + Map textForms = CalendarText.getInstance("generic", loc).getTextForms(); + trailing = + attributes.get(HinduPrimitive.ADHIKA_IS_TRAILING, "R".equals(textForms.get("leap-alignment"))); + indicator = attributes.get(HinduPrimitive.ADHIKA_INDICATOR, textForms.get("leap-indicator").charAt(0)); + adhika = CalendarText.getInstance("extra/hindu", loc).getTextForms().get("adhika"); + + if (!trailing) { + int leapStatus = parseLeadingLeapInfo(text, pos, len, caseInsensitive, adhika, indicator, loc); + + if (leapStatus != -1) { + pos = leapStatus; + leap = true; + } + } + } + + if (pos >= len) { + status.setErrorIndex(start); + return null; + } + + HinduMonth result; + + if (count == 0) { + status.setIndex(pos); + TextWidth tw = attributes.get(Attributes.TEXT_WIDTH, TextWidth.WIDE); + OutputContext oc = attributes.get(Attributes.OUTPUT_CONTEXT, OutputContext.FORMAT); + CalendarText names = CalendarText.getInstance("indian", loc); + IndianMonth im = names.getStdMonths(tw, oc).parse(text, status, IndianMonth.class, attributes); + + if ((im == null) && solar) { + // let's try with rasi names as alternative before giving up + status.setIndex(pos); + status.setErrorIndex(-1); + TextAccessor ta = CalendarText.getInstance("extra/hindu", loc).getTextForms("R", IndianMonth.class); + int rasi = ta.parse(text, status, IndianMonth.class, attributes).getValue(); + im = HinduMonth.ofSolar(rasi).getValue(); // rebase + } + + if (im == null) { + status.setErrorIndex(start); + return null; + } else { + result = HinduMonth.of(im); + pos = status.getIndex(); + } + } else { // numeric case + NumberSystem numsys = attributes.get(Attributes.NUMBER_SYSTEM, NumberSystem.ARABIC); + char zeroDigit = attributes.get(Attributes.ZERO_DIGIT, numsys.getDigits().charAt(0)); + int m = 0; + + if (solar && numsys.isDecimal()) { + while ((pos < len) && (text.charAt(pos) == zeroDigit)) { + pos++; // ignore possible padding + } + } + + for (int num = 12; (num >= 1) && (m == 0); num--) { + String display = DualFormatElement.toNumeral(numsys, zeroDigit, num); + int numlen = display.length(); + for (int i = 0; ; i++) { + if ((len > pos + i) && (text.charAt(pos + i) != display.charAt(i))) { + break; + } else if (i + 1 == numlen) { + m = num; + pos += numlen; + break; + } + } + } + + if (m == 0) { + status.setErrorIndex(start); + return null; + } else { + result = solar ? HinduMonth.ofSolar(m) : HinduMonth.ofLunisolar(m); + } + } + + if (trailing) { + int leapStatus = parseTrailingLeapInfo(text, pos, len, caseInsensitive, adhika, indicator, loc); + + if (leapStatus != -1) { + pos = leapStatus; + leap = true; + } + } + + if (leap) { + result = result.withLeap(); + } + + status.setIndex(pos); + return result; + } + + } + + private static class DayOfMonthElement + extends DisplayElement + implements TextElement, ElementRule { + + //~ Statische Felder/Initialisierungen ---------------------------- + + static final DayOfMonthElement SINGLETON = new DayOfMonthElement(); + + // TODO: private static final long serialVersionUID = -2978966174642315851L; + + //~ Konstruktoren ------------------------------------------------- + + private DayOfMonthElement() { + super("DAY_OF_MONTH"); + } + + //~ Methoden ------------------------------------------------------ + + @Override + public HinduDay getValue(HinduCalendar context) { + return context.dayOfMonth; + } + + @Override + public HinduDay getMinimum(HinduCalendar context) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + @Override + public HinduDay getMaximum(HinduCalendar context) { + throw new UnsupportedOperationException("Not yet implemented."); + } + + @Override + public boolean isValid( + HinduCalendar context, + HinduDay value + ) { + if ((value == null) || (value.isLeap() && context.variant.isSolar())) { + return false; + } + + throw new UnsupportedOperationException("Not yet implemented."); + } + + @Override + public HinduCalendar withValue( + HinduCalendar context, + HinduDay value, + boolean lenient + ) { + if ((value == null) || (value.isLeap() && context.variant.isSolar())) { + throw new IllegalArgumentException("Invalid day of month: " + value); + } + + throw new UnsupportedOperationException("Not yet implemented."); + } + + @Override + public ChronoElement getChildAtFloor(HinduCalendar context) { + return null; + } + + @Override + public ChronoElement getChildAtCeiling(HinduCalendar context) { + return null; + } + + @Override + public Class getType() { + return HinduDay.class; + } + + @Override + public char getSymbol() { + return 'd'; + } + + @Override + public HinduDay getDefaultMinimum() { + return HinduDay.valueOf(1); + } + + @Override + public HinduDay getDefaultMaximum() { + return HinduDay.valueOf(32); + } + + @Override + public boolean isDateElement() { + return true; + } + + @Override + public boolean isTimeElement() { + return false; + } + + @Override + protected boolean isSingleton() { + return true; + } + + /** + * @serialData Preserves the singleton semantic + * @return singleton instance + */ + protected Object readResolve() throws ObjectStreamException { + return SINGLETON; + } + + @Override + public void print( + ChronoDisplay context, + Appendable buffer, + AttributeQuery attributes + ) throws IOException, ChronoException { + HinduVariant v = getVariant(context, attributes); + Locale loc = attributes.get(Attributes.LANGUAGE, Locale.ROOT); + int count = attributes.get(DualFormatElement.COUNT_OF_PATTERN_SYMBOLS, Integer.valueOf(0)).intValue(); + HinduDay dayOfMonth = context.get(DAY_OF_MONTH); + boolean trailing = false; + char indicator = '*'; + String adhika = ""; + + if (dayOfMonth.isLeap()) { + Map textForms = CalendarText.getInstance("generic", loc).getTextForms(); + trailing = + attributes.get(HinduPrimitive.ADHIKA_IS_TRAILING, "R".equals(textForms.get("leap-alignment"))); + indicator = + attributes.get(HinduPrimitive.ADHIKA_INDICATOR, textForms.get("leap-indicator").charAt(0)); + adhika = CalendarText.getInstance("extra/hindu", loc).getTextForms().get("adhika"); + } + + if (dayOfMonth.isLeap() && !trailing) { + if (count >= 2) { + buffer.append(adhika); + buffer.append(' '); + } else { + buffer.append(indicator); + } + } + + NumberSystem numsys = attributes.get(Attributes.NUMBER_SYSTEM, NumberSystem.ARABIC); + char zeroDigit = attributes.get(Attributes.ZERO_DIGIT, numsys.getDigits().charAt(0)); + String s = DualFormatElement.toNumeral(numsys, zeroDigit, dayOfMonth.getValue()); + + if (v.isSolar() && numsys.isDecimal()) { + int padding = count - s.length(); + while (padding > 0) { + buffer.append(zeroDigit); + padding--; + } + } + + buffer.append(s); + + if (trailing) { // only for leap days + if (count >= 2) { + buffer.append(' '); + buffer.append(adhika); + } else { + buffer.append(indicator); + } + } + } + + @Override + public HinduDay parse( + CharSequence text, + ParsePosition status, + AttributeQuery attributes + ) { + int start = status.getIndex(); + int len = text.length(); + int pos = start; + + if (pos >= len) { + status.setErrorIndex(start); + return null; + } + + HinduVariant v = getVariant(null, attributes); + Locale loc = attributes.get(Attributes.LANGUAGE, Locale.ROOT); + boolean caseInsensitive = attributes.get(Attributes.PARSE_CASE_INSENSITIVE, Boolean.TRUE).booleanValue(); + + boolean trailing = false; + char indicator = '*'; + String adhika = ""; + boolean leap = false; + boolean solar = v.isSolar(); + + if (!solar) { + Map textForms = CalendarText.getInstance("generic", loc).getTextForms(); + trailing = + attributes.get(HinduPrimitive.ADHIKA_IS_TRAILING, "R".equals(textForms.get("leap-alignment"))); + indicator = attributes.get(HinduPrimitive.ADHIKA_INDICATOR, textForms.get("leap-indicator").charAt(0)); + adhika = CalendarText.getInstance("extra/hindu", loc).getTextForms().get("adhika"); + + if (!trailing) { + int leapStatus = parseLeadingLeapInfo(text, pos, len, caseInsensitive, adhika, indicator, loc); + + if (leapStatus != -1) { + pos = leapStatus; + leap = true; + } + } + } + + if (pos >= len) { + status.setErrorIndex(start); + return null; + } + + NumberSystem numsys = attributes.get(Attributes.NUMBER_SYSTEM, NumberSystem.ARABIC); + char zeroDigit = attributes.get(Attributes.ZERO_DIGIT, numsys.getDigits().charAt(0)); + int dom = 0; + + if (solar && numsys.isDecimal()) { + while ((pos < len) && (text.charAt(pos) == zeroDigit)) { + pos++; // ignore possible padding + } + } + + for (int num = solar ? 32 : 30; (num >= 1) && (dom == 0); num--) { + String display = DualFormatElement.toNumeral(numsys, zeroDigit, num); + int numlen = display.length(); + for (int i = 0; ; i++) { + if ((len > pos + i) && (text.charAt(pos + i) != display.charAt(i))) { + break; + } else if (i + 1 == numlen) { + dom = num; + pos += numlen; + break; + } + } + } + + HinduDay result; + + if (dom == 0) { + status.setErrorIndex(start); + return null; + } else { + result = HinduDay.valueOf(dom); + } + + if (trailing) { + int leapStatus = parseTrailingLeapInfo(text, pos, len, caseInsensitive, adhika, indicator, loc); + + if (leapStatus != -1) { + pos = leapStatus; + leap = true; + } + } + + if (leap) { + result = result.withLeap(); + } + + status.setIndex(pos); + return result; + } + + } + private static class Merger implements ChronoMerger { @@ -1104,14 +1856,6 @@ public StartOfDay getDefaultStartOfDay() { ).sunrise()); } - @Override - public ChronoDisplay preformat( - HinduCalendar context, - AttributeQuery attributes - ) { - return context; - } - @Override public int getDefaultPivotYear() { return 100; // two-digit-years are effectively switched off diff --git a/base/src/main/java/net/time4j/calendar/hindu/HinduDay.java b/base/src/main/java/net/time4j/calendar/hindu/HinduDay.java index 3a2462207..ed926c377 100644 --- a/base/src/main/java/net/time4j/calendar/hindu/HinduDay.java +++ b/base/src/main/java/net/time4j/calendar/hindu/HinduDay.java @@ -21,6 +21,8 @@ package net.time4j.calendar.hindu; +import net.time4j.engine.ChronoCondition; + import java.io.Serializable; @@ -39,7 +41,8 @@ * @since 5.6 */ public final class HinduDay - implements Serializable { + extends HinduPrimitive + implements Comparable, ChronoCondition, Serializable { //~ Instanzvariablen -------------------------------------------------- @@ -123,13 +126,19 @@ public int getValue() { /** *

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 Comparable, ChronoCondition, Serializable { + + //~ Statische Felder/Initialisierungen -------------------------------- + + /** + *

Format 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 AttributeKey RASI_NAMES = + Attributes.createKey("RASI_NAMES", Boolean.class); //~ Instanzvariablen -------------------------------------------------- @@ -204,39 +233,138 @@ public int getRasi() { *

Obtains 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, + * ----------------------------------------------------------------------- + * This file (HinduPrimitive.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 . + * ----------------------------------------------------------------------- + */ + +package net.time4j.calendar.hindu; + +import net.time4j.calendar.EastAsianMonth; +import net.time4j.engine.AttributeKey; + + +/** + *

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 AttributeKey ADHIKA_INDICATOR = EastAsianMonth.LEAP_MONTH_INDICATOR; + + /** + *

Format 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 AttributeKey ADHIKA_IS_TRAILING = EastAsianMonth.LEAP_MONTH_IS_TRAILING; + + //~ Konstruktoren ----------------------------------------------------- + + // package private - no external instantiation + HinduPrimitive() { + super(); + + } + + //~ Methoden ---------------------------------------------------------- + + /** + *

Determines 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?

*