ICU-12973 CLDR Japanese Era data and tentative Japanese new era support (#111)

* Updated era data format in supplementalData.

* Include tentative era names in data. Implemented Japanese era loaded from CLDR data in ICU4J.

* ICU4C implementation, ICU4C refactoring. WIP.

* VS project updates and some bug fixes

Also added API comments.

* Review feedback and bug fixes

- NULL to nullptr
- use of LocalUResourceBundlePointer
- TYPO "name" to "named"
- env var checking stricmp() == 0

* API comment correction based on feedback

* Duplicate the comment in ucal.h to calendar.h

* Fixed spelling errors in API comment
This commit is contained in:
Yoshito Umaoka 2018-09-12 17:13:30 -04:00 committed by Shane Carr
parent ddf8ff2b7d
commit 45cdda61a6
No known key found for this signature in database
GPG key ID: FCED3B24AAB18B5C
52 changed files with 2089 additions and 1247 deletions

View file

@ -1635,6 +1635,7 @@ ar{
"تيشو",
"شووا",
"هيسي",
"Qqqq",
}
}
}

View file

@ -2304,6 +2304,7 @@ ast{
"Taishō",
"e. Shōwa",
"Heisei",
"Qqqq",
}
narrow{
"Taika",
@ -2542,6 +2543,7 @@ ast{
"T",
"S",
"H",
"Q",
}
wide{
"Taika (645650)",
@ -2780,6 +2782,7 @@ ast{
"Taishō",
"era Shōwa",
"Heisei",
"Qqqq",
}
}
intervalFormats{

View file

@ -4105,6 +4105,7 @@ br{
"Taishō",
"Shōwa",
"Heisei",
"Qqqq",
}
narrow{
"Taika (645650)",
@ -4343,6 +4344,7 @@ br{
"T",
"S",
"H",
"Q",
}
wide{
"Taika (645650)",
@ -4581,6 +4583,7 @@ br{
"Taishō",
"Shōwa",
"Heisei",
"Qqqq",
}
}
intervalFormats{

View file

@ -1035,6 +1035,7 @@ bs_Cyrl{
"Таишо",
"Шова",
"Хаисеи",
"Qqqq",
}
}
}

View file

@ -3697,6 +3697,7 @@ cs{
"Taishō",
"Shōwa",
"Heisei",
"Qqqq",
}
narrow{
"Taika (645650)",
@ -3935,6 +3936,7 @@ cs{
"T",
"S",
"H",
"Q",
}
wide{
"Taika (645650)",
@ -4173,6 +4175,7 @@ cs{
"Taishō",
"Shōwa",
"Heisei",
"Qqqq",
}
}
intervalFormats{

View file

@ -1601,6 +1601,7 @@ de{
"Taishō",
"Shōwa",
"Heisei",
"Qqqq",
}
}
}

View file

@ -1837,6 +1837,7 @@ he{
"Taishō",
"Shōwa",
"Heisei",
"Qqqq",
}
}
}

View file

@ -1239,6 +1239,7 @@ hi{
"ताईशो",
"शोवा",
"हेईसेई",
"Qqqq",
}
}
}

View file

@ -1577,6 +1577,7 @@ hr{
"Taishō",
"Shōwa",
"Heisei",
"Qqqq",
}
}
}

View file

@ -2217,6 +2217,7 @@ id{
"Taishō",
"Shōwa",
"Heisei",
"Qqqq",
}
}
}

View file

@ -2464,6 +2464,7 @@ ja{
"大正",
"昭和",
"平成",
"",
}
narrow{
"大化",
@ -2702,6 +2703,7 @@ ja{
"T",
"S",
"H",
"Q",
}
}
}

View file

@ -1909,6 +1909,7 @@ ko{
"다이쇼",
"쇼와",
"헤이세이",
"Qqqq",
}
}
}

View file

@ -1845,6 +1845,7 @@ lo{
"ໄຕໂຊ",
"ໂຊວາ",
"ຮີຊີ",
"Qqqq",
}
}
}

View file

@ -2466,6 +2466,7 @@ lt{
"Taišo",
"Šova",
"Heisei",
"Qqqq",
}
}
}

View file

@ -5483,6 +5483,7 @@ nb{
"Taishō",
"Shōwa",
"Heisei",
"Qqqq",
}
narrow{
"Taika (645650)",
@ -5721,6 +5722,7 @@ nb{
"T",
"S",
"H",
"Q",
}
wide{
"Taika (645650)",
@ -5959,6 +5961,7 @@ nb{
"Taishō",
"Shōwa",
"Heisei",
"Qqqq",
}
}
intervalFormats{

View file

@ -4894,6 +4894,7 @@ nl{
"Taishō",
"Shōwa",
"Heisei",
"Qqqq",
}
narrow{
"Taika (645650)",
@ -5132,6 +5133,7 @@ nl{
"T",
"S",
"H",
"Q",
}
wide{
"Taika (645650)",
@ -5370,6 +5372,7 @@ nl{
"Taishō",
"Shōwa",
"Heisei",
"Qqqq",
}
}
}

View file

@ -1713,6 +1713,7 @@ root{
"Taishō",
"Shōwa",
"Heisei",
"QQ",
}
narrow{
"Taika (645650)",
@ -1951,6 +1952,7 @@ root{
"T",
"S",
"H",
"Q",
}
wide:alias{"/LOCALE/calendar/japanese/eras/abbreviated"}
}

View file

@ -1837,6 +1837,7 @@ ru{
"Эпоха Тайсьо",
"Сьова",
"Эпоха Хэйсэй",
"Qqqq",
}
}
}

View file

@ -1256,6 +1256,7 @@ sr{
"Таишо",
"Шова",
"Хаисеи",
"Qqqq",
}
}
}

View file

@ -1257,6 +1257,7 @@ sr_Latn{
"Taišo",
"Šova",
"Haisei",
"Qqqq",
}
}
}

View file

@ -1630,6 +1630,7 @@ sv{
"Taishō",
"Shōwa",
"Heisei",
"Qqqq",
}
}
}

View file

@ -2332,6 +2332,7 @@ th{
"ทะอิโช",
"โชวะ",
"เฮเซ",
"Qqqq",
}
}
}

View file

@ -2394,6 +2394,7 @@ yue{
"大正",
"昭和",
"平成",
"Qqqq",
}
}
}

View file

@ -2392,6 +2392,7 @@ yue_Hans{
"大正",
"昭和",
"平成",
"Qqqq",
}
}
}

View file

@ -3403,6 +3403,7 @@ zh{
"大正",
"昭和",
"平成",
"Qqqq",
}
narrow{
"大化(645650)",
@ -3641,6 +3642,7 @@ zh{
"T",
"S",
"H",
"Q",
}
wide{
"大化 (645650)",
@ -3879,6 +3881,7 @@ zh{
"大正",
"昭和",
"平成",
"Qqqq",
}
}
}

View file

@ -5737,6 +5737,7 @@ zh_Hant{
"大正",
"昭和",
"平成",
"Qqqq",
}
narrow{
"大化",
@ -5975,6 +5976,7 @@ zh_Hant{
"大正",
"昭和",
"平成",
"Q",
}
wide{
"大化",
@ -6213,6 +6215,7 @@ zh_Hant{
"大正",
"昭和",
"平成",
"Qqqq",
}
}
intervalFormats{

File diff suppressed because it is too large Load diff

View file

@ -111,6 +111,7 @@ double-conversion-fast-dtoa.o double-conversion-strtod.o \
numparse_stringsegment.o numparse_parsednumber.o numparse_impl.o \
numparse_symbols.o numparse_decimal.o numparse_scientific.o numparse_currency.o \
numparse_affixes.o numparse_compositions.o numparse_validators.o \
erarules.o
## Header files to install

View file

@ -0,0 +1,313 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#include <stdlib.h>
#include "unicode/ucal.h"
#include "unicode/ures.h"
#include "unicode/ustring.h"
#include "cmemory.h"
#include "cstring.h"
#include "erarules.h"
#include "gregoimp.h"
#include "uassert.h"
U_NAMESPACE_BEGIN
static const int32_t MAX_ENCODED_START_YEAR = 32767;
static const int32_t MIN_ENCODED_START_YEAR = -32768;
static const int32_t MIN_ENCODED_START = -2147483391; // encodeDate(MIN_ENCODED_START_YEAR, 1, 1, ...);
static const int32_t YEAR_MASK = 0xFFFF0000;
static const int32_t MONTH_MASK = 0x0000FF00;
static const int32_t DAY_MASK = 0x000000FF;
static const int32_t MAX_INT32 = 0x7FFFFFFF;
static const int32_t MIN_INT32 = 0xFFFFFFFF;
static const UChar VAL_FALSE[] = {0x66, 0x61, 0x6c, 0x73, 0x65}; // "false"
static const UChar VAL_FALSE_LEN = 5;
static UBool isSet(int startDate) {
return startDate != 0;
}
static UBool isValidRuleStartDate(int32_t year, int32_t month, int32_t day) {
return year >= MIN_ENCODED_START_YEAR && year <= MAX_ENCODED_START_YEAR
&& month >= 1 && month <= 12 && day >=1 && day <= 31;
}
/**
* Encode year/month/date to a single integer.
* year is high 16 bits (-32768 to 32767), month is
* next 8 bits and day of month is last 8 bits.
*
* @param year year
* @param month month (1-base)
* @param day day of month
* @return an encoded date.
*/
static int32_t encodeDate(int32_t year, int32_t month, int32_t day) {
return year << 16 | month << 8 | day;
}
static void decodeDate(int32_t encodedDate, int32_t (&fields)[3]) {
if (encodedDate == MIN_ENCODED_START) {
fields[0] = MIN_INT32;
fields[1] = 1;
fields[2] = 1;
} else {
fields[0] = (encodedDate & YEAR_MASK) >> 16;
fields[1] = (encodedDate & MONTH_MASK) >> 8;
fields[2] = encodedDate & DAY_MASK;
}
}
/**
* Compare an encoded date with another date specified by year/month/day.
* @param encoded An encoded date
* @param year Year of another date
* @param month Month of another date
* @param day Day of another date
* @return -1 when encoded date is earlier, 0 when two dates are same,
* and 1 when encoded date is later.
*/
static int32_t compareEncodedDateWithYMD(int encoded, int year, int month, int day) {
if (year < MIN_ENCODED_START_YEAR) {
if (encoded == MIN_ENCODED_START) {
if (year > MIN_INT32 || month > 1 || day > 1) {
return -1;
}
return 0;
} else {
return 1;
}
} else if (year > MAX_ENCODED_START_YEAR) {
return -1;
} else {
int tmp = encodeDate(year, month, day);
if (encoded < tmp) {
return -1;
} else if (encoded == tmp) {
return 0;
} else {
return 1;
}
}
}
EraRules::EraRules(int32_t *startDates, int32_t numEras)
: startDates(startDates), numEras(numEras) {
initCurrentEra();
}
EraRules::~EraRules() {
uprv_free(startDates);
}
EraRules* EraRules::createInstance(const char *calType, UBool includeTentativeEra, UErrorCode& status) {
if(U_FAILURE(status)) {
return nullptr;
}
LocalUResourceBundlePointer rb(ures_openDirect(nullptr, "supplementalData", &status));
ures_getByKey(rb.getAlias(), "calendarData", rb.getAlias(), &status);
ures_getByKey(rb.getAlias(), calType, rb.getAlias(), &status);
ures_getByKey(rb.getAlias(), "eras", rb.getAlias(), &status);
if (U_FAILURE(status)) {
return nullptr;
}
int32_t numEras = ures_getSize(rb.getAlias());
int32_t firstTentativeIdx = MAX_INT32;
int32_t *startDates = (int32_t*)uprv_malloc(numEras * sizeof(int32_t));
if (startDates == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
uprv_memset(startDates, 0, numEras * sizeof(int32_t));
while (ures_hasNext(rb.getAlias())) {
LocalUResourceBundlePointer eraRuleRes(ures_getNextResource(rb.getAlias(), nullptr, &status));
if (U_FAILURE(status)) {
goto error;
}
const char *eraIdxStr = ures_getKey(eraRuleRes.getAlias());
char *endp;
int32_t eraIdx = (int32_t)strtol(eraIdxStr, &endp, 10);
if ((size_t)(endp - eraIdxStr) != uprv_strlen(eraIdxStr)) {
status = U_INVALID_FORMAT_ERROR;
goto error;
}
if (eraIdx < 0 || eraIdx >= numEras) {
status = U_INVALID_FORMAT_ERROR;
goto error;
}
if (isSet(startDates[eraIdx])) {
// start date of the index was already set
status = U_INVALID_FORMAT_ERROR;
goto error;
}
UBool hasName = TRUE;
UBool hasEnd = TRUE;
int32_t len;
while (ures_hasNext(eraRuleRes.getAlias())) {
LocalUResourceBundlePointer res(ures_getNextResource(eraRuleRes.getAlias(), nullptr, &status));
if (U_FAILURE(status)) {
goto error;
}
const char *key = ures_getKey(res.getAlias());
if (uprv_strcmp(key, "start") == 0) {
const int32_t *fields = ures_getIntVector(res.getAlias(), &len, &status);
if (U_FAILURE(status)) {
goto error;
}
if (len != 3 || !isValidRuleStartDate(fields[0], fields[1], fields[2])) {
status = U_INVALID_FORMAT_ERROR;
goto error;
}
startDates[eraIdx] = encodeDate(fields[0], fields[1], fields[2]);
} else if (uprv_strcmp(key, "named") == 0) {
const UChar *val = ures_getString(res.getAlias(), &len, &status);
if (u_strncmp(val, VAL_FALSE, VAL_FALSE_LEN) == 0) {
hasName = FALSE;
}
} else if (uprv_strcmp(key, "end") == 0) {
hasEnd = TRUE;
}
}
if (isSet(startDates[eraIdx])) {
if (hasEnd) {
// This implementation assumes either start or end is available, not both.
// For now, just ignore the end rule.
}
} else {
if (hasEnd) {
if (eraIdx != 0) {
// This implementation does not support end only rule for eras other than
// the first one.
status = U_INVALID_FORMAT_ERROR;
goto error;
}
U_ASSERT(eraIdx == 0);
startDates[eraIdx] = MIN_ENCODED_START;
} else {
status = U_INVALID_FORMAT_ERROR;
goto error;
}
}
if (hasName) {
if (eraIdx >= firstTentativeIdx) {
status = U_INVALID_FORMAT_ERROR;
goto error;
}
} else {
if (eraIdx < firstTentativeIdx) {
firstTentativeIdx = eraIdx;
}
}
}
EraRules *result;
if (firstTentativeIdx < MAX_INT32 && !includeTentativeEra) {
result = new EraRules(startDates, firstTentativeIdx);
} else {
result = new EraRules(startDates, numEras);
}
if (result == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
}
return result;
error:
uprv_free(startDates);
return nullptr;
}
void EraRules::getStartDate(int32_t eraIdx, int32_t (&fields)[3], UErrorCode& status) const {
if(U_FAILURE(status)) {
return;
}
if (eraIdx < 0 || eraIdx >= numEras) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
decodeDate(startDates[eraIdx], fields);
}
int32_t EraRules::getStartYear(int32_t eraIdx, UErrorCode& status) const {
int year = MAX_INT32; // bogus value
if(U_FAILURE(status)) {
return year;
}
if (eraIdx < 0 || eraIdx >= numEras) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return year;
}
int fields[3];
decodeDate(startDates[eraIdx], fields);
year = fields[0];
return year;
}
int32_t EraRules::getEraIndex(int32_t year, int32_t month, int32_t day, UErrorCode& status) const {
if(U_FAILURE(status)) {
return -1;
}
if (month < 1 || month > 12 || day < 1 || day > 31) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return -1;
}
int32_t high = numEras; // last index + 1
int32_t low;
// Short circuit for recent years. Most modern computations will
// occur in the last few eras.
if (compareEncodedDateWithYMD(startDates[getCurrentEraIndex()], year, month, day) <= 0) {
low = getCurrentEraIndex();
} else {
low = 0;
}
// Do binary search
while (low < high - 1) {
int i = (low + high) / 2;
if (compareEncodedDateWithYMD(startDates[i], year, month, day) <= 0) {
low = i;
} else {
high = i;
}
}
return low;
}
void EraRules::initCurrentEra() {
UDate now = ucal_getNow();
int year, month0, dom, dow, doy, mid;
Grego::timeToFields(now, year, month0, dom, dow, doy, mid);
int currentEncodedDate = encodeDate(year, month0 + 1 /* changes to 1-base */, dom);
int eraIdx = numEras - 1;
while (eraIdx > 0) {
if (currentEncodedDate >= startDates[eraIdx]) {
break;
}
eraIdx--;
}
// Note: current era could be before the first era.
// In this case, this implementation returns the first era index (0).
currentEra = eraIdx;}
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,80 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#ifndef ERARULES_H_
#define ERARULES_H_
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#include "unicode/uobject.h"
U_NAMESPACE_BEGIN
class U_I18N_API EraRules : public UMemory {
public:
~EraRules();
static EraRules* createInstance(const char *calType, UBool includeTentativeEra, UErrorCode& status);
/**
* Gets number of effective eras
* @return number of effective eras
*/
inline int32_t getNumberOfEras() const {
return numEras;
}
/**
* Gets start date of an era
* @param eraIdx Era index
* @param fields Receives date fields. The result includes values of year, month,
* day of month in this order. When an era has no start date, the result
* will be January 1st in year whose value is minimum integer.
* @param status Receives status.
*/
void getStartDate(int32_t eraIdx, int32_t (&fields)[3], UErrorCode& status) const;
/**
* Gets start year of an era
* @param eraIdx Era index
* @param status Receives status.
* @return The first year of an era. When a era has no start date, minimum int32
* value is returned.
*/
int32_t getStartYear(int32_t eraIdx, UErrorCode& status) const;
/**
* Returns era index for the specified year/month/day.
* @param year Year
* @param month Month (1-base)
* @param day Day of month
* @param status Receives status
* @return era index (or 0, when the specified date is before the first era)
*/
int32_t getEraIndex(int32_t year, int32_t month, int32_t day, UErrorCode& status) const;
/**
* Gets the current era index. This is calculated only once for an instance of
* EraRules.
*
* @return era index of current era (or 0, when current date is before the first era)
*/
inline int32_t getCurrentEraIndex() const {
return currentEra;
}
private:
EraRules(int32_t *startDates, int32_t numEra);
void initCurrentEra();
int32_t *startDates;
int32_t numEras;
int32_t currentEra;
};
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* ERARULES_H_ */

View file

@ -160,6 +160,7 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="erarules.cpp" />
<ClCompile Include="region.cpp" />
<ClCompile Include="uregion.cpp" />
<ClCompile Include="alphaindex.cpp" />
@ -410,6 +411,7 @@
<ClInclude Include="collationtailoring.h" />
<ClInclude Include="collationweights.h" />
<ClInclude Include="dayperiodrules.h" />
<ClInclude Include="erarules.h" />
<ClInclude Include="numsys_impl.h" />
<ClInclude Include="region_impl.h" />
<ClInclude Include="selfmtimpl.h" />

View file

@ -627,6 +627,9 @@
<ClCompile Include="dayperiodrules.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="erarules.cpp">
<Filter>formatting</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClCompile Include="bocsu.cpp">
@ -1184,10 +1187,13 @@
<ClInclude Include="utf8collationiterator.h">
<Filter>collation</Filter>
</ClInclude>
<ClInclude Include="erarules.h">
<Filter>formatting</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="i18n.rc">
<Filter>misc</Filter>
</ResourceCompile>
</ItemGroup>
</Project>
</Project>

View file

@ -87,7 +87,7 @@
<CompileAsWinRT>false</CompileAsWinRT>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<ResourceCompile>
<ResourceCompile>
<Culture>0x0409</Culture>
<AdditionalIncludeDirectories>../common;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ResourceCompile>
@ -267,6 +267,7 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="erarules.cpp" />
<ClCompile Include="region.cpp" />
<ClCompile Include="uregion.cpp" />
<ClCompile Include="alphaindex.cpp" />
@ -515,6 +516,7 @@
<ClInclude Include="collationtailoring.h" />
<ClInclude Include="collationweights.h" />
<ClInclude Include="dayperiodrules.h" />
<ClInclude Include="erarules.h" />
<ClInclude Include="numsys_impl.h" />
<ClInclude Include="region_impl.h" />
<ClInclude Include="selfmtimpl.h" />
@ -662,4 +664,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View file

@ -16,286 +16,80 @@
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#if U_PLATFORM_HAS_WINUWP_API == 0
#include <stdlib.h> // getenv() is not available in UWP env
#endif
#include "cmemory.h"
#include "erarules.h"
#include "japancal.h"
#include "unicode/gregocal.h"
#include "umutex.h"
#include "uassert.h"
#include "ucln_in.h"
#include "cstring.h"
//#define U_DEBUG_JCAL
static icu::EraRules * gJapaneseEraRules = nullptr;
static icu::UInitOnce gJapaneseEraRulesInitOnce = U_INITONCE_INITIALIZER;
static int32_t gCurrentEra = 0;
#ifdef U_DEBUG_JCAL
#include <stdio.h>
#endif
U_CDECL_BEGIN
static UBool japanese_calendar_cleanup(void) {
if (gJapaneseEraRules) {
delete gJapaneseEraRules;
gJapaneseEraRules = nullptr;
}
gCurrentEra = 0;
gJapaneseEraRulesInitOnce.reset();
return TRUE;
}
U_CDECL_END
U_NAMESPACE_BEGIN
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(JapaneseCalendar)
// Gregorian date of each emperor's ascension
// Years are AD, months are 1-based.
static const struct {
int16_t year;
int8_t month;
int8_t day;
} kEraInfo[] = {
// Year Month Day
{ 645, 6, 19 }, // Taika 0
{ 650, 2, 15 }, // Hakuchi 1
{ 672, 1, 1 }, // Hakuho 2
{ 686, 7, 20 }, // Shucho 3
{ 701, 3, 21 }, // Taiho 4
{ 704, 5, 10 }, // Keiun 5
{ 708, 1, 11 }, // Wado 6
{ 715, 9, 2 }, // Reiki 7
{ 717, 11, 17 }, // Yoro 8
{ 724, 2, 4 }, // Jinki 9
{ 729, 8, 5 }, // Tempyo 10
{ 749, 4, 14 }, // Tempyo-kampo 11
{ 749, 7, 2 }, // Tempyo-shoho 12
{ 757, 8, 18 }, // Tempyo-hoji 13
{ 765, 1, 7 }, // Tempho-jingo 14
{ 767, 8, 16 }, // Jingo-keiun 15
{ 770, 10, 1 }, // Hoki 16
{ 781, 1, 1 }, // Ten-o 17
{ 782, 8, 19 }, // Enryaku 18
{ 806, 5, 18 }, // Daido 19
{ 810, 9, 19 }, // Konin 20
{ 824, 1, 5 }, // Tencho
{ 834, 1, 3 }, // Showa
{ 848, 6, 13 }, // Kajo
{ 851, 4, 28 }, // Ninju
{ 854, 11, 30 }, // Saiko
{ 857, 2, 21 }, // Tennan
{ 859, 4, 15 }, // Jogan
{ 877, 4, 16 }, // Genkei
{ 885, 2, 21 }, // Ninna
{ 889, 4, 27 }, // Kampyo 30
{ 898, 4, 26 }, // Shotai
{ 901, 7, 15 }, // Engi
{ 923, 4, 11 }, // Encho
{ 931, 4, 26 }, // Shohei
{ 938, 5, 22 }, // Tengyo
{ 947, 4, 22 }, // Tenryaku
{ 957, 10, 27 }, // Tentoku
{ 961, 2, 16 }, // Owa
{ 964, 7, 10 }, // Koho
{ 968, 8, 13 }, // Anna 40
{ 970, 3, 25 }, // Tenroku
{ 973, 12, 20 }, // Ten-en
{ 976, 7, 13 }, // Jogen
{ 978, 11, 29 }, // Tengen
{ 983, 4, 15 }, // Eikan
{ 985, 4, 27 }, // Kanna
{ 987, 4, 5 }, // Ei-en
{ 989, 8, 8 }, // Eiso
{ 990, 11, 7 }, // Shoryaku
{ 995, 2, 22 }, // Chotoku 50
{ 999, 1, 13 }, // Choho
{ 1004, 7, 20 }, // Kanko
{ 1012, 12, 25 }, // Chowa
{ 1017, 4, 23 }, // Kannin
{ 1021, 2, 2 }, // Jian
{ 1024, 7, 13 }, // Manju
{ 1028, 7, 25 }, // Chogen
{ 1037, 4, 21 }, // Choryaku
{ 1040, 11, 10 }, // Chokyu
{ 1044, 11, 24 }, // Kantoku 60
{ 1046, 4, 14 }, // Eisho
{ 1053, 1, 11 }, // Tengi
{ 1058, 8, 29 }, // Kohei
{ 1065, 8, 2 }, // Jiryaku
{ 1069, 4, 13 }, // Enkyu
{ 1074, 8, 23 }, // Shoho
{ 1077, 11, 17 }, // Shoryaku
{ 1081, 2, 10 }, // Eiho
{ 1084, 2, 7 }, // Otoku
{ 1087, 4, 7 }, // Kanji 70
{ 1094, 12, 15 }, // Kaho
{ 1096, 12, 17 }, // Eicho
{ 1097, 11, 21 }, // Shotoku
{ 1099, 8, 28 }, // Kowa
{ 1104, 2, 10 }, // Choji
{ 1106, 4, 9 }, // Kasho
{ 1108, 8, 3 }, // Tennin
{ 1110, 7, 13 }, // Ten-ei
{ 1113, 7, 13 }, // Eikyu
{ 1118, 4, 3 }, // Gen-ei 80
{ 1120, 4, 10 }, // Hoan
{ 1124, 4, 3 }, // Tenji
{ 1126, 1, 22 }, // Daiji
{ 1131, 1, 29 }, // Tensho
{ 1132, 8, 11 }, // Chosho
{ 1135, 4, 27 }, // Hoen
{ 1141, 7, 10 }, // Eiji
{ 1142, 4, 28 }, // Koji
{ 1144, 2, 23 }, // Tenyo
{ 1145, 7, 22 }, // Kyuan 90
{ 1151, 1, 26 }, // Ninpei
{ 1154, 10, 28 }, // Kyuju
{ 1156, 4, 27 }, // Hogen
{ 1159, 4, 20 }, // Heiji
{ 1160, 1, 10 }, // Eiryaku
{ 1161, 9, 4 }, // Oho
{ 1163, 3, 29 }, // Chokan
{ 1165, 6, 5 }, // Eiman
{ 1166, 8, 27 }, // Nin-an
{ 1169, 4, 8 }, // Kao 100
{ 1171, 4, 21 }, // Shoan
{ 1175, 7, 28 }, // Angen
{ 1177, 8, 4 }, // Jisho
{ 1181, 7, 14 }, // Yowa
{ 1182, 5, 27 }, // Juei
{ 1184, 4, 16 }, // Genryuku
{ 1185, 8, 14 }, // Bunji
{ 1190, 4, 11 }, // Kenkyu
{ 1199, 4, 27 }, // Shoji
{ 1201, 2, 13 }, // Kennin 110
{ 1204, 2, 20 }, // Genkyu
{ 1206, 4, 27 }, // Ken-ei
{ 1207, 10, 25 }, // Shogen
{ 1211, 3, 9 }, // Kenryaku
{ 1213, 12, 6 }, // Kenpo
{ 1219, 4, 12 }, // Shokyu
{ 1222, 4, 13 }, // Joo
{ 1224, 11, 20 }, // Gennin
{ 1225, 4, 20 }, // Karoku
{ 1227, 12, 10 }, // Antei 120
{ 1229, 3, 5 }, // Kanki
{ 1232, 4, 2 }, // Joei
{ 1233, 4, 15 }, // Tempuku
{ 1234, 11, 5 }, // Bunryaku
{ 1235, 9, 19 }, // Katei
{ 1238, 11, 23 }, // Ryakunin
{ 1239, 2, 7 }, // En-o
{ 1240, 7, 16 }, // Ninji
{ 1243, 2, 26 }, // Kangen
{ 1247, 2, 28 }, // Hoji 130
{ 1249, 3, 18 }, // Kencho
{ 1256, 10, 5 }, // Kogen
{ 1257, 3, 14 }, // Shoka
{ 1259, 3, 26 }, // Shogen
{ 1260, 4, 13 }, // Bun-o
{ 1261, 2, 20 }, // Kocho
{ 1264, 2, 28 }, // Bun-ei
{ 1275, 4, 25 }, // Kenji
{ 1278, 2, 29 }, // Koan
{ 1288, 4, 28 }, // Shoo 140
{ 1293, 8, 55 }, // Einin
{ 1299, 4, 25 }, // Shoan
{ 1302, 11, 21 }, // Kengen
{ 1303, 8, 5 }, // Kagen
{ 1306, 12, 14 }, // Tokuji
{ 1308, 10, 9 }, // Enkei
{ 1311, 4, 28 }, // Ocho
{ 1312, 3, 20 }, // Showa
{ 1317, 2, 3 }, // Bunpo
{ 1319, 4, 28 }, // Geno 150
{ 1321, 2, 23 }, // Genkyo
{ 1324, 12, 9 }, // Shochu
{ 1326, 4, 26 }, // Kareki
{ 1329, 8, 29 }, // Gentoku
{ 1331, 8, 9 }, // Genko
{ 1334, 1, 29 }, // Kemmu
{ 1336, 2, 29 }, // Engen
{ 1340, 4, 28 }, // Kokoku
{ 1346, 12, 8 }, // Shohei
{ 1370, 7, 24 }, // Kentoku 160
{ 1372, 4, 1 }, // Bunch\u0169
{ 1375, 5, 27 }, // Tenju
{ 1379, 3, 22 }, // Koryaku
{ 1381, 2, 10 }, // Kowa
{ 1384, 4, 28 }, // Gench\u0169
{ 1384, 2, 27 }, // Meitoku
{ 1387, 8, 23 }, // Kakei
{ 1389, 2, 9 }, // Koo
{ 1390, 3, 26 }, // Meitoku
{ 1394, 7, 5 }, // Oei 170
{ 1428, 4, 27 }, // Shocho
{ 1429, 9, 5 }, // Eikyo
{ 1441, 2, 17 }, // Kakitsu
{ 1444, 2, 5 }, // Bun-an
{ 1449, 7, 28 }, // Hotoku
{ 1452, 7, 25 }, // Kyotoku
{ 1455, 7, 25 }, // Kosho
{ 1457, 9, 28 }, // Choroku
{ 1460, 12, 21 }, // Kansho
{ 1466, 2, 28 }, // Bunsho 180
{ 1467, 3, 3 }, // Onin
{ 1469, 4, 28 }, // Bunmei
{ 1487, 7, 29 }, // Chokyo
{ 1489, 8, 21 }, // Entoku
{ 1492, 7, 19 }, // Meio
{ 1501, 2, 29 }, // Bunki
{ 1504, 2, 30 }, // Eisho
{ 1521, 8, 23 }, // Taiei
{ 1528, 8, 20 }, // Kyoroku
{ 1532, 7, 29 }, // Tenmon 190
{ 1555, 10, 23 }, // Koji
{ 1558, 2, 28 }, // Eiroku
{ 1570, 4, 23 }, // Genki
{ 1573, 7, 28 }, // Tensho
{ 1592, 12, 8 }, // Bunroku
{ 1596, 10, 27 }, // Keicho
{ 1615, 7, 13 }, // Genwa
{ 1624, 2, 30 }, // Kan-ei
{ 1644, 12, 16 }, // Shoho
{ 1648, 2, 15 }, // Keian 200
{ 1652, 9, 18 }, // Shoo
{ 1655, 4, 13 }, // Meiryaku
{ 1658, 7, 23 }, // Manji
{ 1661, 4, 25 }, // Kanbun
{ 1673, 9, 21 }, // Enpo
{ 1681, 9, 29 }, // Tenwa
{ 1684, 2, 21 }, // Jokyo
{ 1688, 9, 30 }, // Genroku
{ 1704, 3, 13 }, // Hoei
{ 1711, 4, 25 }, // Shotoku 210
{ 1716, 6, 22 }, // Kyoho
{ 1736, 4, 28 }, // Genbun
{ 1741, 2, 27 }, // Kanpo
{ 1744, 2, 21 }, // Enkyo
{ 1748, 7, 12 }, // Kan-en
{ 1751, 10, 27 }, // Horyaku
{ 1764, 6, 2 }, // Meiwa
{ 1772, 11, 16 }, // An-ei
{ 1781, 4, 2 }, // Tenmei
{ 1789, 1, 25 }, // Kansei 220
{ 1801, 2, 5 }, // Kyowa
{ 1804, 2, 11 }, // Bunka
{ 1818, 4, 22 }, // Bunsei
{ 1830, 12, 10 }, // Tenpo
{ 1844, 12, 2 }, // Koka
{ 1848, 2, 28 }, // Kaei
{ 1854, 11, 27 }, // Ansei
{ 1860, 3, 18 }, // Man-en
{ 1861, 2, 19 }, // Bunkyu
{ 1864, 2, 20 }, // Genji 230
{ 1865, 4, 7 }, // Keio 231
{ 1868, 9, 8 }, // Meiji 232
{ 1912, 7, 30 }, // Taisho 233
{ 1926, 12, 25 }, // Showa 234
{ 1989, 1, 8 } // Heisei 235
};
#define kEraCount UPRV_LENGTHOF(kEraInfo)
/**
* The current era, for reference.
*/
static const int32_t kCurrentEra = (kEraCount-1); // int32_t to match the calendar field type
static const int32_t kGregorianEpoch = 1970; // used as the default value of EXTENDED_YEAR
static const char* TENTATIVE_ERA_VAR_NAME = "ICU_ENABLE_TENTATIVE_ERA";
// Initialize global Japanese era data
static void U_CALLCONV initializeEras(UErrorCode &status) {
// Although start date of next Japanese era is planned ahead, a name of
// new era might not be available. This implementation allows tester to
// check a new era without era names by settings below (in priority order).
// By default, such tentative era is disabled.
// 1. Environment variable ICU_ENABLE_TENTATIVE_ERA=true or false
// 2. Windows registry (TBD)
UBool includeTentativeEra = FALSE;
#if U_PLATFORM_HAS_WINUWP_API == 0
char *envVarVal = getenv(TENTATIVE_ERA_VAR_NAME);
if (envVarVal != NULL && uprv_stricmp(envVarVal, "true") == 0) {
includeTentativeEra = TRUE;
}
#endif
gJapaneseEraRules = EraRules::createInstance("japanese", includeTentativeEra, status);
if (U_FAILURE(status)) {
return;
}
gCurrentEra = gJapaneseEraRules->getCurrentEraIndex();
}
static void init(UErrorCode &status) {
umtx_initOnce(gJapaneseEraRulesInitOnce, &initializeEras, status);
ucln_i18n_registerCleanup(UCLN_I18N_JAPANESE_CALENDAR, japanese_calendar_cleanup);
}
/* Some platforms don't like to export constants, like old Palm OS and some z/OS configurations. */
uint32_t JapaneseCalendar::getCurrentEra() {
return kCurrentEra;
return gCurrentEra;
}
JapaneseCalendar::JapaneseCalendar(const Locale& aLocale, UErrorCode& success)
: GregorianCalendar(aLocale, success)
{
init(success);
setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly.
}
@ -306,6 +100,9 @@ JapaneseCalendar::~JapaneseCalendar()
JapaneseCalendar::JapaneseCalendar(const JapaneseCalendar& source)
: GregorianCalendar(source)
{
UErrorCode status = U_ZERO_ERROR;
init(status);
U_ASSERT(U_SUCCESS(status));
}
JapaneseCalendar& JapaneseCalendar::operator= ( const JapaneseCalendar& right)
@ -332,10 +129,14 @@ int32_t JapaneseCalendar::getDefaultMonthInYear(int32_t eyear)
int32_t month = 0;
// Find out if we are at the edge of an era
if(eyear == kEraInfo[era].year) {
int32_t eraStart[3] = { 0,0,0 };
UErrorCode status = U_ZERO_ERROR;
gJapaneseEraRules->getStartDate(era, eraStart, status);
U_ASSERT(U_SUCCESS(status));
if(eyear == eraStart[0]) {
// Yes, we're in the first year of this era.
return kEraInfo[era].month-1;
return eraStart[1] // month
-1; // return 0-based month
}
return month;
@ -346,9 +147,13 @@ int32_t JapaneseCalendar::getDefaultDayInMonth(int32_t eyear, int32_t month)
int32_t era = internalGetEra();
int32_t day = 1;
if(eyear == kEraInfo[era].year) {
if(month == (kEraInfo[era].month-1)) {
return kEraInfo[era].day;
int32_t eraStart[3] = { 0,0,0 };
UErrorCode status = U_ZERO_ERROR;
gJapaneseEraRules->getStartDate(era, eraStart, status);
U_ASSERT(U_SUCCESS(status));
if(eyear == eraStart[0]) {
if(month == eraStart[1] - 1) {
return eraStart[2];
}
}
@ -358,7 +163,7 @@ int32_t JapaneseCalendar::getDefaultDayInMonth(int32_t eyear, int32_t month)
int32_t JapaneseCalendar::internalGetEra() const
{
return internalGet(UCAL_ERA, kCurrentEra);
return internalGet(UCAL_ERA, gCurrentEra);
}
int32_t JapaneseCalendar::handleGetExtendedYear()
@ -369,12 +174,18 @@ int32_t JapaneseCalendar::handleGetExtendedYear()
if (newerField(UCAL_EXTENDED_YEAR, UCAL_YEAR) == UCAL_EXTENDED_YEAR &&
newerField(UCAL_EXTENDED_YEAR, UCAL_ERA) == UCAL_EXTENDED_YEAR) {
year = internalGet(UCAL_EXTENDED_YEAR, kGregorianEpoch);
} else {
// Subtract one because year starts at 1
year = internalGet(UCAL_YEAR) + kEraInfo[internalGetEra()].year - 1;
}
return year;
year = internalGet(UCAL_EXTENDED_YEAR, kGregorianEpoch);
} else {
UErrorCode status = U_ZERO_ERROR;
int32_t eraStartYear = gJapaneseEraRules->getStartYear(internalGet(UCAL_ERA, gCurrentEra), status);
U_ASSERT(U_SUCCESS(status));
// extended year is a gregorian year, where 1 = 1AD, 0 = 1BC, -1 = 2BC, etc
year = internalGet(UCAL_YEAR, 1) // pin to minimum of year 1 (first year)
+ eraStartYear // add gregorian starting year
- 1; // Subtract one because year starts at 1
}
return year;
}
@ -383,79 +194,10 @@ void JapaneseCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status
//Calendar::timeToFields(theTime, quick, status);
GregorianCalendar::handleComputeFields(julianDay, status);
int32_t year = internalGet(UCAL_EXTENDED_YEAR); // Gregorian year
int32_t eraIdx = gJapaneseEraRules->getEraIndex(year, internalGet(UCAL_MONTH) + 1, internalGet(UCAL_DAY_OF_MONTH), status);
int32_t low = 0;
// Short circuit for recent years. Most modern computations will
// occur in the current era and won't require the binary search.
// Note that if the year is == the current era year, then we use
// the binary search to handle the month/dom comparison.
#ifdef U_DEBUG_JCAL
fprintf(stderr, "== %d \n", year);
#endif
if (year > kEraInfo[kCurrentEra].year) {
low = kCurrentEra;
#ifdef U_DEBUG_JCAL
fprintf(stderr, " low=%d (special)\n", low);
#endif
} else {
// Binary search
int32_t high = kEraCount;
#ifdef U_DEBUG_JCAL
fprintf(stderr, " high=%d\n", high);
#endif
while (low < high - 1) {
int32_t i = (low + high) / 2;
int32_t diff = year - kEraInfo[i].year;
#ifdef U_DEBUG_JCAL
fprintf(stderr, " d=%d low=%d, high=%d. Considering %d:M%d D%d Y%d. { we are ?:M%d D%d Y%d }\n",
diff,low, high, i, kEraInfo[i].month-1, kEraInfo[i].day, kEraInfo[i].year, internalGet(UCAL_MONTH), internalGet(UCAL_DATE),year);
#endif
// If years are the same, then compare the months, and if those
// are the same, compare days of month. In the ERAS array
// months are 1-based for easier maintenance.
if (diff == 0) {
diff = internalGet(UCAL_MONTH) - (kEraInfo[i].month - 1);
#ifdef U_DEBUG_JCAL
fprintf(stderr, "diff now %d (M) = %d - %d - 1\n", diff, internalGet(UCAL_MONTH), kEraInfo[i].month);
#endif
if (diff == 0) {
diff = internalGet(UCAL_DATE) - kEraInfo[i].day;
#ifdef U_DEBUG_JCAL
fprintf(stderr, "diff now %d (D)\n", diff);
#endif
}
}
if (diff >= 0) {
low = i;
} else {
high = i;
}
#ifdef U_DEBUG_JCAL
fprintf(stderr, ". low=%d, high=%d, i=%d, diff=%d.. %d\n", low, high, i, diff, year);
#endif
}
}
#ifdef U_DEBUG_JCAL
fprintf(stderr, " low[era]=%d,.. %d\n", low, year);
#endif
// Now we've found the last era that starts before this date, so
// adjust the year to count from the start of that era. Note that
// all dates before the first era will fall into the first era by
// the algorithm.
internalSet(UCAL_ERA, low);
internalSet(UCAL_YEAR, year - kEraInfo[low].year + 1);
#ifdef U_DEBUG_JCAL
fprintf(stderr, " Set ERA=%d, year=%d\n", low, year-kEraInfo[low].year+1);
#endif
internalSet(UCAL_ERA, eraIdx);
internalSet(UCAL_YEAR, year - gJapaneseEraRules->getStartYear(eraIdx, status) + 1);
}
/*
@ -483,7 +225,7 @@ int32_t JapaneseCalendar::handleGetLimit(UCalendarDateFields field, ELimitType l
if (limitType == UCAL_LIMIT_MINIMUM || limitType == UCAL_LIMIT_GREATEST_MINIMUM) {
return 0;
}
return kCurrentEra;
return gCurrentEra;
case UCAL_YEAR:
{
switch (limitType) {
@ -494,7 +236,12 @@ int32_t JapaneseCalendar::handleGetLimit(UCalendarDateFields field, ELimitType l
return 1;
case UCAL_LIMIT_COUNT: //added to avoid warning
case UCAL_LIMIT_MAXIMUM:
return GregorianCalendar::handleGetLimit(UCAL_YEAR, UCAL_LIMIT_MAXIMUM) - kEraInfo[kCurrentEra].year;
{
UErrorCode status = U_ZERO_ERROR;
int32_t eraStartYear = gJapaneseEraRules->getStartYear(gCurrentEra, status);
U_ASSERT(U_SUCCESS(status));
return GregorianCalendar::handleGetLimit(UCAL_YEAR, UCAL_LIMIT_MAXIMUM) - eraStartYear;
}
default:
return 1; // Error condition, invalid limitType
}
@ -510,15 +257,18 @@ int32_t JapaneseCalendar::getActualMaximum(UCalendarDateFields field, UErrorCode
if (U_FAILURE(status)) {
return 0; // error case... any value
}
if (era == kCurrentEra) {
if (era == gCurrentEra) {
// TODO: Investigate what value should be used here - revisit after 4.0.
return handleGetLimit(UCAL_YEAR, UCAL_LIMIT_MAXIMUM);
} else {
int32_t nextEraYear = kEraInfo[era + 1].year;
int32_t nextEraMonth = kEraInfo[era + 1].month;
int32_t nextEraDate = kEraInfo[era + 1].day;
int32_t nextEraStart[3] = { 0,0,0 };
gJapaneseEraRules->getStartDate(era + 1, nextEraStart, status);
int32_t nextEraYear = nextEraStart[0];
int32_t nextEraMonth = nextEraStart[1]; // 1-base
int32_t nextEraDate = nextEraStart[2];
int32_t maxYear = nextEraYear - kEraInfo[era].year + 1; // 1-base
int32_t eraStartYear = gJapaneseEraRules->getStartYear(era, status);
int32_t maxYear = nextEraYear - eraStartYear + 1; // 1-base
if (nextEraMonth == 1 && nextEraDate == 1) {
// Subtract 1, because the next era starts at Jan 1
maxYear--;

View file

@ -49,10 +49,18 @@ U_NAMESPACE_BEGIN
* July 30, 1912 (Taisho), December 25, 1926 (Showa), and January 7, 1989 (Heisei). Constants
* for these eras, suitable for use in the <code>UCAL_ERA</code> field, are provided
* in this class. Note that the <em>number</em> used for each era is more or
* less arbitrary. Currently, the era starting in 1053 AD is era #0; however this
* may change in the future as we add more historical data. Use the predefined
* constants rather than using actual, absolute numbers.
* less arbitrary. Currently, the era starting in 645 AD is era #0; however this
* may change in the future. Use the predefined constants rather than using actual,
* absolute numbers.
* <p>
* Since ICU4C 63, start date of each era is imported from CLDR. CLDR era data
* may contain tentative era in near future with placeholder names. By default,
* such era data is not enabled. ICU4C users who want to test the behavior of
* the future era can enable this one of following settings (in the priority
* order):
* <ol>
* <li>Environment variable <code>ICU_ENABLE_TENTATIVE_ERA=true</code>.</li>
* </nl>
* @internal
*/
class JapaneseCalendar : public GregorianCalendar {

View file

@ -32,6 +32,7 @@ typedef enum ECleanupI18NType {
UCLN_I18N_SPOOFDATA,
UCLN_I18N_TRANSLITERATOR,
UCLN_I18N_REGEX,
UCLN_I18N_JAPANESE_CALENDAR,
UCLN_I18N_ISLAMIC_CALENDAR,
UCLN_I18N_CHINESE_CALENDAR,
UCLN_I18N_HEBREW_CALENDAR,

View file

@ -167,6 +167,19 @@ class BasicTimeZone;
* to ~5,800,000 CE. Programmers should use the protected constants in `Calendar` to
* specify an extremely early or extremely late date.
*
* <p>
* The Japanese calendar uses a combination of era name and year number.
* When an emperor of Japan abdicates and a new emperor ascends the throne,
* a new era is declared and year number is reset to 1. Even if the date of
* abdication is scheduled ahead of time, the new era name might not be
* announced until just before the date. In such case, ICU4C may include
* a start date of future era without actual era name, but not enabled
* by default. ICU4C users who want to test the behavior of the future era
* can enable the tentative era by:
* <ul>
* <li>Environment variable <code>ICU_ENABLE_TENTATIVE_ERA=true</code>.</li>
* </ul>
*
* @stable ICU 2.0
*/
class U_I18N_API Calendar : public UObject {

View file

@ -139,6 +139,19 @@
* For example, subtracting 5 days from the date <code>September 12, 1996</code>
* results in <code>September 7, 1996</code>.
*
* <p>
* The Japanese calendar uses a combination of era name and year number.
* When an emperor of Japan abdicates and a new emperor ascends the throne,
* a new era is declared and year number is reset to 1. Even if the date of
* abdication is scheduled ahead of time, the new era name might not be
* announced until just before the date. In such case, ICU4C may include
* a start date of future era without actual era name, but not enabled
* by default. ICU4C users who want to test the behavior of the future era
* can enable the tentative era by:
* <ul>
* <li>Environment variable <code>ICU_ENABLE_TENTATIVE_ERA=true</code>.</li>
* </ul>
*
* @stable ICU 2.0
*/

View file

@ -66,7 +66,7 @@ numbertest_affixutils.o numbertest_api.o numbertest_decimalquantity.o \
numbertest_modifiers.o numbertest_patternmodifier.o numbertest_patternstring.o \
numbertest_stringbuilder.o numbertest_stringsegment.o \
numbertest_parse.o numbertest_doubleconversion.o numbertest_skeletons.o \
static_unisets_test.o numfmtdatadriventest.o
static_unisets_test.o numfmtdatadriventest.o erarulestest.o
DEPS = $(OBJECTS:.o=.d)

View file

@ -0,0 +1,130 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#include "unicode/calendar.h"
#include "unicode/localpointer.h"
#include "unicode/unistr.h"
#include "erarules.h"
#include "erarulestest.h"
void EraRulesTest::runIndexedTest(int32_t index, UBool exec, const char* &name, char* /*par*/)
{
if (exec) {
logln("TestSuite EraRulesTest");
}
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testAPIs);
TESTCASE_AUTO(testJapanese);
TESTCASE_AUTO_END;
}
void EraRulesTest::testAPIs() {
const char * calTypes[] = {
"gregorian",
//"iso8601",
"buddhist",
"chinese",
"coptic",
"dangi",
"ethiopic",
"ethiopic-amete-alem",
"hebrew",
"indian",
"islamic",
"islamic-civil",
"islamic-rgsa",
"islamic-tbla",
"islamic-umalqura",
"japanese",
"persian",
"roc",
//"unknown",
NULL
};
for (int32_t i = 0; calTypes[i] != NULL; i++) {
UErrorCode status = U_ZERO_ERROR;
const char *calId = calTypes[i];
LocalPointer<EraRules> rules1(EraRules::createInstance(calId, FALSE, status));
if (U_FAILURE(status)) {
errln(UnicodeString("Era rules for ") + calId + " is not available.");
continue;
}
LocalPointer<EraRules> rules2(EraRules::createInstance(calId, TRUE, status));
if (U_FAILURE(status)) {
errln(UnicodeString("Era rules for ") + calId + " (including tentative eras) is not available.");
continue;
}
int32_t numEras1 = rules1->getNumberOfEras();
if (numEras1 <= 0) {
errln(UnicodeString("Number of era rules for ") + calId + " is " + numEras1);
}
int32_t numEras2 = rules2->getNumberOfEras();
if (numEras2 < numEras1) {
errln(UnicodeString("Number of era including tentative eras is fewer than one without tentative eras in calendar: ")
+ calId);
}
LocalPointer<Calendar> cal(Calendar::createInstance("en", status));
if (U_FAILURE(status)) {
errln("Failed to create a Calendar instance.");
continue;
}
int32_t currentIdx = rules1->getCurrentEraIndex();
int32_t currentYear = cal->get(UCAL_YEAR, status);
int32_t idx = rules1->getEraIndex(
currentYear, cal->get(UCAL_MONTH, status) + 1,
cal->get(UCAL_DATE, status), status);
if (U_FAILURE(status)) {
errln("Error while getting index of era.");
continue;
}
if (idx != currentIdx) {
errln(UnicodeString("Current era index:") + currentIdx + " is different from era index of now:" + idx
+ " in calendar:" + calId);
}
int32_t eraStartYear = rules1->getStartYear(currentIdx, status);
if (U_FAILURE(status)) {
errln(UnicodeString("Failed to get the start year of era index: ") + currentIdx + " in calendar: " + calId);
}
if (currentYear < eraStartYear) {
errln(UnicodeString("Current era's start year is after the current year in calendar:") + calId);
}
}
}
void EraRulesTest::testJapanese() {
const int32_t HEISEI = 235; // ICU4C does not define constants for eras
UErrorCode status = U_ZERO_ERROR;
LocalPointer<EraRules> rules(EraRules::createInstance("japanese", TRUE, status));
if (U_FAILURE(status)) {
errln("Failed to get era rules for Japanese calendar.");
return;
}
// Rules should have an era after Heisei
int32_t numRules = rules->getNumberOfEras();
if (numRules <= HEISEI) {
errln("Era after Heisei is not available.");
return;
}
int postHeiseiStartYear = rules->getStartYear(HEISEI + 1, status);
if (U_FAILURE(status)) {
errln("Failed to get the start year of era after Heisei.");
}
if (postHeiseiStartYear != 2019) {
errln(UnicodeString("Era after Heisei should start in 2019, but got ") + postHeiseiStartYear);
}
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -0,0 +1,23 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#ifndef ERARULESTEST_H_
#define ERARULESTEST_H_
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#include "intltest.h"
class EraRulesTest : public IntlTest {
public:
void runIndexedTest(int32_t index, UBool exec, const char* &name, char* par = NULL);
private:
void testAPIs();
void testJapanese();
};
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* ERARULESTEST_H_ */

View file

@ -2,7 +2,6 @@
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- The following import will include the 'default' configuration options for VS projects. -->
<Import Project="..\..\allinone\Build.Windows.ProjectConfiguration.props" />
<PropertyGroup Label="Globals">
<ProjectGuid>{73632960-B3A6-464D-83A3-4B43365F19B8}</ProjectGuid>
<RootNamespace>intltest</RootNamespace>
@ -160,6 +159,7 @@
<ClCompile Include="colldata.cpp">
<DisableLanguageExtensions>false</DisableLanguageExtensions>
</ClCompile>
<ClCompile Include="erarulestest.cpp" />
<ClCompile Include="numfmtspectest.cpp" />
<ClCompile Include="regiontst.cpp" />
<ClCompile Include="ucharstrietest.cpp" />
@ -365,6 +365,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="colldata.h" />
<ClInclude Include="erarulestest.h" />
<ClInclude Include="itrbbi.h" />
<ClInclude Include="rbbiapts.h" />
<ClInclude Include="rbbitst.h" />

View file

@ -72,7 +72,7 @@
</ClCompile>
<ClCompile Include="rbbimonkeytest.cpp">
<Filter>break iteration</Filter>
</ClCompile>
</ClCompile>
<ClCompile Include="itspoof.cpp">
<Filter>spoof detection</Filter>
</ClCompile>
@ -532,6 +532,9 @@
<ClCompile Include="ucharstrietest.cpp">
<Filter>data &amp; memory</Filter>
</ClCompile>
<ClCompile Include="erarulestest.cpp">
<Filter>formatting</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="itrbbi.h">
@ -915,5 +918,8 @@
<ClInclude Include="listformattertest.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="erarulestest.h">
<Filter>formatting</Filter>
</ClInclude>
</ItemGroup>
</Project>
</Project>

View file

@ -60,6 +60,7 @@
#include "listformattertest.h" // ListFormatterTest
#include "regiontst.h" // RegionTest
#include "numbertest.h" // NumberTest
#include "erarulestest.h" // EraRulesTest
extern IntlTest *createCompactDecimalFormatTest();
extern IntlTest *createGenderInfoTest();
@ -215,6 +216,7 @@ void IntlTestFormat::runIndexedTest( int32_t index, UBool exec, const char* &nam
break;
TESTCLASS(50,NumberFormatDataDrivenTest);
TESTCLASS(51,NumberTest);
TESTCLASS(52,EraRulesTest);
default: name = ""; break; //needed to end loop
}
if (exec) {

View file

@ -25805,6 +25805,7 @@ structLocale:table(nofallback){
"",
"",
"",
"",
}
wide{
"",
@ -26043,6 +26044,7 @@ structLocale:table(nofallback){
"",
"",
"",
"",
}
narrow{
"",
@ -26281,6 +26283,7 @@ structLocale:table(nofallback){
"",
"",
"",
"",
}
}
intervalFormats{

View file

@ -0,0 +1,42 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl;
/**
* Calendar type enum, moved from com.ibm.icu.util.Calendar.
*
* @author Yoshito Umaoka
*/
public enum CalType {
GREGORIAN("gregorian"),
ISO8601("iso8601"),
BUDDHIST("buddhist"),
CHINESE("chinese"),
COPTIC("coptic"),
DANGI("dangi"),
ETHIOPIC("ethiopic"),
ETHIOPIC_AMETE_ALEM("ethiopic-amete-alem"),
HEBREW("hebrew"),
INDIAN("indian"),
ISLAMIC("islamic"),
ISLAMIC_CIVIL("islamic-civil"),
ISLAMIC_RGSA("islamic-rgsa"),
ISLAMIC_TBLA("islamic-tbla"),
ISLAMIC_UMALQURA("islamic-umalqura"),
JAPANESE("japanese"),
PERSIAN("persian"),
ROC("roc"),
UNKNOWN("unknown");
String id;
CalType(String id) {
this.id = id;
}
public String getId() {
return id;
}
}

View file

@ -0,0 +1,309 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl;
import java.util.Arrays;
import com.ibm.icu.util.ICUException;
import com.ibm.icu.util.UResourceBundle;
import com.ibm.icu.util.UResourceBundleIterator;
/**
* <code>EraRules</code> represents calendar era rules specified
* in supplementalData/calendarData.
*
* @author Yoshito Umaoka
*/
public class EraRules {
private static final int MAX_ENCODED_START_YEAR = 32767;
private static final int MIN_ENCODED_START_YEAR = -32768;
public static final int MIN_ENCODED_START = encodeDate(MIN_ENCODED_START_YEAR, 1, 1);
private static final int YEAR_MASK = 0xFFFF0000;
private static final int MONTH_MASK = 0x0000FF00;
private static final int DAY_MASK = 0x000000FF;
private int[] startDates;
private int numEras;
private int currentEra;
private EraRules(int[] startDates, int numEras) {
this.startDates = startDates;
this.numEras = numEras;
initCurrentEra();
}
public static EraRules getInstance(CalType calType, boolean includeTentativeEra) {
UResourceBundle supplementalDataRes = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME,
"supplementalData", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
UResourceBundle calendarDataRes = supplementalDataRes.get("calendarData");
UResourceBundle calendarTypeRes = calendarDataRes.get(calType.getId());
UResourceBundle erasRes = calendarTypeRes.get("eras");
int numEras = erasRes.getSize();
int firstTentativeIdx = Integer.MAX_VALUE; // first tentative era index
int[] startDates = new int[numEras];
UResourceBundleIterator itr = erasRes.getIterator();
while (itr.hasNext()) {
UResourceBundle eraRuleRes = itr.next();
String eraIdxStr = eraRuleRes.getKey();
int eraIdx = -1;
try {
eraIdx = Integer.parseInt(eraIdxStr);
} catch (NumberFormatException e) {
throw new ICUException("Invald era rule key:" + eraIdxStr + " in era rule data for " + calType.getId());
}
if (eraIdx < 0 || eraIdx >= numEras) {
throw new ICUException("Era rule key:" + eraIdxStr + " in era rule data for " + calType.getId()
+ " must be in range [0, " + (numEras - 1) + "]");
}
if (isSet(startDates[eraIdx])) {
throw new ICUException(
"Dupulicated era rule for rule key:" + eraIdxStr + " in era rule data for " + calType.getId());
}
boolean hasName = true;
boolean hasEnd = false;
UResourceBundleIterator ruleItr = eraRuleRes.getIterator();
while (ruleItr.hasNext()) {
UResourceBundle res = ruleItr.next();
String key = res.getKey();
if (key.equals("start")) {
int[] fields = res.getIntVector();
if (fields.length != 3 || !isValidRuleStartDate(fields[0], fields[1], fields[2])) {
throw new ICUException(
"Invalid era rule date data:" + Arrays.toString(fields) + " in era rule data for "
+ calType.getId());
}
startDates[eraIdx] = encodeDate(fields[0], fields[1], fields[2]);
} else if (key.equals("named")) {
String val = res.getString();
if (val.equals("false")) {
hasName = false;
}
} else if (key.equals("end")) {
hasEnd = true;
}
}
if (isSet(startDates[eraIdx])) {
if (hasEnd) {
// This implementation assumes either start or end is available, not both.
// For now, just ignore the end rule.
}
} else {
if (hasEnd) {
if (eraIdx != 0) {
// This implementation does not support end only rule for eras other than
// the first one.
throw new ICUException(
"Era data for " + eraIdxStr + " in era rule data for " + calType.getId()
+ " has only end rule.");
}
startDates[eraIdx] = MIN_ENCODED_START;
} else {
throw new ICUException("Missing era start/end rule date for key:" + eraIdxStr + " in era rule data for "
+ calType.getId());
}
}
if (hasName) {
if (eraIdx >= firstTentativeIdx) {
throw new ICUException(
"Non-tentative era(" + eraIdx + ") must be placed before the first tentative era");
}
} else {
if (eraIdx < firstTentativeIdx) {
firstTentativeIdx = eraIdx;
}
}
}
if (firstTentativeIdx < Integer.MAX_VALUE && !includeTentativeEra) {
return new EraRules(startDates, firstTentativeIdx);
}
return new EraRules(startDates, numEras);
}
/**
* Gets number of effective eras
* @return number of effective eras
*/
public int getNumberOfEras() {
return numEras;
}
/**
* Gets start date of an era
* @param eraIdx Era index
* @param fillIn Receives date fields if supplied. If null, or size of array
* is less than 3, then a new int[] will be newly allocated.
* @return An int array including values of year, month, day of month in this order.
* When an era has no start date, the result will be January 1st in year
* whose value is minimum integer.
*/
public int[] getStartDate(int eraIdx, int[] fillIn) {
if (eraIdx < 0 || eraIdx >= numEras) {
throw new IllegalArgumentException("eraIdx is out of range");
}
return decodeDate(startDates[eraIdx], fillIn);
}
/**
* Gets start year of an era
* @param eraIdx Era index
* @return The first year of an era. When a era has no start date, minimum integer
* value is returned.
*/
public int getStartYear(int eraIdx) {
if (eraIdx < 0 || eraIdx >= numEras) {
throw new IllegalArgumentException("eraIdx is out of range");
}
int[] fields = decodeDate(startDates[eraIdx], null);
return fields[0];
}
/**
* Returns era index for the specified year/month/day.
* @param year Year
* @param month Month (1-base)
* @param day Day of month
* @return era index (or 0, when the specified date is before the first era)
*/
public int getEraIndex(int year, int month, int day) {
if (month < 1 || month > 12 || day < 1 || day > 31) {
throw new IllegalArgumentException("Illegal date - year:" + year + "month:" + month + "day:" + day);
}
int high = numEras; // last index + 1
int low;
// Short circuit for recent years. Most modern computations will
// occur in the last few eras.
if (compareEncodedDateWithYMD(startDates[getCurrentEraIndex()], year, month, day) <= 0) {
low = getCurrentEraIndex();
} else {
low = 0;
}
// Do binary search
while (low < high - 1) {
int i = (low + high) / 2;
if (compareEncodedDateWithYMD(startDates[i], year, month, day) <= 0) {
low = i;
} else {
high = i;
}
}
return low;
}
/**
* Gets the current era index. This is calculated only once for an instance of
* EraRules.
*
* @return era index of current era (or 0, when current date is before the first era)
*/
public int getCurrentEraIndex() {
return currentEra;
}
private void initCurrentEra() {
int[] fields = Grego.timeToFields(System.currentTimeMillis(), null);
int currentEncodedDate = encodeDate(fields[0], fields[1] + 1 /* changes to 1-base */, fields[2]);
int eraIdx = numEras - 1;
while (eraIdx > 0) {
if (currentEncodedDate >= startDates[eraIdx]) {
break;
}
eraIdx--;
}
// Note: current era could be before the first era.
// In this case, this implementation returns the first era index (0).
currentEra = eraIdx;
}
//
// private methods
//
private static boolean isSet(int startDate) {
return startDate != 0;
}
private static boolean isValidRuleStartDate(int year, int month, int day) {
return year >= MIN_ENCODED_START_YEAR && year <= MAX_ENCODED_START_YEAR
&& month >= 1 && month <= 12 && day >= 1 && day <= 31;
}
/**
* Encode year/month/date to a single integer.
* year is high 16 bits (-32768 to 32767), month is
* next 8 bits and day of month is last 8 bits.
*
* @param year year
* @param month month (1-base)
* @param day day of month
* @return an encoded date.
*/
private static int encodeDate(int year, int month, int day) {
return year << 16 | month << 8 | day;
}
private static int[] decodeDate(int encodedDate, int[] fillIn) {
int year, month, day;
if (encodedDate == MIN_ENCODED_START) {
year = Integer.MIN_VALUE;
month = 1;
day = 1;
} else {
year = (encodedDate & YEAR_MASK) >> 16;
month = (encodedDate & MONTH_MASK) >> 8;
day = encodedDate & DAY_MASK;
}
if (fillIn != null && fillIn.length >= 3) {
fillIn[0] = year;
fillIn[1] = month;
fillIn[2] = day;
return fillIn;
}
int[] result = {year, month, day};
return result;
}
/**
* Compare an encoded date with another date specified by year/month/day.
* @param encoded An encoded date
* @param year Year of another date
* @param month Month of another date
* @param day Day of another date
* @return -1 when encoded date is earlier, 0 when two dates are same,
* and 1 when encoded date is later.
*/
private static int compareEncodedDateWithYMD(int encoded, int year, int month, int day) {
if (year < MIN_ENCODED_START_YEAR) {
if (encoded == MIN_ENCODED_START) {
if (year > Integer.MIN_VALUE || month > 1 || day > 1) {
return -1;
}
return 0;
} else {
return 1;
}
} else if (year > MAX_ENCODED_START_YEAR) {
return -1;
} else {
int tmp = encodeDate(year, month, day);
if (encoded < tmp) {
return -1;
} else if (encoded == tmp) {
return 0;
} else {
return 1;
}
}
}
}

View file

@ -17,6 +17,7 @@ import java.util.Date;
import java.util.Locale;
import java.util.MissingResourceException;
import com.ibm.icu.impl.CalType;
import com.ibm.icu.impl.CalendarUtil;
import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.ICUData;
@ -1780,42 +1781,12 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
return region;
}
private enum CalType {
GREGORIAN("gregorian"),
ISO8601("iso8601"),
BUDDHIST("buddhist"),
CHINESE("chinese"),
COPTIC("coptic"),
DANGI("dangi"),
ETHIOPIC("ethiopic"),
ETHIOPIC_AMETE_ALEM("ethiopic-amete-alem"),
HEBREW("hebrew"),
INDIAN("indian"),
ISLAMIC("islamic"),
ISLAMIC_CIVIL("islamic-civil"),
ISLAMIC_RGSA("islamic-rgsa"),
ISLAMIC_TBLA("islamic-tbla"),
ISLAMIC_UMALQURA("islamic-umalqura"),
JAPANESE("japanese"),
PERSIAN("persian"),
ROC("roc"),
UNKNOWN("unknown");
String id;
CalType(String id) {
this.id = id;
}
}
private static CalType getCalendarTypeForLocale(ULocale l) {
String s = CalendarUtil.getCalendarType(l);
if (s != null) {
s = s.toLowerCase(Locale.ENGLISH);
for (CalType type : CalType.values()) {
if (s.equals(type.id)) {
if (s.equals(type.getId())) {
return type;
}
}
@ -1938,7 +1909,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
String prefRegion = ULocale.getRegionForSupplementalData(locale, true);
// Read preferred calendar values from supplementalData calendarPreferences
ArrayList<String> values = new ArrayList<String>();
ArrayList<String> values = new ArrayList<>();
UResourceBundle rb = UResourceBundle.getBundleInstance(
ICUData.ICU_BASE_NAME,
@ -1965,8 +1936,8 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
}
// then, add other available clanedars
for (CalType t : CalType.values()) {
if (!values.contains(t.id)) {
values.add(t.id);
if (!values.contains(t.getId())) {
values.add(t.getId());
}
}
return values.toArray(new String[values.size()]);
@ -2191,7 +2162,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
CalType type = CalType.GREGORIAN;
String typeString = getType();
for (CalType testType : CalType.values()) {
if (typeString.equals(testType.id)) {
if (typeString.equals(testType.getId())) {
type = testType;
break;
}
@ -2265,7 +2236,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
CalType type = CalType.GREGORIAN;
String typeString = getType();
for (CalType testType : CalType.values()) {
if (typeString.equals(testType.id)) {
if (typeString.equals(testType.getId())) {
type = testType;
break;
}
@ -3526,7 +3497,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
// date format pattern cache
private static final ICUCache<String, PatternData> PATTERN_CACHE =
new SimpleCache<String, PatternData>();
new SimpleCache<>();
// final fallback patterns
private static final String[] DEFAULT_PATTERNS = {
"HH:mm:ss z",

View file

@ -10,6 +10,9 @@ package com.ibm.icu.util;
import java.util.Date;
import java.util.Locale;
import com.ibm.icu.impl.CalType;
import com.ibm.icu.impl.EraRules;
/**
* <code>JapaneseCalendar</code> is a subclass of <code>GregorianCalendar</code>
* that numbers years and eras based on the reigns of the Japanese emperors.
@ -24,19 +27,34 @@ import java.util.Locale;
* of that year were in the Showa era, e.g. "January 6, 64 Showa", while the rest
* of the year was in the Heisei era, e.g. "January 7, 1 Heisei". This class
* handles this distinction correctly when computing dates. However, in lenient
* mode either form of date is acceptable as input.
* mode either form of date is acceptable as input.
* <p>
* In modern times, eras have started on January 8, 1868 AD, Gregorian (Meiji),
* July 30, 1912 (Taisho), December 25, 1926 (Showa), and January 7, 1989 (Heisei). Constants
* for these eras, suitable for use in the <code>ERA</code> field, are provided
* in this class. Note that the <em>number</em> used for each era is more or
* less arbitrary. Currently, the era starting in 1053 AD is era #0; however this
* may change in the future as we add more historical data. Use the predefined
* constants rather than using actual, absolute numbers.
* less arbitrary. Currently, the era starting in 645 AD is era #0; however this
* may change in the future. Use the predefined constants rather than using actual,
* absolute numbers.
* <p>
* Since ICU4J 63, start date of each era is imported from CLDR. CLDR era data
* may contain tentative era in near future with placeholder names. By default,
* such era data is not enabled. ICU4J users who want to test the behavior of
* the future era can enable this by one of following settings (in the priority
* order):
* <ol>
* <li>Java system property <code>ICU_ENABLE_TENTATIVE_ERA=true</code>.</li>
* <li>Environment variable <code>ICU_ENABLE_TENTATIVE_ERA=true</code>.</li>
* <li>Java system property <code>jdk.calendar.japanese.supplemental.era=xxx</code>.
* (Note: This configuration is used for specifying a new era's start date and
* names in OpenJDK. ICU4J implementation enables the CLDR tentative era when
* this property is defined, but it does not use the start date and names specified
* by the property value.)</li>
* </nl>
* <p>
* This class should not be subclassed.</p>
* <p>
* JapaneseCalendar usually should be instantiated using
* JapaneseCalendar usually should be instantiated using
* {@link com.ibm.icu.util.Calendar#getInstance(ULocale)} passing in a <code>ULocale</code>
* with the tag <code>"@calendar=japanese"</code>.</p>
*
@ -210,9 +228,44 @@ public class JapaneseCalendar extends GregorianCalendar {
// Use 1970 as the default value of EXTENDED_YEAR
private static final int GREGORIAN_EPOCH = 1970;
private static final EraRules ERA_RULES;
static {
// Although start date of next Japanese era is planned ahead, a name of
// new era might not be available. This implementation allows tester to
// check a new era without era names by settings below (in priority order).
// By default, such tentative era is disabled.
//
// 1. System property ICU_ENABLE_TENTATIVE_ERA=true or false
// 2. Environment variable ICU_ENABLE_TENTATIVE_ERA=true or false
// 3. Java system property - jdk.calendar.japanese.supplemental.era for Japanese
//
// Note: Java system property specifies the start date of new Japanese era,
// but this implementation always uses the date read from ICU data.
boolean includeTentativeEra = false;
final String VAR_NAME = "ICU_ENABLE_TENTATIVE_ERA";
String eraConf = System.getProperty(VAR_NAME);
if (eraConf == null) {
eraConf = System.getenv(VAR_NAME);
}
if (eraConf != null) {
includeTentativeEra = eraConf.equalsIgnoreCase("true");
} else {
String jdkEraConf = System.getProperty("jdk.calendar.japanese.supplemental.era");
includeTentativeEra = jdkEraConf != null;
}
ERA_RULES = EraRules.getInstance(CalType.JAPANESE, includeTentativeEra);
}
/**
* @stable ICU 2.8
*/
@Override
protected int handleGetExtendedYear() {
// EXTENDED_YEAR in JapaneseCalendar is a Gregorian year
// The default value of EXTENDED_YEAR is 1970 (Showa 45)
@ -221,14 +274,14 @@ public class JapaneseCalendar extends GregorianCalendar {
newerField(EXTENDED_YEAR, ERA) == EXTENDED_YEAR) {
year = internalGet(EXTENDED_YEAR, GREGORIAN_EPOCH);
} else {
// extended year is a gregorian year, where 1 = 1AD, 0 = 1BC, -1 = 2BC, etc
year = internalGet(YEAR, 1) // pin to minimum of year 1 (first year)
+ ERAS[internalGet(ERA, CURRENT_ERA) * 3] // add gregorian starting year
- 1; // Subtract one because year starts at 1
// extended year is a gregorian year, where 1 = 1AD, 0 = 1BC, -1 = 2BC, etc
year = internalGet(YEAR, 1) // pin to minimum of year 1 (first year)
+ ERA_RULES.getStartYear(internalGet(ERA, CURRENT_ERA)) // add gregorian starting year
- 1; // Subtract one because year starts at 1
}
return year;
}
/**
* Called by handleComputeJulianDay. Returns the default month (0-based) for the year,
* taking year and era into account. Defaults to 0 (JANUARY) for Gregorian.
@ -238,18 +291,19 @@ public class JapaneseCalendar extends GregorianCalendar {
* @draft ICU 3.6 (retain)
* @see #MONTH
*/
protected int getDefaultMonthInYear(int extendedYear)
{
int era = internalGet(ERA, CURRENT_ERA);
//computeFields(status); // No need to compute fields here - expect the caller already did so.
@Override
protected int getDefaultMonthInYear(int extendedYear) {
int era = internalGet(ERA, CURRENT_ERA);
// computeFields(status); // No need to compute fields here - expect the caller already did so.
// Find out if we are at the edge of an era
if(extendedYear == ERAS[era*3]) {
return ERAS[(era*3)+1] // month..
-1; // return 0-based month
} else {
return super.getDefaultMonthInYear(extendedYear);
}
// Find out if we are at the edge of an era
int[] eraStart = ERA_RULES.getStartDate(era, null);
if (extendedYear == eraStart[0]) {
return eraStart[1] // month..
- 1; // return 0-based month
} else {
return super.getDefaultMonthInYear(extendedYear);
}
}
/**
@ -262,308 +316,33 @@ public class JapaneseCalendar extends GregorianCalendar {
* @provisional ICU 3.6
* @see #DAY_OF_MONTH
*/
@Override
protected int getDefaultDayInMonth(int extendedYear, int month) {
int era = internalGet(ERA, CURRENT_ERA);
if(extendedYear == ERAS[era*3]) { // if it is year 1..
if(month == ((ERAS[(era*3)+1])-1)) { // if it is the emperor's first month..
return ERAS[(era*3)+2]; // return the D_O_M of acession
}
}
int era = internalGet(ERA, CURRENT_ERA);
int[] eraStart = ERA_RULES.getStartDate(era, null);
return super.getDefaultDayInMonth(extendedYear, month);
if (extendedYear == eraStart[0]) { // if it is year 1..
if (month == (eraStart[1] - 1)) { // if it is the emperor's first month..
return eraStart[2]; // return the D_O_M of accession
}
}
return super.getDefaultDayInMonth(extendedYear, month);
}
/**
* @stable ICU 2.8
*/
@Override
protected void handleComputeFields(int julianDay) {
super.handleComputeFields(julianDay);
int year = internalGet(EXTENDED_YEAR);
int eraIdx = ERA_RULES.getEraIndex(year, internalGet(MONTH) + 1 /* 1-base */, internalGet(DAY_OF_MONTH));
int low = 0;
// Short circuit for recent years. Most modern computations will
// occur in the current era and won't require the binary search.
// Note that if the year is == the current era year, then we use
// the binary search to handle the month/dom comparison.
if (year > ERAS[ERAS.length - 3]) {
low = CURRENT_ERA;
} else {
// Binary search
int high = ERAS.length / 3;
while (low < high - 1) {
int i = (low + high) / 2;
int diff = year - ERAS[i*3];
// If years are the same, then compare the months, and if those
// are the same, compare days of month. In the ERAS array
// months are 1-based for easier maintenance.
if (diff == 0) {
diff = internalGet(MONTH) - (ERAS[i*3 + 1] - 1);
if (diff == 0) {
diff = internalGet(DAY_OF_MONTH) - ERAS[i*3 + 2];
}
}
if (diff >= 0) {
low = i;
} else {
high = i;
}
}
}
// Now we've found the last era that starts before this date, so
// adjust the year to count from the start of that era. Note that
// all dates before the first era will fall into the first era by
// the algorithm.
internalSet(ERA, low);
internalSet(YEAR, year - ERAS[low*3] + 1);
internalSet(ERA, eraIdx);
internalSet(YEAR, year - ERA_RULES.getStartYear(eraIdx) + 1);
}
private static final int[] ERAS = {
// Gregorian date of each emperor's ascension
// Years are AD, months are 1-based.
// Year Month Day
645, 6, 19, // Taika
650, 2, 15, // Hakuchi
672, 1, 1, // Hakuho
686, 7, 20, // Shucho
701, 3, 21, // Taiho
704, 5, 10, // Keiun
708, 1, 11, // Wado
715, 9, 2, // Reiki
717, 11, 17, // Yoro
724, 2, 4, // Jinki
729, 8, 5, // Tempyo
749, 4, 14, // Tempyo-kampo
749, 7, 2, // Tempyo-shoho
757, 8, 18, // Tempyo-hoji
765, 1, 7, // Tempho-jingo
767, 8, 16, // Jingo-keiun
770, 10, 1, // Hoki
781, 1, 1, // Ten-o
782, 8, 19, // Enryaku
806, 5, 18, // Daido
810, 9, 19, // Konin
824, 1, 5, // Tencho
834, 1, 3, // Showa
848, 6, 13, // Kajo
851, 4, 28, // Ninju
854, 11, 30, // Saiko
857, 2, 21, // Tennan
859, 4, 15, // Jogan
877, 4, 16, // Genkei
885, 2, 21, // Ninna
889, 4, 27, // Kampyo
898, 4, 26, // Shotai
901, 7, 15, // Engi
923, 4, 11, // Encho
931, 4, 26, // Shohei
938, 5, 22, // Tengyo
947, 4, 22, // Tenryaku
957, 10, 27, // Tentoku
961, 2, 16, // Owa
964, 7, 10, // Koho
968, 8, 13, // Anna
970, 3, 25, // Tenroku
973, 12, 20, // Ten-en
976, 7, 13, // Jogen
978, 11, 29, // Tengen
983, 4, 15, // Eikan
985, 4, 27, // Kanna
987, 4, 5, // Ei-en
989, 8, 8, // Eiso
990, 11, 7, // Shoryaku
995, 2, 22, // Chotoku
999, 1, 13, // Choho
1004, 7, 20, // Kanko
1012, 12, 25, // Chowa
1017, 4, 23, // Kannin
1021, 2, 2, // Jian
1024, 7, 13, // Manju
1028, 7, 25, // Chogen
1037, 4, 21, // Choryaku
1040, 11, 10, // Chokyu
1044, 11, 24, // Kantoku
1046, 4, 14, // Eisho
1053, 1, 11, // Tengi
1058, 8, 29, // Kohei
1065, 8, 2, // Jiryaku
1069, 4, 13, // Enkyu
1074, 8, 23, // Shoho
1077, 11, 17, // Shoryaku
1081, 2, 10, // Eiho
1084, 2, 7, // Otoku
1087, 4, 7, // Kanji
1094, 12, 15, // Kaho
1096, 12, 17, // Eicho
1097, 11, 21, // Shotoku
1099, 8, 28, // Kowa
1104, 2, 10, // Choji
1106, 4, 9, // Kasho
1108, 8, 3, // Tennin
1110, 7, 13, // Ten-ei
1113, 7, 13, // Eikyu
1118, 4, 3, // Gen-ei
1120, 4, 10, // Hoan
1124, 4, 3, // Tenji
1126, 1, 22, // Daiji
1131, 1, 29, // Tensho
1132, 8, 11, // Chosho
1135, 4, 27, // Hoen
1141, 7, 10, // Eiji
1142, 4, 28, // Koji
1144, 2, 23, // Tenyo
1145, 7, 22, // Kyuan
1151, 1, 26, // Ninpei
1154, 10, 28, // Kyuju
1156, 4, 27, // Hogen
1159, 4, 20, // Heiji
1160, 1, 10, // Eiryaku
1161, 9, 4, // Oho
1163, 3, 29, // Chokan
1165, 6, 5, // Eiman
1166, 8, 27, // Nin-an
1169, 4, 8, // Kao
1171, 4, 21, // Shoan
1175, 7, 28, // Angen
1177, 8, 4, // Jisho
1181, 7, 14, // Yowa
1182, 5, 27, // Juei
1184, 4, 16, // Genryuku
1185, 8, 14, // Bunji
1190, 4, 11, // Kenkyu
1199, 4, 27, // Shoji
1201, 2, 13, // Kennin
1204, 2, 20, // Genkyu
1206, 4, 27, // Ken-ei
1207, 10, 25, // Shogen
1211, 3, 9, // Kenryaku
1213, 12, 6, // Kenpo
1219, 4, 12, // Shokyu
1222, 4, 13, // Joo
1224, 11, 20, // Gennin
1225, 4, 20, // Karoku
1227, 12, 10, // Antei
1229, 3, 5, // Kanki
1232, 4, 2, // Joei
1233, 4, 15, // Tempuku
1234, 11, 5, // Bunryaku
1235, 9, 19, // Katei
1238, 11, 23, // Ryakunin
1239, 2, 7, // En-o
1240, 7, 16, // Ninji
1243, 2, 26, // Kangen
1247, 2, 28, // Hoji
1249, 3, 18, // Kencho
1256, 10, 5, // Kogen
1257, 3, 14, // Shoka
1259, 3, 26, // Shogen
1260, 4, 13, // Bun-o
1261, 2, 20, // Kocho
1264, 2, 28, // Bun-ei
1275, 4, 25, // Kenji
1278, 2, 29, // Koan
1288, 4, 28, // Shoo
1293, 8, 55, // Einin
1299, 4, 25, // Shoan
1302, 11, 21, // Kengen
1303, 8, 5, // Kagen
1306, 12, 14, // Tokuji
1308, 10, 9, // Enkei
1311, 4, 28, // Ocho
1312, 3, 20, // Showa
1317, 2, 3, // Bunpo
1319, 4, 28, // Geno
1321, 2, 23, // Genkyo
1324, 12, 9, // Shochu
1326, 4, 26, // Kareki
1329, 8, 29, // Gentoku
1331, 8, 9, // Genko
1334, 1, 29, // Kemmu
1336, 2, 29, // Engen
1340, 4, 28, // Kokoku
1346, 12, 8, // Shohei
1370, 7, 24, // Kentoku
1372, 4, 1, // Bunch\u0169
1375, 5, 27, // Tenju
1379, 3, 22, // Koryaku
1381, 2, 10, // Kowa
1384, 4, 28, // Gench\u0169
1384, 2, 27, // Meitoku
1387, 8, 23, // Kakei
1389, 2, 9, // Koo
1390, 3, 26, // Meitoku
1394, 7, 5, // Oei
1428, 4, 27, // Shocho
1429, 9, 5, // Eikyo
1441, 2, 17, // Kakitsu
1444, 2, 5, // Bun-an
1449, 7, 28, // Hotoku
1452, 7, 25, // Kyotoku
1455, 7, 25, // Kosho
1457, 9, 28, // Choroku
1460, 12, 21, // Kansho
1466, 2, 28, // Bunsho
1467, 3, 3, // Onin
1469, 4, 28, // Bunmei
1487, 7, 29, // Chokyo
1489, 8, 21, // Entoku
1492, 7, 19, // Meio
1501, 2, 29, // Bunki
1504, 2, 30, // Eisho
1521, 8, 23, // Taiei
1528, 8, 20, // Kyoroku
1532, 7, 29, // Tenmon
1555, 10, 23, // Koji
1558, 2, 28, // Eiroku
1570, 4, 23, // Genki
1573, 7, 28, // Tensho
1592, 12, 8, // Bunroku
1596, 10, 27, // Keicho
1615, 7, 13, // Genwa
1624, 2, 30, // Kan-ei
1644, 12, 16, // Shoho
1648, 2, 15, // Keian
1652, 9, 18, // Shoo
1655, 4, 13, // Meiryaku
1658, 7, 23, // Manji
1661, 4, 25, // Kanbun
1673, 9, 21, // Enpo
1681, 9, 29, // Tenwa
1684, 2, 21, // Jokyo
1688, 9, 30, // Genroku
1704, 3, 13, // Hoei
1711, 4, 25, // Shotoku
1716, 6, 22, // Kyoho
1736, 4, 28, // Genbun
1741, 2, 27, // Kanpo
1744, 2, 21, // Enkyo
1748, 7, 12, // Kan-en
1751, 10, 27, // Horyaku
1764, 6, 2, // Meiwa
1772, 11, 16, // An-ei
1781, 4, 2, // Tenmei
1789, 1, 25, // Kansei
1801, 2, 5, // Kyowa
1804, 2, 11, // Bunka
1818, 4, 22, // Bunsei
1830, 12, 10, // Tenpo
1844, 12, 2, // Koka
1848, 2, 28, // Kaei
1854, 11, 27, // Ansei
1860, 3, 18, // Man-en
1861, 2, 19, // Bunkyu
1864, 2, 20, // Genji
1865, 4, 7, // Keio
1868, 9, 8, // Meiji
1912, 7, 30, // Taisho
1926, 12, 25, // Showa
1989, 1, 8, // Heisei
};
//-------------------------------------------------------------------------
// Public constants for some of the recent eras that folks might use...
//-------------------------------------------------------------------------
@ -572,31 +351,31 @@ public class JapaneseCalendar extends GregorianCalendar {
/**
* @stable ICU 2.8
*/
static public final int CURRENT_ERA = (ERAS.length / 3) - 1;
/**
static public final int CURRENT_ERA = ERA_RULES.getCurrentEraIndex();
/**
* Constant for the era starting on Sept. 8, 1868 AD.
* @stable ICU 2.8
* @stable ICU 2.8
*/
static public final int MEIJI = CURRENT_ERA - 3;
static public final int MEIJI = 232;
/**
* Constant for the era starting on July 30, 1912 AD.
* @stable ICU 2.8
/**
* Constant for the era starting on July 30, 1912 AD.
* @stable ICU 2.8
*/
static public final int TAISHO = CURRENT_ERA - 2;
/**
* Constant for the era starting on Dec. 25, 1926 AD.
* @stable ICU 2.8
*/
static public final int SHOWA = CURRENT_ERA - 1;
static public final int TAISHO = 233;
/**
* Constant for the era starting on Jan. 7, 1989 AD.
* @stable ICU 2.8
/**
* Constant for the era starting on Dec. 25, 1926 AD.
* @stable ICU 2.8
*/
static public final int HEISEI = CURRENT_ERA;
static public final int SHOWA = 234;
/**
* Constant for the era starting on Jan. 7, 1989 AD.
* @stable ICU 2.8
*/
static public final int HEISEI = 235;
/**
* Override GregorianCalendar. We should really handle YEAR_WOY and
@ -604,6 +383,7 @@ public class JapaneseCalendar extends GregorianCalendar {
* not critical.
* @stable ICU 2.8
*/
@Override
@SuppressWarnings("fallthrough")
protected int handleGetLimit(int field, int limitType) {
switch (field) {
@ -621,7 +401,7 @@ public class JapaneseCalendar extends GregorianCalendar {
case LEAST_MAXIMUM:
return 1;
case MAXIMUM:
return super.handleGetLimit(field, MAXIMUM) - ERAS[CURRENT_ERA*3];
return super.handleGetLimit(field, MAXIMUM) - ERA_RULES.getStartYear(CURRENT_ERA);
}
//Fall through to the default if not handled above
}
@ -634,6 +414,7 @@ public class JapaneseCalendar extends GregorianCalendar {
* {@inheritDoc}
* @stable ICU 3.8
*/
@Override
public String getType() {
return "japanese";
}
@ -643,6 +424,7 @@ public class JapaneseCalendar extends GregorianCalendar {
* @internal
* @deprecated This API is ICU internal only.
*/
@Override
@Deprecated
public boolean haveDefaultCentury() {
return false;
@ -652,6 +434,7 @@ public class JapaneseCalendar extends GregorianCalendar {
* {@inheritDoc}
* @stable ICU 4.0
*/
@Override
public int getActualMaximum(int field) {
if (field == YEAR) {
int era = get(Calendar.ERA);
@ -659,11 +442,12 @@ public class JapaneseCalendar extends GregorianCalendar {
// TODO: Investigate what value should be used here - revisit after 4.0.
return handleGetLimit(YEAR, MAXIMUM);
} else {
int nextEraYear = ERAS[(era+1)*3];
int nextEraMonth = ERAS[(era+1)*3 + 1];
int nextEraDate = ERAS[(era+1)*3 + 2];
int[] nextEraStart = ERA_RULES.getStartDate(era + 1, null);
int nextEraYear = nextEraStart[0];
int nextEraMonth = nextEraStart[1]; // 1-base
int nextEraDate = nextEraStart[2];
int maxYear = nextEraYear - ERAS[era*3] + 1; // 1-base
int maxYear = nextEraYear - ERA_RULES.getStartYear(era) + 1; // 1-base
if (nextEraMonth == 1 && nextEraDate == 1) {
// Substract 1, because the next era starts at Jan 1
maxYear--;

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:36d0ec0c543d1dccafcc6985a7c18285b255afb98bc2bdb16a867a22600bfddb
size 12487287
oid sha256:92dc0a5ca71ac54537a6c7c42c2f80ccbd3298d9ebf69c3d732199230d5100c6
size 12487439

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:469f76e391dced8e9ae4a9543513dddd6d4d2026ad6cbc0ab79d9553da803e6a
oid sha256:d2308b3498ce1c2b869b60b5a0f7cea6f08aff2fac046ae260e10688c15c60b2
size 92857

View file

@ -0,0 +1,76 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.dev.test.calendar;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.impl.CalType;
import com.ibm.icu.impl.EraRules;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.JapaneseCalendar;
import com.ibm.icu.util.ULocale;
/**
* Tests for EraRules class
*/
@RunWith(JUnit4.class)
public class EraRulesTest extends TestFmwk {
@Test
public void testAPIs() {
for (CalType calType : CalType.values()) {
String calId = calType.getId();
if (calId.equals("iso8601") || calId.equals("unknown")) {
continue;
}
EraRules rules1 = EraRules.getInstance(calType, false);
if (rules1 == null) {
errln("Era rules for " + calId + " is not available.");
}
EraRules rules2 = EraRules.getInstance(calType, true);
if (rules2 == null) {
errln("Era rules for " + calId + " (including tentative eras) is not available.");
}
int numEras1 = rules1.getNumberOfEras();
if (numEras1 <= 0) {
errln("Number of era rules for " + calId + " is " + numEras1);
}
int numEras2 = rules2.getNumberOfEras();
if (numEras2 < numEras1) {
errln("Number of era including tentative eras is fewer than one without tentative eras in calendar: "
+ calId);
}
Calendar cal = Calendar.getInstance(new ULocale("en"));
int currentIdx = rules1.getCurrentEraIndex();
int currentYear = cal.get(Calendar.YEAR);
int idx = rules1.getEraIndex(currentYear, cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DATE));
if (idx != currentIdx) {
errln("Current era index:" + currentIdx + " is different from era index of now:" + idx
+ " in calendar:" + calId);
}
int eraStartYear = rules1.getStartYear(currentIdx);
if (currentYear < eraStartYear) {
errln("Current era's start year is after the current year in calendar:" + calId);
}
}
}
@Test
public void testJapanese() {
EraRules rules = EraRules.getInstance(CalType.JAPANESE, true);
// Rules should have an era after Heisei
int numRules = rules.getNumberOfEras();
if (numRules <= JapaneseCalendar.HEISEI) {
errln("Era after Heisei is not available.");
}
int postHeiseiStartYear = rules.getStartYear(JapaneseCalendar.HEISEI + 1);
if (postHeiseiStartYear != 2019) {
errln("Era after Heisei should start in 2019, but got " + postHeiseiStartYear);
}
}
}