Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 12 additions & 15 deletions icu4c/source/i18n/coptccal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,8 @@ CopticCalendar::handleGetExtendedYear(UErrorCode& status)
if (newerField(UCAL_EXTENDED_YEAR, UCAL_YEAR) == UCAL_EXTENDED_YEAR) {
return internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1
}
// The year defaults to the epoch start, the era to CE
int32_t era = internalGet(UCAL_ERA, CE);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: I think if someone explicitly set the era to the old backwards-counting era Before Diocletian (BD), then we should still do 1 - X. We should just never create BD dates implicitly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you suggesting we do not change this part?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm suggesting that as long as the Before Diocletian era is public and stable, we should probably continue respecting it on input.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm suggesting that as long as the Before Diocletian era is public and stable, we should probably continue respecting it on input.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none of them are public. They are all private

if (era == BCE) {
return 1 - internalGet(UCAL_YEAR, 1); // Convert to extended year
}
if (era == CE){
return internalGet(UCAL_YEAR, 1); // Default to year 1
}
status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
// Default to year 1
return internalGet(UCAL_YEAR, 1);
}

IMPL_SYSTEM_DEFAULT_CENTURY(CopticCalendar, "@calendar=coptic")
Expand All @@ -86,16 +78,21 @@ CopticCalendar::getJDEpochOffset() const
return COPTIC_JD_EPOCH_OFFSET;
}

int32_t CopticCalendar::extendedYearToEra(int32_t extendedYear) const {
return extendedYear <= 0 ? BCE : CE;
int32_t CopticCalendar::extendedYearToEra(int32_t /* extendedYear */) const {
return CE;
}

int32_t CopticCalendar::extendedYearToYear(int32_t extendedYear) const {
return extendedYear <= 0 ? 1 - extendedYear : extendedYear;
return extendedYear;
}

bool CopticCalendar::isEra0CountingBackward() const {
return true;
int32_t
CopticCalendar::handleGetLimit(UCalendarDateFields field, ELimitType limitType) const
{
if (field == UCAL_ERA) {
return CE; // Only one era in this mode, era is always CE
}
return CECalendar::handleGetLimit(field, limitType);
}

int32_t
Expand Down
3 changes: 2 additions & 1 deletion icu4c/source/i18n/coptccal.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,10 @@ class CopticCalendar : public CECalendar {
int32_t extendedYearToYear(int32_t extendedYear) const override;

/**
* Calculate the limit for a specified type of limit and field
* @internal
*/
bool isEra0CountingBackward() const override;
virtual int32_t handleGetLimit(UCalendarDateFields field, ELimitType limitType) const override;
public:
/**
* Override Calendar Returns a unique class ID POLYMORPHICALLY. Pure virtual
Expand Down
1 change: 0 additions & 1 deletion icu4c/source/test/cintltst/ccaltst.c
Original file line number Diff line number Diff line change
Expand Up @@ -2145,7 +2145,6 @@ static const EraTestItem eraTestItems[] = {
/* calendars with non-modern era 0 that goes backwards, max era == 1 */
{ "en@calendar=gregorian", true },
{ "en@calendar=roc", true },
{ "en@calendar=coptic", true },
/* calendars with non-modern era 0 that goes forwards, max era > 1 */
{ "en@calendar=japanese", false },
{ "en@calendar=chinese", false },
Expand Down
10 changes: 5 additions & 5 deletions icu4c/source/test/intltest/calcasts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -399,15 +399,15 @@ void CalendarCaseTest::Coptic() {
{2402708.5, 1, 1582, 8, 10, TUE, 0, 0, 0}, // Gregorian: 17/04/1866
{2402971.5, 1, 1583, 4, 28, SAT, 0, 0, 0}, // Gregorian: 05/01/1867
{2403344.5, 1, 1584, 5, 5, MON, 0, 0, 0}, // Gregorian: 13/01/1868
{1721059.5, 0, 285, 5, 7, SAT, 0, 0, 0}, // Gregorian: 01/01/0000
{1721425.5, 0, 284, 5, 8, MON, 0, 0, 0}, // Gregorian: 01/01/0001
{1824663.5, 0, 2, 13, 6, WED, 0, 0, 0}, // Gregorian: 29/08/0283
{1824664.5, 0, 1, 1, 1, THU, 0, 0, 0}, // Gregorian: 30/08/0283
{1721059.5, 1, -284, 5, 7, SAT, 0, 0, 0}, // Gregorian: 01/01/0000
{1721425.5, 1, -283, 5, 8, MON, 0, 0, 0}, // Gregorian: 01/01/0001
{1824663.5, 1, -1, 13, 6, WED, 0, 0, 0}, // Gregorian: 29/08/0283
{1824664.5, 1, 0, 1, 1, THU, 0, 0, 0}, // Gregorian: 30/08/0283
{1825029.5, 1, 1, 1, 1, FRI, 0, 0, 0}, // Gregorian: 29/08/0284
{1825394.5, 1, 2, 1, 1, SAT, 0, 0, 0}, // Gregorian: 29/08/0285
{1825759.5, 1, 3, 1, 1, SUN, 0, 0, 0}, // Gregorian: 29/08/0286
{1826125.5, 1, 4, 1, 1, TUE, 0, 0, 0}, // Gregorian: 30/08/0287
{1825028.5, 0, 1, 13, 5, THU, 0, 0, 0}, // Gregorian: 28/08/0284
{1825028.5, 1, 0, 13, 5, THU, 0, 0, 0}, // Gregorian: 28/08/0284
{1825393.5, 1, 1, 13, 5, FRI, 0, 0, 0}, // Gregorian: 28/08/0285
{1825758.5, 1, 2, 13, 5, SAT, 0, 0, 0}, // Gregorian: 28/08/0286
{1826123.5, 1, 3, 13, 5, SUN, 0, 0, 0}, // Gregorian: 28/08/0287
Expand Down
24 changes: 24 additions & 0 deletions icu4c/source/test/intltest/caltest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ void CalendarTest::runIndexedTest( int32_t index, UBool exec, const char* &name,
TESTCASE_AUTO(Test22441Year88369);
TESTCASE_AUTO(Test22730JapaneseOverflow);
TESTCASE_AUTO(Test22730CopticOverflow);
TESTCASE_AUTO(TestBug23277);
TESTCASE_AUTO(Test22962ComputeJulianDayOverflow);

TESTCASE_AUTO(TestAddOverflow);
Expand Down Expand Up @@ -6156,6 +6157,29 @@ void CalendarTest::Test22730CopticOverflow() {
assertEquals("status return without overflow", status, U_ILLEGAL_ARGUMENT_ERROR);
}

void CalendarTest::TestBug23277() {
IcuTestErrorCode status(*this, "TestBug23277");
LocalPointer<Calendar> cal(
Calendar::createInstance(Locale("en-u-ca-coptic"), status),
status);
if (failure(status, "Calendar::createInstance en-u-ca-coptic")) return;

cal->set(2000, 0, 1); // 2000 CE (around 1716 AM)
assertEquals("Era for positive extended year should be CE (1)",
(int32_t)CopticCalendar::CE, cal->get(UCAL_ERA, status));
assertEquals("Minimum era should be CE (1)",
(int32_t)CopticCalendar::CE, cal->getMinimum(UCAL_ERA));
assertEquals("Maximum era should be CE (1)",
(int32_t)CopticCalendar::CE, cal->getMaximum(UCAL_ERA));

cal->clear();
cal->set(UCAL_EXTENDED_YEAR, -283); // Year -283 AM (Julian year 1 CE)
assertEquals("Era for negative extended year should be CE (1) in single-era model",
(int32_t)CopticCalendar::CE, cal->get(UCAL_ERA, status));
assertEquals("Year for negative extended year should equal extended year in single-era model",
-283, cal->get(UCAL_YEAR, status));
}

void CalendarTest::Test22962ComputeJulianDayOverflow() {
UErrorCode status = U_ZERO_ERROR;
LocalPointer<Calendar> calendar(
Expand Down
1 change: 1 addition & 0 deletions icu4c/source/test/intltest/caltest.h
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ class CalendarTest: public CalendarTimeZoneTest {
void Test22633RollTwiceGetTimeOverflow();
void Test22730JapaneseOverflow();
void Test22730CopticOverflow();
void TestBug23277();
void Test22962ComputeJulianDayOverflow();
void Test22962MonthAddOneOverflow();

Expand Down
33 changes: 18 additions & 15 deletions icu4j/main/core/src/main/java/com/ibm/icu/util/CopticCalendar.java
Original file line number Diff line number Diff line change
Expand Up @@ -248,12 +248,6 @@ public String getType() {
* @internal
* @deprecated This API is ICU internal only.
*/
@Override
@Deprecated
protected boolean isEra0CountingBackward() {
return true;
}

/**
* {@inheritDoc}
*
Expand All @@ -267,13 +261,7 @@ protected int handleGetExtendedYear() {
if (newerField(EXTENDED_YEAR, YEAR) == EXTENDED_YEAR) {
eyear = internalGet(EXTENDED_YEAR, 1); // Default to year 1
} else {
// The year defaults to the epoch start, the era to AD
int era = internalGet(ERA, CE);
if (era == BCE) {
eyear = 1 - internalGet(YEAR, 1); // Convert to extended year
} else {
eyear = internalGet(YEAR, 1); // Default to year 1
}
eyear = internalGet(YEAR, 1); // Default to year 1
}
return eyear;
}
Expand All @@ -287,7 +275,7 @@ protected int handleGetExtendedYear() {
@Deprecated
@Override
protected int extendedYearToEra(int eyear) {
return (eyear <= 0) ? BCE : CE;
return CE;
}

/**
Expand All @@ -299,7 +287,22 @@ protected int extendedYearToEra(int eyear) {
@Deprecated
@Override
protected int extendedYearToYear(int eyear) {
return (eyear <= 0) ? 1 - eyear : eyear;
return eyear;
}

/**
* {@inheritDoc}
*
* @internal
* @deprecated This API is ICU internal only.
*/
@Override
@Deprecated
protected int handleGetLimit(int field, int limitType) {
if (field == ERA) {
return CE; // Only one era in this mode, era is always CE

@sffc sffc Jul 15, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: Coptic has never used CE (Common Era). This looks like a typo made during some previous refactor, since the era has been sometimes called AD, but that stands for "After Diocletian", not "Anno Domini". The best English/Latin name for this era based on my research is Anno Martyrum or "AM".

Unless CE is intended to stand for "Coptic Era"? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is used in the code

look at the code removed above

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok so the CE era means "Coptic/Ethiopian"? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yumaoka You need to ask him.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original implementation was merged into ICU4J. Doug Felt created the initial version. I guess he got the implementation from someone else. I did C port about two decades ago. I think "CE" era means "Copting/Ethiopian".

}
return super.handleGetLimit(field, limitType);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,15 @@ public void TestCases() {
new TestCase(2402708.5, 1, 1582, 8, 10, TUE, 0, 0, 0), // Gregorian: 17/04/1866
new TestCase(2402971.5, 1, 1583, 4, 28, SAT, 0, 0, 0), // Gregorian: 05/01/1867
new TestCase(2403344.5, 1, 1584, 5, 5, MON, 0, 0, 0), // Gregorian: 13/01/1868
new TestCase(1721059.5, 0, 285, 5, 7, SAT, 0, 0, 0), // Gregorian: 01/01/0000
new TestCase(1721425.5, 0, 284, 5, 8, MON, 0, 0, 0), // Gregorian: 01/01/0001
new TestCase(1824663.5, 0, 2, 13, 6, WED, 0, 0, 0), // Gregorian: 29/08/0283
new TestCase(1824664.5, 0, 1, 1, 1, THU, 0, 0, 0), // Gregorian: 30/08/0283
new TestCase(1721059.5, 1, -284, 5, 7, SAT, 0, 0, 0), // Gregorian: 01/01/0000
new TestCase(1721425.5, 1, -283, 5, 8, MON, 0, 0, 0), // Gregorian: 01/01/0001
new TestCase(1824663.5, 1, -1, 13, 6, WED, 0, 0, 0), // Gregorian: 29/08/0283
new TestCase(1824664.5, 1, 0, 1, 1, THU, 0, 0, 0), // Gregorian: 30/08/0283
new TestCase(1825029.5, 1, 1, 1, 1, FRI, 0, 0, 0), // Gregorian: 29/08/0284
new TestCase(1825394.5, 1, 2, 1, 1, SAT, 0, 0, 0), // Gregorian: 29/08/0285
new TestCase(1825759.5, 1, 3, 1, 1, SUN, 0, 0, 0), // Gregorian: 29/08/0286
new TestCase(1826125.5, 1, 4, 1, 1, TUE, 0, 0, 0), // Gregorian: 30/08/0287
new TestCase(1825028.5, 0, 1, 13, 5, THU, 0, 0, 0), // Gregorian: 28/08/0284
new TestCase(1825028.5, 1, 0, 13, 5, THU, 0, 0, 0), // Gregorian: 28/08/0284
new TestCase(1825393.5, 1, 1, 13, 5, FRI, 0, 0, 0), // Gregorian: 28/08/0285
new TestCase(1825758.5, 1, 2, 13, 5, SAT, 0, 0, 0), // Gregorian: 28/08/0286
new TestCase(1826123.5, 1, 3, 13, 5, SUN, 0, 0, 0), // Gregorian: 28/08/0287
Expand Down Expand Up @@ -183,9 +183,8 @@ public void TestEraStart() {
assertEquals("Coptic Date", "Fri Jan 01, 0001 AD", copticFmt.format(cal));
assertEquals("Gregorian Date", "Fri Aug 29, 0284 AD", fmt.format(cal.getTime()));

cal.set(Calendar.ERA, 0);
cal.set(Calendar.YEAR, 1);
assertEquals("Coptic Date", "Thu Jan 01, 0001 BC", copticFmt.format(cal));
cal.set(Calendar.EXTENDED_YEAR, 0);
assertEquals("Coptic Date", "Thu Jan 01, 0000 AD", copticFmt.format(cal));
assertEquals("Gregorian Date", "Thu Aug 30, 0283 AD", fmt.format(cal.getTime()));
}

Expand Down Expand Up @@ -458,4 +457,22 @@ public int getEndDay() {
}
}
}

@Test
public void TestBug23277() {
CopticCalendar cal = new CopticCalendar(ULocale.forLanguageTag("en-u-ca-coptic"));
assertEquals("Minimum era should be CE (1)", 1, cal.getMinimum(Calendar.ERA));
assertEquals("Maximum era should be CE (1)", 1, cal.getMaximum(Calendar.ERA));

cal.set(Calendar.EXTENDED_YEAR, 1716);
assertEquals("Era for positive extended year should be CE (1)", 1, cal.get(Calendar.ERA));
assertEquals("Year should be 1716", 1716, cal.get(Calendar.YEAR));

cal.set(Calendar.EXTENDED_YEAR, -283);
assertEquals("Era for negative extended year should be CE (1)", 1, cal.get(Calendar.ERA));
assertEquals(
"Year for negative extended year should equal extended year",
-283,
cal.get(Calendar.YEAR));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2463,7 +2463,6 @@ public void TestAddRollEra0AndEraBounds() {
// calendars with non-modern era 0 that goes backwards, max era == 1
"en@calendar=gregorian",
"en@calendar=roc",
"en@calendar=coptic",
// calendars with non-modern era 0 that goes backwards, max era > 1
"en@calendar=japanese",
// calendars with non-modern era 0 that goes forwards, max era > 1
Expand Down
Loading