ICU-9255 Adding the traditional Korean calendar (Dangi) support to ICU4J.

X-SVN-Rev: 32426
This commit is contained in:
Yoshito Umaoka 2012-09-25 21:45:16 +00:00
parent f75135af94
commit 331d9f169f
9 changed files with 662 additions and 71 deletions

View File

@ -34,6 +34,10 @@ public class CalendarData {
fMainType = "gregorian"; fMainType = "gregorian";
fFallbackType = null; fFallbackType = null;
} else { } else {
// TODO: Until CLDR supports "dangi" calendar type
if (type.equalsIgnoreCase("dangi")) {
type = "chinese";
}
fMainType = type; fMainType = type;
fFallbackType = "gregorian"; fFallbackType = "gregorian";
} }

View File

@ -184,6 +184,12 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
if ( calendarTypeToUse == null ) { if ( calendarTypeToUse == null ) {
calendarTypeToUse = "gregorian"; // fallback calendarTypeToUse = "gregorian"; // fallback
} }
// TODO: Until CLDR supports "dangi" calendar type
if (calendarTypeToUse.equalsIgnoreCase("dangi")) {
calendarTypeToUse = "chinese";
}
// Get data for that calendar // Get data for that calendar
ICUResourceBundle calBundle = rb.getWithFallback("calendar"); ICUResourceBundle calBundle = rb.getWithFallback("calendar");
ICUResourceBundle calTypeBundle = calBundle.getWithFallback(calendarTypeToUse); ICUResourceBundle calTypeBundle = calBundle.getWithFallback(calendarTypeToUse);

View File

@ -1739,6 +1739,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
"ethiopic", "ethiopic",
"ethiopic-amete-alem", "ethiopic-amete-alem",
"iso8601", "iso8601",
"dangi",
}; };
// must be in the order of calTypes above // must be in the order of calTypes above
@ -1756,6 +1757,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
private static final int CALTYPE_ETHIOPIC = 11; private static final int CALTYPE_ETHIOPIC = 11;
private static final int CALTYPE_ETHIOPIC_AMETE_ALEM = 12; private static final int CALTYPE_ETHIOPIC_AMETE_ALEM = 12;
private static final int CALTYPE_ISO8601 = 13; private static final int CALTYPE_ISO8601 = 13;
private static final int CALTYPE_DANGI = 14;
private static final int CALTYPE_UNKNOWN = -1; private static final int CALTYPE_UNKNOWN = -1;
private static int getCalendarTypeForLocale(ULocale l) { private static int getCalendarTypeForLocale(ULocale l) {
@ -1901,6 +1903,9 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
cal = new EthiopicCalendar(zone, locale); cal = new EthiopicCalendar(zone, locale);
((EthiopicCalendar)cal).setAmeteAlemEra(true); ((EthiopicCalendar)cal).setAmeteAlemEra(true);
break; break;
case CALTYPE_DANGI:
cal = new DangiCalendar(zone, locale);
break;
case CALTYPE_ISO8601: case CALTYPE_ISO8601:
// Only differs week numbering rule from Gregorian // Only differs week numbering rule from Gregorian
cal = new GregorianCalendar(zone, locale); cal = new GregorianCalendar(zone, locale);

View File

@ -101,6 +101,17 @@ public class ChineseCalendar extends Calendar {
// the object, not class level, under the assumption that typical // the object, not class level, under the assumption that typical
// usage will be to have one instance of ChineseCalendar at a time. // usage will be to have one instance of ChineseCalendar at a time.
/**
* The start year of this Chinese calendar instance.
*/
private int epochYear;
/**
* The zone used for the astronomical calculation of this Chinese
* calendar instance.
*/
private TimeZone zoneAstro;
/** /**
* We have one instance per object, and we don't synchronize it because * We have one instance per object, and we don't synchronize it because
* Calendar doesn't support multithreaded execution in the first place. * Calendar doesn't support multithreaded execution in the first place.
@ -135,8 +146,7 @@ public class ChineseCalendar extends Calendar {
* @stable ICU 2.8 * @stable ICU 2.8
*/ */
public ChineseCalendar() { public ChineseCalendar() {
super(); this(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT), CHINESE_EPOCH_YEAR, CHINA_ZONE);
setTimeInMillis(System.currentTimeMillis());
} }
/** /**
@ -146,7 +156,7 @@ public class ChineseCalendar extends Calendar {
* @stable ICU 4.0 * @stable ICU 4.0
*/ */
public ChineseCalendar(Date date) { public ChineseCalendar(Date date) {
super(); this(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT), CHINESE_EPOCH_YEAR, CHINA_ZONE);
setTime(date); setTime(date);
} }
@ -164,19 +174,7 @@ public class ChineseCalendar extends Calendar {
* @stable ICU 4.0 * @stable ICU 4.0
*/ */
public ChineseCalendar(int year, int month, int isLeapMonth, int date) { public ChineseCalendar(int year, int month, int isLeapMonth, int date) {
super(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT)); this(year, month, isLeapMonth, date, 0, 0, 0);
// We need to set the current time once to initialize the ChineseCalendar's
// ERA field to be the current era.
setTimeInMillis(System.currentTimeMillis());
// Then we need to clean up time fields
this.set(MILLISECONDS_IN_DAY, 0);
// Then set the given field values.
this.set(YEAR, year);
this.set(MONTH, month);
this.set(IS_LEAP_MONTH, isLeapMonth);
this.set(DATE, date);
} }
/** /**
@ -201,12 +199,12 @@ public class ChineseCalendar extends Calendar {
public ChineseCalendar(int year, int month, int isLeapMonth, int date, int hour, public ChineseCalendar(int year, int month, int isLeapMonth, int date, int hour,
int minute, int second) int minute, int second)
{ {
super(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT)); this(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT), CHINESE_EPOCH_YEAR, CHINA_ZONE);
// We need to set the current time once to initialize the ChineseCalendar's // The current time is set at this point, so ERA field is already
// ERA field to be the current era. // set to the current era.
setTimeInMillis(System.currentTimeMillis());
// Then set 0 to millisecond field // Then we need to clean up time fields
this.set(MILLISECOND, 0); this.set(MILLISECOND, 0);
// Then, set the given field values. // Then, set the given field values.
@ -235,21 +233,7 @@ public class ChineseCalendar extends Calendar {
*/ */
public ChineseCalendar(int era, int year, int month, int isLeapMonth, int date) public ChineseCalendar(int era, int year, int month, int isLeapMonth, int date)
{ {
super(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT)); this(era, year, month, isLeapMonth, 0, 0, 0);
// We need to set the current time once to initialize the ChineseCalendar's
// ERA field to be the current era.
setTimeInMillis(System.currentTimeMillis());
// Then we need to clean up time fields
this.set(MILLISECONDS_IN_DAY, 0);
// Then set the given field values.
this.set(ERA, era);
this.set(YEAR, year);
this.set(MONTH, month);
this.set(IS_LEAP_MONTH, isLeapMonth);
this.set(DATE, date);
} }
/** /**
@ -275,13 +259,9 @@ public class ChineseCalendar extends Calendar {
public ChineseCalendar(int era, int year, int month, int isLeapMonth, int date, int hour, public ChineseCalendar(int era, int year, int month, int isLeapMonth, int date, int hour,
int minute, int second) int minute, int second)
{ {
super(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT)); this(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT), CHINESE_EPOCH_YEAR, CHINA_ZONE);
// We need to set the current time once to initialize the ChineseCalendar's // Set 0 to millisecond field
// ERA field to be the current era.
setTimeInMillis(System.currentTimeMillis());
// Then set 0 to millisecond field
this.set(MILLISECOND, 0); this.set(MILLISECOND, 0);
// Then, set the given field values. // Then, set the given field values.
@ -302,8 +282,7 @@ public class ChineseCalendar extends Calendar {
* @stable ICU 4.0 * @stable ICU 4.0
*/ */
public ChineseCalendar(Locale aLocale) { public ChineseCalendar(Locale aLocale) {
this(TimeZone.getDefault(), aLocale); this(TimeZone.getDefault(), ULocale.forLocale(aLocale), CHINESE_EPOCH_YEAR, CHINA_ZONE);
setTimeInMillis(System.currentTimeMillis());
} }
/** /**
@ -314,8 +293,7 @@ public class ChineseCalendar extends Calendar {
* @stable ICU 4.0 * @stable ICU 4.0
*/ */
public ChineseCalendar(TimeZone zone) { public ChineseCalendar(TimeZone zone) {
super(zone, ULocale.getDefault(Category.FORMAT)); this(zone, ULocale.getDefault(Category.FORMAT), CHINESE_EPOCH_YEAR, CHINA_ZONE);
setTimeInMillis(System.currentTimeMillis());
} }
/** /**
@ -326,8 +304,7 @@ public class ChineseCalendar extends Calendar {
* @stable ICU 2.8 * @stable ICU 2.8
*/ */
public ChineseCalendar(TimeZone zone, Locale aLocale) { public ChineseCalendar(TimeZone zone, Locale aLocale) {
super(zone, aLocale); this(zone, ULocale.forLocale(aLocale), CHINESE_EPOCH_YEAR, CHINA_ZONE);
setTimeInMillis(System.currentTimeMillis());
} }
/** /**
@ -338,8 +315,7 @@ public class ChineseCalendar extends Calendar {
* @stable ICU 4.0 * @stable ICU 4.0
*/ */
public ChineseCalendar(ULocale locale) { public ChineseCalendar(ULocale locale) {
this(TimeZone.getDefault(), locale); this(TimeZone.getDefault(), locale, CHINESE_EPOCH_YEAR, CHINA_ZONE);
setTimeInMillis(System.currentTimeMillis());
} }
/** /**
@ -350,7 +326,20 @@ public class ChineseCalendar extends Calendar {
* @stable ICU 3.2 * @stable ICU 3.2
*/ */
public ChineseCalendar(TimeZone zone, ULocale locale) { public ChineseCalendar(TimeZone zone, ULocale locale) {
this(zone, locale, CHINESE_EPOCH_YEAR, CHINA_ZONE);
}
/**
* Construct a <code>ChineseCalenar</code> based on the current time
* with the given time zone, the locale, the epoch year and the time zone
* used for astronomical calculation.
* @internal
* @deprecated This API is ICU internal only.
*/
protected ChineseCalendar(TimeZone zone, ULocale locale, int epochYear, TimeZone zoneAstroCalc) {
super(zone, locale); super(zone, locale);
this.epochYear = epochYear;
this.zoneAstro = zoneAstroCalc;
setTimeInMillis(System.currentTimeMillis()); setTimeInMillis(System.currentTimeMillis());
} }
@ -455,7 +444,8 @@ public class ChineseCalendar extends Calendar {
year = internalGet(EXTENDED_YEAR, 1); // Default to year 1 year = internalGet(EXTENDED_YEAR, 1); // Default to year 1
} else { } else {
int cycle = internalGet(ERA, 1) - 1; // 0-based cycle int cycle = internalGet(ERA, 1) - 1; // 0-based cycle
year = cycle * 60 + internalGet(YEAR, 1); // adjust to the instance specific epoch
year = cycle * 60 + internalGet(YEAR, 1) - (epochYear - CHINESE_EPOCH_YEAR);
} }
return year; return year;
} }
@ -652,11 +642,11 @@ public class ChineseCalendar extends Calendar {
private static final int CHINESE_EPOCH_YEAR = -2636; // Gregorian year private static final int CHINESE_EPOCH_YEAR = -2636; // Gregorian year
/** /**
* The offset from GMT in milliseconds at which we perform astronomical * The time zone used for performing astronomical computations.
* computations. Some sources use a different historically accurate * Some sources use a different historically accurate
* offset of GMT+7:45:40 for years before 1929; we do not do this. * offset of GMT+7:45:40 for years before 1929; we do not do this.
*/ */
private static final long CHINA_OFFSET = 8*ONE_HOUR; private static final TimeZone CHINA_ZONE = new SimpleTimeZone(8 * ONE_HOUR, "CHINA_ZONE").freeze();
/** /**
* Value to be added or subtracted from the local days of a new moon to * Value to be added or subtracted from the local days of a new moon to
@ -667,20 +657,28 @@ public class ChineseCalendar extends Calendar {
/** /**
* Convert local days to UTC epoch milliseconds. * Convert local days to UTC epoch milliseconds.
* @param days days after January 1, 1970 0:00 Asia/Shanghai * This is not an accurate conversion in terms that getTimezoneOffset
* takes the milliseconds in GMT (not local time). In theory, more
* accurate algorithm can be implemented but practically we do not need
* to go through that complication as long as the historically timezone
* changes did not happen around the 'tricky' new moon (new moon around
* the midnight).
*
* @param days days after January 1, 1970 0:00 in the astronomical base zone
* @return milliseconds after January 1, 1970 0:00 GMT * @return milliseconds after January 1, 1970 0:00 GMT
*/ */
private static final long daysToMillis(int days) { private final long daysToMillis(int days) {
return (days * ONE_DAY) - CHINA_OFFSET; long millis = days * ONE_DAY;
return millis - zoneAstro.getOffset(millis);
} }
/** /**
* Convert UTC epoch milliseconds to local days. * Convert UTC epoch milliseconds to local days.
* @param millis milliseconds after January 1, 1970 0:00 GMT * @param millis milliseconds after January 1, 1970 0:00 GMT
* @return days after January 1, 1970 0:00 Asia/Shanghai * @return days days after January 1, 1970 0:00 in the astronomical base zone
*/ */
private static final int millisToDays(long millis) { private final int millisToDays(long millis) {
return (int) floorDivide(millis + CHINA_OFFSET, ONE_DAY); return (int) floorDivide(millis + zoneAstro.getOffset(millis), ONE_DAY);
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
@ -787,9 +785,9 @@ public class ChineseCalendar extends Calendar {
/** /**
* Return true if there is a leap month on or after month newMoon1 and * Return true if there is a leap month on or after month newMoon1 and
* at or before month newMoon2. * at or before month newMoon2.
* @param newMoon1 days after January 1, 1970 0:00 Asia/Shanghai of a * @param newMoon1 days after January 1, 1970 0:00 astronomical base zone of a
* new moon * new moon
* @param newMoon2 days after January 1, 1970 0:00 Asia/Shanghai of a * @param newMoon2 days after January 1, 1970 0:00 astronomical base zone of a
* new moon * new moon
*/ */
private boolean isLeapMonthBetween(int newMoon1, int newMoon2) { private boolean isLeapMonthBetween(int newMoon1, int newMoon2) {
@ -841,7 +839,7 @@ public class ChineseCalendar extends Calendar {
* <code>handleComputeMonthStart()</code>. * <code>handleComputeMonthStart()</code>.
* *
* <p>As a side effect, this method sets {@link #isLeapYear}. * <p>As a side effect, this method sets {@link #isLeapYear}.
* @param days days after January 1, 1970 0:00 Asia/Shanghai of the * @param days days after January 1, 1970 0:00 astronomical base zone of the
* date to compute fields for * date to compute fields for
* @param gyear the Gregorian year of the given date * @param gyear the Gregorian year of the given date
* @param gmonth the Gregorian month of the given date * @param gmonth the Gregorian month of the given date
@ -891,18 +889,21 @@ public class ChineseCalendar extends Calendar {
if (setAllFields) { if (setAllFields) {
int year = gyear - CHINESE_EPOCH_YEAR; // Extended year and cycle year is based on the epoch year
int extended_year = gyear - epochYear;
int cycle_year = gyear - CHINESE_EPOCH_YEAR;
if (month < 11 || if (month < 11 ||
gmonth >= JULY) { gmonth >= JULY) {
year++; extended_year++;
cycle_year++;
} }
int dayOfMonth = days - thisMoon + 1; int dayOfMonth = days - thisMoon + 1;
internalSet(EXTENDED_YEAR, year); internalSet(EXTENDED_YEAR, extended_year);
// 0->0,60 1->1,1 60->1,60 61->2,1 etc. // 0->0,60 1->1,1 60->1,60 61->2,1 etc.
int[] yearOfCycle = new int[1]; int[] yearOfCycle = new int[1];
int cycle = floorDivide(year-1, 60, yearOfCycle); int cycle = floorDivide(cycle_year-1, 60, yearOfCycle);
internalSet(ERA, cycle+1); internalSet(ERA, cycle+1);
internalSet(YEAR, yearOfCycle[0]+1); internalSet(YEAR, yearOfCycle[0]+1);
@ -927,7 +928,7 @@ public class ChineseCalendar extends Calendar {
/** /**
* Return the Chinese new year of the given Gregorian year. * Return the Chinese new year of the given Gregorian year.
* @param gyear a Gregorian year * @param gyear a Gregorian year
* @return days after January 1, 1970 0:00 Asia/Shanghai of the * @return days after January 1, 1970 0:00 astronomical base zone of the
* Chinese new year of the given year (this will be a new moon) * Chinese new year of the given year (this will be a new moon)
*/ */
private int newYear(int gyear) { private int newYear(int gyear) {
@ -977,7 +978,7 @@ public class ChineseCalendar extends Calendar {
month = rem[0]; month = rem[0];
} }
int gyear = eyear + CHINESE_EPOCH_YEAR - 1; // Gregorian year int gyear = eyear + epochYear - 1; // Gregorian year
int newYear = newYear(gyear); int newYear = newYear(gyear);
int newMoon = newMoonNear(newYear + month * 29, true); int newMoon = newMoonNear(newYear + month * 29, true);
@ -1022,6 +1023,9 @@ public class ChineseCalendar extends Calendar {
private void readObject(ObjectInputStream stream) private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException throws IOException, ClassNotFoundException
{ {
epochYear = CHINESE_EPOCH_YEAR;
zoneAstro = CHINA_ZONE;
stream.defaultReadObject(); stream.defaultReadObject();
/* set up the transient caches... */ /* set up the transient caches... */

View File

@ -0,0 +1,125 @@
/*
*******************************************************************************
* Copyright (C) 2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.util;
import java.util.Date;
import com.ibm.icu.util.ULocale.Category;
/**
* <code>DangiCalendar</code> is a concrete subclass of {@link Calendar}
* that implements a traditional Korean calendar.
*
* @internal
* @deprecated This API is ICU internal only.
*/
public class DangiCalendar extends ChineseCalendar {
private static final long serialVersionUID = 8156297445349501985L;
/**
* The start year of the Korean traditional calendar (Dan-gi) is the inaugural
* year of Dan-gun (BC 2333).
*/
private static final int DANGI_EPOCH_YEAR = -2332;
/**
* The time zone used for performing astronomical computations for
* Dangi calendar. In Korea various timezones have been used historically
* (cf. http://www.math.snu.ac.kr/~kye/others/lunar.html):
*
* - 1908/04/01: GMT+8
* 1908/04/01 - 1911/12/31: GMT+8.5
* 1912/01/01 - 1954/03/20: GMT+9
* 1954/03/21 - 1961/08/09: GMT+8.5
* 1961/08/10 - : GMT+9
*
* Note that, in 1908-1911, the government did not apply the timezone change
* but used GMT+8. In addition, 1954-1961's timezone change does not affect
* the lunar date calculation. Therefore, the following simpler rule works:
*
* -1911: GMT+8
* 1912-: GMT+9
*
* Unfortunately, our astronomer's approximation doesn't agree with the
* references (http://www.math.snu.ac.kr/~kye/others/lunar.html and
* http://astro.kasi.re.kr/Life/ConvertSolarLunarForm.aspx?MenuID=115)
* in 1897/7/30. So the following ad hoc fix is used here:
*
* -1896: GMT+8
* 1897: GMT+7
* 1898-1911: GMT+8
* 1912- : GMT+9
*/
private static final TimeZone KOREA_ZONE;
static {
InitialTimeZoneRule initialTimeZone = new InitialTimeZoneRule("GMT+8", 8 * ONE_HOUR, 0);
long[] millis1897 = { (1897 - 1970) * 365L * ONE_DAY }; // some days of error is not a problem here
long[] millis1898 = { (1898 - 1970) * 365L * ONE_DAY }; // some days of error is not a problem here
long[] millis1912 = { (1912 - 1970) * 365L * ONE_DAY }; // this doesn't create an issue for 1911/12/20
TimeZoneRule rule1897 = new TimeArrayTimeZoneRule("Korean 1897", 7 * ONE_HOUR, 0, millis1897,
DateTimeRule.STANDARD_TIME);
TimeZoneRule rule1898to1911 = new TimeArrayTimeZoneRule("Korean 1898-1911", 8 * ONE_HOUR, 0, millis1898,
DateTimeRule.STANDARD_TIME);
TimeZoneRule ruleFrom1912 = new TimeArrayTimeZoneRule("Korean 1912-", 9 * ONE_HOUR, 0, millis1912,
DateTimeRule.STANDARD_TIME);
RuleBasedTimeZone tz = new RuleBasedTimeZone("KOREA_ZONE", initialTimeZone);
tz.addTransitionRule(rule1897);
tz.addTransitionRule(rule1898to1911);
tz.addTransitionRule(ruleFrom1912);
tz.freeze();
KOREA_ZONE = tz;
};
/**
* Construct a <code>DangiCalendar</code> with the default time zone and locale.
*
* @internal
* @deprecated This API is ICU internal only.
*/
public DangiCalendar() {
this(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT));
}
/**
* Construct a <code>DangiCalendar</code> with the give date set in the default time zone
* with the default locale.
* @param date The date to which the new calendar is set.
*
* @internal
* @deprecated This API is ICU internal only.
*/
public DangiCalendar(Date date) {
this(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT));
setTime(date);
}
/**
* Construct a <code>DangiCalendar</code> based on the current time
* with the given time zone with the given locale.
* @param zone the given time zone
* @param locale the given locale
*
* @internal
* @deprecated This API is ICU internal only.
*/
public DangiCalendar(TimeZone zone, ULocale locale) {
super(zone, locale, DANGI_EPOCH_YEAR, KOREA_ZONE);
}
/**
* {@inheritDoc}
*
* @internal
* @deprecated This API is ICU internal only.
*/
public String getType() {
return "dangi";
}
}

View File

@ -0,0 +1,427 @@
/*
*******************************************************************************
* Copyright (C) 2012, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.dev.test.calendar;
import java.util.Date;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.DangiCalendar;
import com.ibm.icu.util.GregorianCalendar;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;
public class DangiTest extends CalendarTest {
public static void main(String args[]) throws Exception {
new DangiTest().run(args);
}
/**
* Test basic mapping to and from Gregorian.
*/
public void TestMapping() {
final int[] DATA = {
// (Note: months are 1-based)
// Gregorian Korean (Dan-gi)
1964, 9, 4, 4297, 7,0, 28,
1964, 9, 5, 4297, 7,0, 29,
1964, 9, 6, 4297, 8,0, 1,
1964, 9, 7, 4297, 8,0, 2,
1961, 12, 25, 4294, 11,0, 18,
1999, 6, 4, 4332, 4,0, 21,
1990, 5, 23, 4323, 4,0, 29,
1990, 5, 24, 4323, 5,0, 1,
1990, 6, 22, 4323, 5,0, 30,
1990, 6, 23, 4323, 5,1, 1,
1990, 7, 20, 4323, 5,1, 28,
1990, 7, 21, 4323, 5,1, 29,
1990, 7, 22, 4323, 6,0, 1,
// Some tricky dates (where GMT+8 doesn't agree with GMT+9)
//
// The list is from http://www.math.snu.ac.kr/~kye/others/lunar.html ('kye ref').
// However, for some dates disagree with the above reference so KASI's
// calculation was cross-referenced:
// http://astro.kasi.re.kr/Life/ConvertSolarLunarForm.aspx?MenuID=115
1880, 11, 3, 4213, 10,0, 1, // astronomer's GMT+8 / KASI disagrees with the kye ref
1882, 12, 10, 4215, 11,0, 1,
1883, 7, 4, 4216, 6,0, 1,
1884, 4, 25, 4217, 4,0, 1,
1885, 5, 14, 4218, 4,0, 1,
1891, 1, 10, 4223, 12,0, 1,
1893, 4, 16, 4226, 3,0, 1,
1894, 5, 5, 4227, 4,0, 1,
1897, 7, 29, 4230, 7,0, 1, // astronomer's GMT+8 disagrees with all other ref (looks like our astronomer's error, see ad hoc fix at ChineseCalendar::getTimezoneOffset)
1903, 10, 20, 4236, 9,0, 1,
1904, 1, 17, 4236, 12,0, 1,
1904, 11, 7, 4237, 10,0, 1,
1905, 5, 4, 4238, 4,0, 1,
1907, 7, 10, 4240, 6,0, 1,
1908, 4, 30, 4241, 4,0, 1,
1908, 9, 25, 4241, 9,0, 1,
1909, 9, 14, 4242, 8,0, 1,
1911, 12, 20, 4244, 11,0, 1,
1976, 11, 22, 4309, 10,0, 1,
};
Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
StringBuilder buf = new StringBuilder();
logln("Gregorian -> Korean Lunar (Dangi)");
Calendar grego = Calendar.getInstance();
grego.clear();
for (int i = 0; i < DATA.length;) {
grego.set(DATA[i++], DATA[i++] - 1, DATA[i++]);
Date date = grego.getTime();
cal.setTime(date);
int y = cal.get(Calendar.EXTENDED_YEAR);
int m = cal.get(Calendar.MONTH) + 1; // 0-based -> 1-based
int L = cal.get(Calendar.IS_LEAP_MONTH);
int d = cal.get(Calendar.DAY_OF_MONTH);
int yE = DATA[i++]; // Expected y, m, isLeapMonth, d
int mE = DATA[i++]; // 1-based
int LE = DATA[i++];
int dE = DATA[i++];
buf.setLength(0);
buf.append(date + " -> ");
buf.append(y + "/" + m + (L == 1 ? "(leap)" : "") + "/" + d);
if (y == yE && m == mE && L == LE && d == dE) {
logln("OK: " + buf.toString());
} else {
errln("Fail: " + buf.toString() + ", expected " + yE + "/" + mE + (LE == 1 ? "(leap)" : "") + "/" + dE);
}
}
logln("Korean Lunar (Dangi) -> Gregorian");
for (int i = 0; i < DATA.length;) {
grego.set(DATA[i++], DATA[i++] - 1, DATA[i++]);
Date dexp = grego.getTime();
int cyear = DATA[i++];
int cmonth = DATA[i++];
int cisleapmonth = DATA[i++];
int cdayofmonth = DATA[i++];
cal.clear();
cal.set(Calendar.EXTENDED_YEAR, cyear);
cal.set(Calendar.MONTH, cmonth - 1);
cal.set(Calendar.IS_LEAP_MONTH, cisleapmonth);
cal.set(Calendar.DAY_OF_MONTH, cdayofmonth);
Date date = cal.getTime();
buf.setLength(0);
buf.append(cyear + "/" + cmonth + (cisleapmonth == 1 ? "(leap)" : "") + "/" + cdayofmonth);
buf.append(" -> " + date);
if (date.equals(dexp)) {
logln("OK: " + buf.toString());
} else {
errln("Fail: " + buf.toString() + ", expected " + dexp);
}
}
}
/**
* Make sure no Gregorian dates map to Chinese 1-based day of
* month zero. This was a problem with some of the astronomical
* new moon determinations.
*/
public void TestZeroDOM() {
Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
GregorianCalendar greg = new GregorianCalendar(1989, Calendar.SEPTEMBER, 1);
logln("Start: " + greg.getTime());
for (int i=0; i<1000; ++i) {
cal.setTimeInMillis(greg.getTimeInMillis());
if (cal.get(Calendar.DAY_OF_MONTH) == 0) {
errln("Fail: " + greg.getTime() + " -> " +
cal.get(Calendar.YEAR) + "/" +
cal.get(Calendar.MONTH) +
(cal.get(Calendar.IS_LEAP_MONTH)==1?"(leap)":"") +
"/" + cal.get(Calendar.DAY_OF_MONTH));
}
greg.add(Calendar.DAY_OF_YEAR, 1);
}
logln("End: " + greg.getTime());
}
/**
* Test minimum and maximum functions.
*/
public void TestLimits() {
// The number of days and the start date can be adjusted
// arbitrarily to either speed up the test or make it more
// thorough, but try to test at least a full year, preferably a
// full non-leap and a full leap year.
// Final parameter is either number of days, if > 0, or test
// duration in seconds, if < 0.
Calendar tempcal = Calendar.getInstance();
tempcal.clear();
tempcal.set(1989, Calendar.NOVEMBER, 1);
Calendar dangi = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
doLimitsTest(dangi, null, tempcal.getTime());
doTheoreticalLimitsTest(dangi, true);
}
/**
* Make sure IS_LEAP_MONTH participates in field resolution.
*/
public void TestResolution() {
Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
DateFormat fmt = DateFormat.getDateInstance(cal, DateFormat.DEFAULT);
// May 22 4334 = y4334 m4 d30 doy119
// May 23 4334 = y4334 m4* d1 doy120
final int THE_YEAR = 4334;
final int END = -1;
int[] DATA = {
// Format:
// (field, value)+, END, exp.month, exp.isLeapMonth, exp.DOM
// Note: exp.month is ONE-BASED
// If we set DAY_OF_YEAR only, that should be used
Calendar.DAY_OF_YEAR, 1,
END,
1,0,1, // Expect 1-1
// If we set MONTH only, that should be used
Calendar.IS_LEAP_MONTH, 1,
Calendar.DAY_OF_MONTH, 1,
Calendar.MONTH, 3,
END,
4,1,1, // Expect 4*-1
// If we set the DOY last, that should take precedence
Calendar.MONTH, 1, // Should ignore
Calendar.IS_LEAP_MONTH, 1, // Should ignore
Calendar.DAY_OF_MONTH, 1, // Should ignore
Calendar.DAY_OF_YEAR, 121,
END,
4,1,2, // Expect 4*-2
// If we set IS_LEAP_MONTH last, that should take precedence
Calendar.MONTH, 3,
Calendar.DAY_OF_MONTH, 1,
Calendar.DAY_OF_YEAR, 5, // Should ignore
Calendar.IS_LEAP_MONTH, 1,
END,
4,1,1, // Expect 4*-1
};
StringBuilder buf = new StringBuilder();
for (int i=0; i<DATA.length; ) {
cal.clear();
cal.set(Calendar.EXTENDED_YEAR, THE_YEAR);
buf.setLength(0);
buf.append("EXTENDED_YEAR=" + THE_YEAR);
while (DATA[i] != END) {
cal.set(DATA[i++], DATA[i++]);
buf.append(" " + fieldName(DATA[i-2]) + "=" + DATA[i-1]);
}
++i; // Skip over END mark
int expMonth = DATA[i++]-1;
int expIsLeapMonth = DATA[i++];
int expDOM = DATA[i++];
int month = cal.get(Calendar.MONTH);
int isLeapMonth = cal.get(Calendar.IS_LEAP_MONTH);
int dom = cal.get(Calendar.DAY_OF_MONTH);
if (expMonth == month && expIsLeapMonth == isLeapMonth &&
dom == expDOM) {
logln("OK: " + buf + " => " + fmt.format(cal.getTime()));
} else {
String s = fmt.format(cal.getTime());
cal.clear();
cal.set(Calendar.EXTENDED_YEAR, THE_YEAR);
cal.set(Calendar.MONTH, expMonth);
cal.set(Calendar.IS_LEAP_MONTH, expIsLeapMonth);
cal.set(Calendar.DAY_OF_MONTH, expDOM);
errln("Fail: " + buf + " => " + s +
"=" + (month+1) + "," + isLeapMonth + "," + dom +
", expected " + fmt.format(cal.getTime()) +
"=" + (expMonth+1) + "," + expIsLeapMonth + "," + expDOM);
}
}
}
/**
* Test the behavior of fields that are out of range.
*/
public void TestOutOfRange() {
int[] DATA = new int[] {
// Input Output
4334, 13, 1, 4335, 1, 1,
4334, 18, 1, 4335, 6, 1,
4335, 0, 1, 4334, 12, 1,
4335, -6, 1, 4334, 6, 1,
4334, 1, 32, 4334, 2, 2, // 1-4334 has 30 days
4334, 2, -1, 4334, 1, 29,
};
Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
for (int i = 0; i < DATA.length;) {
int y1 = DATA[i++];
int m1 = DATA[i++] - 1;
int d1 = DATA[i++];
int y2 = DATA[i++];
int m2 = DATA[i++] - 1;
int d2 = DATA[i++];
cal.clear();
cal.set(Calendar.EXTENDED_YEAR, y1);
cal.set(MONTH, m1);
cal.set(DATE, d1);
int y = cal.get(Calendar.EXTENDED_YEAR);
int m = cal.get(MONTH);
int d = cal.get(DATE);
if (y != y2 || m != m2 || d != d2) {
errln("Fail: " + y1 + "/" + (m1 + 1) + "/" + d1 + " resolves to " + y + "/" + (m + 1) + "/" + d
+ ", expected " + y2 + "/" + (m2 + 1) + "/" + d2);
} else if (isVerbose()) {
logln("OK: " + y1 + "/" + (m1 + 1) + "/" + d1 + " resolves to " + y + "/" + (m + 1) + "/" + d);
}
}
}
/**
* Test the behavior of KoreanLunarCalendar.add(). The only real
* nastiness with roll is the MONTH field around leap months.
*/
public void TestAdd() {
int[][] tests = new int[][] {
// MONTHS ARE 1-BASED HERE
// input add output
// year mon day field amount year mon day
{ 4338, 3,0, 15, MONTH, 3, 4338, 6,0, 15 }, // normal
{ 4335, 12,0, 15, MONTH, 1, 4336, 1,0, 15 }, // across year
{ 4336, 1,0, 15, MONTH, -1, 4335, 12,0, 15 }, // across year
{ 4334, 3,0, 15, MONTH, 3, 4334, 5,0, 15 }, // 4=leap
{ 4334, 3,0, 15, MONTH, 2, 4334, 4,1, 15 }, // 4=leap
{ 4334, 4,0, 15, MONTH, 1, 4334, 4,1, 15 }, // 4=leap
{ 4334, 4,1, 15, MONTH, 1, 4334, 5,0, 15 }, // 4=leap
{ 4334, 3,0, 30, MONTH, 2, 4334, 4,1, 29 }, // dom should pin
{ 4334, 3,0, 30, MONTH, 3, 4334, 5,0, 30 }, // no dom pin
{ 4334, 3,0, 30, MONTH, 4, 4334, 6,0, 29 }, // dom should pin
};
Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
doRollAddDangi(ADD, cal, tests);
}
/**
* Test the behavior of KoreanLunarCalendar.roll(). The only real
* nastiness with roll is the MONTH field around leap months.
*/
public void TestRoll() {
int[][] tests = new int[][] {
// MONTHS ARE 1-BASED HERE
// input add output
// year mon day field amount year mon day
{ 4338, 3,0, 15, MONTH, 3, 4338, 6,0, 15 }, // normal
{ 4338, 3,0, 15, MONTH, 11, 4338, 2,0, 15 }, // normal
{ 4335, 12,0, 15, MONTH, 1, 4335, 1,0, 15 }, // across year
{ 4336, 1,0, 15, MONTH, -1, 4336, 12,0, 15 }, // across year
{ 4334, 3,0, 15, MONTH, 3, 4334, 5,0, 15 }, // 4=leap
{ 4334, 3,0, 15, MONTH, 16, 4334, 5,0, 15 }, // 4=leap
{ 4334, 3,0, 15, MONTH, 2, 4334, 4,1, 15 }, // 4=leap
{ 4334, 3,0, 15, MONTH, 28, 4334, 4,1, 15 }, // 4=leap
{ 4334, 4,0, 15, MONTH, 1, 4334, 4,1, 15 }, // 4=leap
{ 4334, 4,0, 15, MONTH, -12, 4334, 4,1, 15 }, // 4=leap
{ 4334, 4,1, 15, MONTH, 1, 4334, 5,0, 15 }, // 4=leap
{ 4334, 4,1, 15, MONTH, -25, 4334, 5,0, 15 }, // 4=leap
{ 4334, 3,0, 30, MONTH, 2, 4334, 4,1, 29 }, // dom should pin
{ 4334, 3,0, 30, MONTH, 15, 4334, 4,1, 29 }, // dom should pin
{ 4334, 3,0, 30, MONTH, 16, 4334, 5,0, 30 }, // no dom pin
{ 4334, 3,0, 30, MONTH, -9, 4334, 6,0, 29 }, // dom should pin
};
Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
doRollAddDangi(ROLL, cal, tests);
}
void doRollAddDangi(boolean roll, Calendar cal, int[][] tests) {
String name = roll ? "rolling" : "adding";
for (int i = 0; i < tests.length; i++) {
int[] test = tests[i];
cal.clear();
cal.set(Calendar.EXTENDED_YEAR, test[0]);
cal.set(Calendar.MONTH, test[1] - 1);
cal.set(Calendar.IS_LEAP_MONTH, test[2]);
cal.set(Calendar.DAY_OF_MONTH, test[3]);
if (roll) {
cal.roll(test[4], test[5]);
} else {
cal.add(test[4], test[5]);
}
if (cal.get(Calendar.EXTENDED_YEAR) != test[6] || cal.get(MONTH) != (test[7] - 1)
|| cal.get(Calendar.IS_LEAP_MONTH) != test[8] || cal.get(DATE) != test[9]) {
errln("Fail: " + name + " " + ymdToString(test[0], test[1] - 1, test[2], test[3]) + " "
+ fieldName(test[4]) + " by " + test[5] + ": expected "
+ ymdToString(test[6], test[7] - 1, test[8], test[9]) + ", got " + ymdToString(cal));
} else if (isVerbose()) {
logln("OK: " + name + " " + ymdToString(test[0], test[1] - 1, test[2], test[3]) + " "
+ fieldName(test[4]) + " by " + test[5] + ": got " + ymdToString(cal));
}
}
}
/**
* Convert year,month,day values to the form "year/month/day".
* On input the month value is zero-based, but in the result string it is one-based.
*/
static public String ymdToString(int year, int month, int isLeapMonth, int day) {
return "" + year + "/" + (month + 1) + ((isLeapMonth != 0) ? "(leap)" : "") + "/" + day;
}
public void TestCoverage() {
// DangiCalendar()
// DangiCalendar(Date)
// DangiCalendar(TimeZone, ULocale)
Date d = new Date();
DangiCalendar cal1 = new DangiCalendar();
cal1.setTime(d);
DangiCalendar cal2 = new DangiCalendar(d);
DangiCalendar cal3 = new DangiCalendar(TimeZone.getDefault(), ULocale.getDefault());
cal3.setTime(d);
assertEquals("DangiCalendar() and DangiCalendar(Date)", cal1, cal2);
assertEquals("DangiCalendar() and DangiCalendar(TimeZone,ULocale)", cal1, cal3);
// String getType()
String type = cal1.getType();
assertEquals("getType()", "dangi", type);
}
public void TestInitWithCurrentTime() {
// If the chinese calendar current millis isn't called, the default year is wrong.
// this test is assuming the 'year' is the current cycle
// so when we cross a cycle boundary, the target will need to change
// that shouldn't be for awhile yet...
Calendar cc = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
cc.set(Calendar.EXTENDED_YEAR, 4338);
cc.set(Calendar.MONTH, 0);
// need to set leap month flag off, otherwise, the test case always fails when
// current time is in a leap month
cc.set(Calendar.IS_LEAP_MONTH, 0);
cc.set(Calendar.DATE, 19);
cc.set(Calendar.HOUR_OF_DAY, 0);
cc.set(Calendar.MINUTE, 0);
cc.set(Calendar.SECOND, 0);
cc.set(Calendar.MILLISECOND, 0);
cc.add(Calendar.DATE, 1);
Calendar cal = new GregorianCalendar(2005, Calendar.FEBRUARY, 28);
Date target = cal.getTime();
Date result = cc.getTime();
assertEquals("chinese and gregorian date should match", target, result);
}
}

View File

@ -3391,6 +3391,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
new ULocale("en@calendar=islamic"), new ULocale("en@calendar=islamic"),
new ULocale("ja_JP@calendar=japanese"), new ULocale("ja_JP@calendar=japanese"),
new ULocale("zh_Hans_CN@calendar=bogus"), new ULocale("zh_Hans_CN@calendar=bogus"),
new ULocale("ko_KR@calendar=dangi"),
}; };
SimpleDateFormat[] formatters = new SimpleDateFormat[5]; SimpleDateFormat[] formatters = new SimpleDateFormat[5];

View File

@ -1,6 +1,6 @@
/* /*
******************************************************************************* *******************************************************************************
* Copyright (C) 1996-2007, International Business Machines Corporation and * * Copyright (C) 1996-2012, International Business Machines Corporation and *
* others. All Rights Reserved. * * others. All Rights Reserved. *
******************************************************************************* *******************************************************************************
* *
@ -14,6 +14,7 @@ import com.ibm.icu.util.BuddhistCalendar;
import com.ibm.icu.util.Calendar; import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.ChineseCalendar; import com.ibm.icu.util.ChineseCalendar;
import com.ibm.icu.util.CopticCalendar; import com.ibm.icu.util.CopticCalendar;
import com.ibm.icu.util.DangiCalendar;
import com.ibm.icu.util.EthiopicCalendar; import com.ibm.icu.util.EthiopicCalendar;
import com.ibm.icu.util.GregorianCalendar; import com.ibm.icu.util.GregorianCalendar;
import com.ibm.icu.util.HebrewCalendar; import com.ibm.icu.util.HebrewCalendar;
@ -22,6 +23,7 @@ import com.ibm.icu.util.IslamicCalendar;
import com.ibm.icu.util.JapaneseCalendar; import com.ibm.icu.util.JapaneseCalendar;
import com.ibm.icu.util.TaiwanCalendar; import com.ibm.icu.util.TaiwanCalendar;
import com.ibm.icu.util.TimeZone; import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;
/** /**
* @author emader * @author emader
@ -110,6 +112,22 @@ public class CalendarTests
} }
} }
static class DangiCalendarHandler extends CalendarHandler
{
public Object[] getTestObjects()
{
Locale locales[] = SerializableTest.getLocales();
TimeZone kst = TimeZone.getTimeZone("Asia/Seoul");
DangiCalendar calendars[] = new DangiCalendar[locales.length];
for (int i = 0; i < locales.length; i += 1) {
calendars[i] = new DangiCalendar(kst, ULocale.forLocale(locales[i]));
}
return calendars;
}
}
static class EthiopicCalendarHandler extends CalendarHandler static class EthiopicCalendarHandler extends CalendarHandler
{ {
public Object[] getTestObjects() public Object[] getTestObjects()

View File

@ -682,6 +682,7 @@ public class SerializableTest extends TestFmwk.TestGroup
map.put("com.ibm.icu.util.BuddhistCalendar", new CalendarTests.BuddhistCalendarHandler()); map.put("com.ibm.icu.util.BuddhistCalendar", new CalendarTests.BuddhistCalendarHandler());
map.put("com.ibm.icu.util.ChineseCalendar", new CalendarTests.ChineseCalendarHandler()); map.put("com.ibm.icu.util.ChineseCalendar", new CalendarTests.ChineseCalendarHandler());
map.put("com.ibm.icu.util.CopticCalendar", new CalendarTests.CopticCalendarHandler()); map.put("com.ibm.icu.util.CopticCalendar", new CalendarTests.CopticCalendarHandler());
map.put("com.ibm.icu.util.DangiCalendar", new CalendarTests.DangiCalendarHandler());
map.put("com.ibm.icu.util.EthiopicCalendar", new CalendarTests.EthiopicCalendarHandler()); map.put("com.ibm.icu.util.EthiopicCalendar", new CalendarTests.EthiopicCalendarHandler());
map.put("com.ibm.icu.util.GregorianCalendar", new CalendarTests.GregorianCalendarHandler()); map.put("com.ibm.icu.util.GregorianCalendar", new CalendarTests.GregorianCalendarHandler());
map.put("com.ibm.icu.util.HebrewCalendar", new CalendarTests.HebrewCalendarHandler()); map.put("com.ibm.icu.util.HebrewCalendar", new CalendarTests.HebrewCalendarHandler());