From: Peter Edberg Date: Tue, 19 Jun 2012 09:51:24 +0000 (+0000) Subject: ICU-9226 (J) Calendar add +year should always move forward in time; roll year should... X-Git-Tag: milestone-59-0-1~3756 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=11cb54b9513321be57f4996d946f746adf242412;p=icu ICU-9226 (J) Calendar add +year should always move forward in time; roll year should wrap for eras with real bounds, pin otherwise X-SVN-Rev: 31968 --- diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java b/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java index a1f471e7a52..4ba0414b695 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java @@ -2637,6 +2637,18 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable + * Rolling up always means rolling forward in time (unless + * the limit of the field is reached, in which case it may pin or wrap), so for the + * Gregorian calendar, starting with 100 BC and rolling the year up results in 99 BC. + * When eras have a definite beginning and end (as in the Chinese calendar, or as in + * most eras in the Japanese calendar) then rolling the year past either limit of the + * era will cause the year to wrap around. When eras only have a limit at one end, + * then attempting to roll the year past that limit will result in pinning the year + * at that limit. Note that for most calendars in which era 0 years move forward in + * time (such as Buddhist, Hebrew, or Islamic), it is possible for add or roll to + * result in negative years for era 0 (that is the only way to represent years before + * the calendar epoch in such calendars). + *

* Note: Calling roll(field, true) N times is not * necessarily equivalent to calling roll(field, N). For example, * imagine that you start with the date Gregorian date January 31, 1995. If you call @@ -2685,6 +2697,18 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable + * Rolling by a positive value always means rolling forward in time (unless + * the limit of the field is reached, in which case it may pin or wrap), so for the + * Gregorian calendar, starting with 100 BC and rolling the year by + 1 results in 99 BC. + * When eras have a definite beginning and end (as in the Chinese calendar, or as in + * most eras in the Japanese calendar) then rolling the year past either limit of the + * era will cause the year to wrap around. When eras only have a limit at one end, + * then attempting to roll the year past that limit will result in pinning the year + * at that limit. Note that for most calendars in which era 0 years move forward in + * time (such as Buddhist, Hebrew, or Islamic), it is possible for add or roll to + * result in negative years for era 0 (that is the only way to represent years before + * the calendar epoch in such calendars). + *

* {@icunote} the ICU implementation of this method is able to roll * all fields except for {@link #ERA ERA}, {@link #DST_OFFSET DST_OFFSET}, * and {@link #ZONE_OFFSET ZONE_OFFSET}. Subclasses may, of course, add support for @@ -2806,6 +2830,44 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable 0 || newYear >= 1) { + int maxYear = getActualMaximum(field); + if (maxYear < 32768) { + // this era has real bounds, roll should wrap years + if (newYear < 1) { + newYear = maxYear - ((-newYear) % maxYear); + } else if (newYear > maxYear) { + newYear = ((newYear - 1) % maxYear) + 1; + } + // else era is unbounded, just pin low year instead of wrapping + } else if (newYear < 1) { + newYear = 1; + } + // else we are in era 0 with newYear < 1; + // calendars with years that go backwards must pin the year value at 0, + // other calendars can have years < 0 in era 0 + } else if (era0WithYearsThatGoBackwards) { + newYear = 1; + } + set(field, newYear); + pinField(MONTH); + pinField(DAY_OF_MONTH); + return; + } case EXTENDED_YEAR: // Rolling the year can involve pinning the DAY_OF_MONTH. set(field, internalGet(field) + amount); @@ -3024,6 +3086,10 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable + * Adding a positive value always means moving forward in time, so for the Gregorian + * calendar, starting with 100 BC and adding +1 to year results in 99 BC (even though + * this actually reduces the numeric value of the field itself). + *

* {@icunote} The ICU implementation of this method is able to add to * all fields except for {@link #ERA ERA}, {@link #DST_OFFSET DST_OFFSET}, * and {@link #ZONE_OFFSET ZONE_OFFSET}. Subclasses may, of course, add support for @@ -3099,8 +3165,25 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable 1 + "en@calendar=japanese", + "en@calendar=chinese", + // calendars with non-modern era 0 that goes forwards, max era == 1 + "en@calendar=ethiopic", + // calendars with only one era = 0, forwards + "en@calendar=buddhist", + "en@calendar=hebrew", + "en@calendar=islamic", + "en@calendar=indian", + //"en@calendar=persian", // no persian calendar in ICU4J yet + "en@calendar=ethiopic-amete-alem", + }; + TimeZone zoneGMT = TimeZone.getFrozenTimeZone("GMT"); + for (String localeID : localeIDs) { + Calendar ucalTest = Calendar.getInstance(zoneGMT, new ULocale(localeID)); + String calType = ucalTest.getType(); + boolean era0YearsGoBackwards = (calType.equals("gregorian") || calType.equals("roc") || calType.equals("coptic")); + int yrBefore, yrAfter, yrMax, eraAfter, eraMax, eraNow; + + ucalTest.clear(); + ucalTest.set(Calendar.YEAR, 2); + ucalTest.set(Calendar.ERA, 0); + yrBefore = ucalTest.get(Calendar.YEAR); + ucalTest.add(Calendar.YEAR, 1); + yrAfter = ucalTest.get(Calendar.YEAR); + if ( (era0YearsGoBackwards && yrAfter>yrBefore) || (!era0YearsGoBackwards && yrAfteryrBefore) || (!era0YearsGoBackwards && yrAfter 0) { + // try similar tests for era 1 (if calendar has it), in which years always go forward + + ucalTest.clear(); + ucalTest.set(Calendar.YEAR, 2); + ucalTest.set(Calendar.ERA, 1); + yrBefore = ucalTest.get(Calendar.YEAR); + ucalTest.add(Calendar.YEAR, 1); + yrAfter = ucalTest.get(Calendar.YEAR); + if ( yrAfter= 32768) { + if (eraAfter != 1 || yrAfter != 1) { + errln("Fail: era 1 roll -1 year from year 1 does not stay within era or pin to year 1 for " + + localeID + " (get era " + eraAfter + " year " + yrAfter + ")"); + } + } else if (eraAfter != 1 || yrAfter != yrMax) { + errln("Fail: era 1 roll -1 year from year 1 does not stay within era or wrap to year " + + yrMax + " for " + localeID + " (get era " + eraAfter + " year " + yrAfter + ")"); + } else { + ucalTest.roll(Calendar.YEAR, 1); // now roll up which should wrap to beginning + yrAfter = ucalTest.get(Calendar.YEAR); + eraAfter = ucalTest.get(Calendar.ERA); + if (eraAfter != 1 || yrAfter != 1) { + errln("Fail: era 1 roll 1 year from year " + yrMax + + " does not stay within era or wrap to year 1 for " + + localeID + " (get era " + eraAfter + " year " + yrAfter + ")"); + } + } + + // if current era > 1, try the same roll tests for current era + ucalTest.setTime(new Date()); + eraNow = ucalTest.get(Calendar.ERA); + if (eraNow > 1) { + ucalTest.clear(); + ucalTest.set(Calendar.YEAR, 1); + ucalTest.set(Calendar.ERA, eraNow); + yrMax = ucalTest.getActualMaximum(Calendar.YEAR); // max year value for this era + ucalTest.roll(Calendar.YEAR, -1); + yrAfter = ucalTest.get(Calendar.YEAR); + eraAfter = ucalTest.get(Calendar.ERA); + // if yrMax is reasonable we should wrap to that, else we should pin at yr 1 + if (yrMax >= 32768) { + if (eraAfter != eraNow || yrAfter != 1) { + errln("Fail: era " + eraNow + + " roll -1 year from year 1 does not stay within era or pin to year 1 for " + + localeID + " (get era " + eraAfter + " year " + yrAfter + ")"); + } + } else if (eraAfter != eraNow || yrAfter != yrMax) { + errln("Fail: era " + eraNow + + " roll -1 year from year 1 does not stay within era or wrap to year " + yrMax + + " for " + localeID + " (get era " + eraAfter + " year " + yrAfter + ")"); + } else { + ucalTest.roll(Calendar.YEAR, 1); // now roll up which should wrap to beginning + yrAfter = ucalTest.get(Calendar.YEAR); + eraAfter = ucalTest.get(Calendar.ERA); + if (eraAfter != eraNow || yrAfter != 1) { + errln("Fail: era " + eraNow + " roll 1 year from year " + yrMax + + " does not stay within era or wrap to year 1 for " + + localeID + " (get era " + eraAfter + " year " + yrAfter + ")"); + } + } + } + } + } + } + }