diff --git a/icu4c/source/i18n/calendar.cpp b/icu4c/source/i18n/calendar.cpp index cf7917beb9f0..432cda77e2f5 100644 --- a/icu4c/source/i18n/calendar.cpp +++ b/icu4c/source/i18n/calendar.cpp @@ -50,6 +50,7 @@ #include "coptccal.h" #include "dangical.h" #include "ethpccal.h" +#include "myancal.h" #include "unicode/calendar.h" #include "cpputils.h" #include "servloc.h" @@ -162,6 +163,7 @@ static const char * const gCalTypes[] = { "gregorian", "japanese", "buddhist", + "myanmar", "roc", "persian", "islamic-civil", @@ -186,6 +188,7 @@ typedef enum ECalType { CALTYPE_GREGORIAN = 0, CALTYPE_JAPANESE, CALTYPE_BUDDHIST, + CALTYPE_MYANMAR, CALTYPE_ROC, CALTYPE_PERSIAN, CALTYPE_ISLAMIC_CIVIL, @@ -323,6 +326,9 @@ static Calendar *createStandardCalendar(ECalType calType, const Locale &loc, UEr case CALTYPE_BUDDHIST: cal.adoptInsteadAndCheckErrorCode(new BuddhistCalendar(loc, status), status); break; + case CALTYPE_MYANMAR: + cal.adoptInsteadAndCheckErrorCode(new MyanmarCalendar(loc, status), status); + break; case CALTYPE_ROC: cal.adoptInsteadAndCheckErrorCode(new TaiwanCalendar(loc, status), status); break; diff --git a/icu4c/source/i18n/i18n.vcxproj b/icu4c/source/i18n/i18n.vcxproj index eea2571e4f62..fac7a0e039f3 100644 --- a/icu4c/source/i18n/i18n.vcxproj +++ b/icu4c/source/i18n/i18n.vcxproj @@ -185,6 +185,7 @@ + @@ -400,6 +401,7 @@ + diff --git a/icu4c/source/i18n/i18n.vcxproj.filters b/icu4c/source/i18n/i18n.vcxproj.filters index fd0e99497dc0..c483198f8eab 100644 --- a/icu4c/source/i18n/i18n.vcxproj.filters +++ b/icu4c/source/i18n/i18n.vcxproj.filters @@ -252,6 +252,9 @@ formatting + + formatting + formatting @@ -908,6 +911,9 @@ formatting + + formatting + formatting diff --git a/icu4c/source/i18n/i18n_uwp.vcxproj b/icu4c/source/i18n/i18n_uwp.vcxproj index db763118b57c..0ea1e453a564 100644 --- a/icu4c/source/i18n/i18n_uwp.vcxproj +++ b/icu4c/source/i18n/i18n_uwp.vcxproj @@ -418,6 +418,7 @@ + @@ -631,6 +632,7 @@ + diff --git a/icu4c/source/i18n/myancal.cpp b/icu4c/source/i18n/myancal.cpp new file mode 100644 index 000000000000..5229c9ee81ac --- /dev/null +++ b/icu4c/source/i18n/myancal.cpp @@ -0,0 +1,107 @@ +// © 2025 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html +/* +* +* File MYANCAL.CPP +* +* Modification History: +* 04/18/2025 mapmeld copied from buddhcal.h +* +*/ + +#include "unicode/ucal.h" +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "myancal.h" +#include "gregoimp.h" +#include "unicode/gregocal.h" +#include "umutex.h" +#include + +U_NAMESPACE_BEGIN + +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(MyanmarCalendar) + +//static const int32_t kMaxEra = 0; // only 1 era + +static const int32_t kMEEraStart = 639; // 639 AD (Gregorian) + +static const int32_t kGregorianEpoch = 1970; // used as the default value of EXTENDED_YEAR + +MyanmarCalendar::MyanmarCalendar(const Locale& aLocale, UErrorCode& success) +: GregorianCalendar(aLocale, success) +{ +} + +MyanmarCalendar::~MyanmarCalendar() +{ +} + +MyanmarCalendar::MyanmarCalendar(const MyanmarCalendar& source) +: GregorianCalendar(source) +{ +} + +MyanmarCalendar* MyanmarCalendar::clone() const +{ + return new MyanmarCalendar(*this); +} + +const char *MyanmarCalendar::getType() const +{ + return "myanmar"; +} + +int32_t MyanmarCalendar::yearStart(int32_t year, UErrorCode& status) { + return GregorianCalendar::handleComputeMonthStart(year + 638, 3, true, status) + 16; +} + +int32_t MyanmarCalendar::handleGetExtendedYear(UErrorCode& status) +{ + if (U_FAILURE(status)) { + return 0; + } + + // extended year is a gregorian year, where 1 = 1AD, 0 = 1BC, -1 = 2BC, etc + int32_t y = internalGet(UCAL_YEAR, kGregorianEpoch - kMEEraStart); + int32_t m = internalGet(UCAL_MONTH); + int32_t d = internalGet(UCAL_DAY_OF_MONTH); + if ((m == 3 && d >= 17) || m >= 4) { + y--; + internalSet(UCAL_YEAR, y); + } + if (uprv_add32_overflow(y, kMEEraStart, &y)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } + return y; +} + +void MyanmarCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status) +{ + GregorianCalendar::handleComputeFields(julianDay, status); + int32_t y = internalGet(UCAL_EXTENDED_YEAR) - kMEEraStart; + int32_t m = internalGet(UCAL_MONTH); + int32_t d = internalGet(UCAL_DAY_OF_MONTH); + if ((m == 3 && d >= 17) || m >= 4) { + y++; + } + internalSet(UCAL_ERA, 0); + internalSet(UCAL_YEAR, y); +} + +int32_t MyanmarCalendar::handleGetLimit(UCalendarDateFields field, ELimitType limitType) const +{ + if(field == UCAL_ERA) { + return ME; + } + return GregorianCalendar::handleGetLimit(field,limitType); +} + +IMPL_SYSTEM_DEFAULT_CENTURY(MyanmarCalendar, "@calendar=myanmar") + +U_NAMESPACE_END + +#endif diff --git a/icu4c/source/i18n/myancal.h b/icu4c/source/i18n/myancal.h new file mode 100644 index 000000000000..b762732d77b7 --- /dev/null +++ b/icu4c/source/i18n/myancal.h @@ -0,0 +1,163 @@ +// © 2025 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html +/* + * + * File MYANCAL.H + * + * Modification History: + * + * Date Name Description + * 04/18/2025 srl copied from buddhcal.h + ******************************************************************************** + */ + +#ifndef MYANCAL_H +#define MYANCAL_H + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "unicode/calendar.h" +#include "unicode/gregocal.h" + +U_NAMESPACE_BEGIN + +/** + * Concrete class which provides the Myanmar calendar. + *

+ * MyanmarCalendar is a subclass of GregorianCalendar + * that numbers years since the birth of the Buddha. This imementatin ]is ]based ]n the civil calendar + * used in Myanmar. + *

+ * The Myanmar calendar increments in mid April; represented as April 17 as it + * has been between April 2001 - 2034 (inclusive). + *

+ * The Myanmar Calendar has only one allowable era: ME. If the + * calendar is not in lenient mode (see setLenient), dates before + * 1/1/1 ME are rejected as an illegal argument. + *

+ * @internal + */ +class MyanmarCalendar : public GregorianCalendar { +public: + + /** + * Useful constants for MyanmarCalendar. Only one Era. + * @internal + */ + enum EEras { + ME + }; + + /** + * Constructs a MyanmarCalendar based on the current time in the default time zone + * with the given locale. + * + * @param aLocale The given locale. + * @param success Indicates the status of MyanmarCalendar object construction. + * Returns U_ZERO_ERROR if constructed successfully. + * @internal + */ + MyanmarCalendar(const Locale& aLocale, UErrorCode& success); + + + /** + * Destructor + * @internal + */ + virtual ~MyanmarCalendar(); + + /** + * Copy constructor + * @param source the object to be copied. + * @internal + */ + MyanmarCalendar(const MyanmarCalendar& source); + + /** + * Create and return a polymorphic copy of this calendar. + * @return return a polymorphic copy of this calendar. + * @internal + */ + virtual MyanmarCalendar* clone() const override; + +public: + /** + * Override Calendar Returns a unique class ID POLYMORPHICALLY. Pure virtual + * override. This method is to implement a simple version of RTTI, since not all C++ + * compilers support genuine RTTI. Polymorphic operator==() and clone() methods call + * this method. + * + * @return The class ID for this object. All objects of a given class have the + * same class ID. Objects of other classes have different class IDs. + * @internal + */ + virtual UClassID getDynamicClassID() const override; + + /** + * Return the class ID for this class. This is useful only for comparing to a return + * value from getDynamicClassID(). For example: + * + * Base* polymorphic_pointer = createPolymorphicObject(); + * if (polymorphic_pointer->getDynamicClassID() == + * Derived::getStaticClassID()) ... + * + * @return The class ID for all objects of this class. + * @internal + */ + U_I18N_API static UClassID U_EXPORT2 getStaticClassID(); + + /** + * return the calendar type, "myanmar". + * + * @return calendar type + * @internal + */ + virtual const char * getType() const override; + +private: + MyanmarCalendar(); // default constructor not implemented + + /** + * Return the day # on which the given Myanmar era year starts (April 17th). + */ + int32_t yearStart(int32_t year, UErrorCode& status); + + protected: + /** + * Return the extended year defined by the current fields. This will + * use the UCAL_EXTENDED_YEAR field or the UCAL_YEAR and supra-year fields (such + * as UCAL_ERA) specific to the calendar system, depending on which set of + * fields is newer. + * @param status + * @return the extended year + * @internal + */ + virtual int32_t handleGetExtendedYear(UErrorCode& status) override; + /** + * Subclasses may override this method to compute several fields + * specific to each calendar system. + * @internal + */ + virtual void handleComputeFields(int32_t julianDay, UErrorCode& status) override; + /** + * Subclass API for defining limits of different types. + * @param field one of the field numbers + * @param limitType one of MINIMUM, GREATEST_MINIMUM, + * LEAST_MAXIMUM, or MAXIMUM + * @internal + */ + virtual int32_t handleGetLimit(UCalendarDateFields field, ELimitType limitType) const override; + + virtual bool isEra0CountingBackward() const override { return false; } + + DECLARE_OVERRIDE_SYSTEM_DEFAULT_CENTURY +}; + +U_NAMESPACE_END + +#endif /* #if !UCONFIG_NO_FORMATTING */ + +#endif // _GREGOCAL +//eof diff --git a/icu4c/source/i18n/sources.txt b/icu4c/source/i18n/sources.txt index 77532f556467..c7f48cd90506 100644 --- a/icu4c/source/i18n/sources.txt +++ b/icu4c/source/i18n/sources.txt @@ -105,6 +105,7 @@ messageformat2_formattable.cpp messageformat2_function_registry.cpp messageformat2_parser.cpp messageformat2_serializer.cpp +myancal.cpp name2uni.cpp nfrs.cpp nfrule.cpp diff --git a/icu4c/source/i18n/ucal.cpp b/icu4c/source/i18n/ucal.cpp index b8905fe9a3d1..c503b6c0447b 100644 --- a/icu4c/source/i18n/ucal.cpp +++ b/icu4c/source/i18n/ucal.cpp @@ -710,6 +710,7 @@ static const char * const CAL_TYPES[] = { "islamic-umalqura", "islamic-tbla", "islamic-rgsa", + "myanmar", nullptr }; diff --git a/icu4c/source/test/depstest/dependencies.txt b/icu4c/source/test/depstest/dependencies.txt index caf3c57cdbe8..f789f9f7b418 100644 --- a/icu4c/source/test/depstest/dependencies.txt +++ b/icu4c/source/test/depstest/dependencies.txt @@ -1083,7 +1083,7 @@ group: formatting measfmt.o quantityformatter.o # dateformat astro.o buddhcal.o calendar.o cecal.o chnsecal.o coptccal.o dangical.o ethpccal.o - gregocal.o gregoimp.o hebrwcal.o indiancal.o islamcal.o iso8601cal.o japancal.o persncal.o taiwncal.o + gregocal.o gregoimp.o hebrwcal.o indiancal.o islamcal.o iso8601cal.o japancal.o persncal.o taiwncal.o myancal.o erarules.o # mostly for Japanese eras ucal.o basictz.o olsontz.o rbtz.o simpletz.o timezone.o tzrule.o tztrans.o diff --git a/icu4c/source/test/intltest/callimts.cpp b/icu4c/source/test/intltest/callimts.cpp index bbac90cea1f7..6296c0caa65c 100644 --- a/icu4c/source/test/intltest/callimts.cpp +++ b/icu4c/source/test/intltest/callimts.cpp @@ -170,9 +170,10 @@ TestCase TestCases[] = { {"indian", false, DEFAULT_START, DEFAULT_END}, {"coptic", false, DEFAULT_START, DEFAULT_END}, {"ethiopic", false, DEFAULT_START, DEFAULT_END}, - {"ethiopic-amete-alem", false, DEFAULT_START, DEFAULT_END} + {"ethiopic-amete-alem", false, DEFAULT_START, DEFAULT_END}, + {"myanmar", false, DEFAULT_START, DEFAULT_END} }; - + struct { int32_t fIndex; UBool next (int32_t &rIndex) { diff --git a/icu4c/source/test/intltest/incaltst.cpp b/icu4c/source/test/intltest/incaltst.cpp index 4b825fda06c1..17384a553cb5 100644 --- a/icu4c/source/test/intltest/incaltst.cpp +++ b/icu4c/source/test/intltest/incaltst.cpp @@ -92,6 +92,8 @@ void IntlCalendarTest::runIndexedTest( int32_t index, UBool exec, const char* &n TESTCASE_AUTO(TestGregorianToPersian); TESTCASE_AUTO(TestPersianFormat); TESTCASE_AUTO(TestTaiwan); + TESTCASE_AUTO(TestMyanmar); + TESTCASE_AUTO(TestMyanmarFormat); TESTCASE_AUTO(TestConsistencyGregorian); TESTCASE_AUTO(TestConsistencyCoptic); TESTCASE_AUTO(TestConsistencyEthiopic); @@ -779,6 +781,118 @@ void IntlCalendarTest::TestForceGannenNumbering() } } +/** + * Verify the Myanmar Calendar. + */ +void IntlCalendarTest::TestMyanmar() { + UDate timeA = Calendar::getNow(); + + Calendar *cal; + UErrorCode status = U_ZERO_ERROR; + cal = Calendar::createInstance("en_US@calendar=myanmar", status); + CHECK(status, UnicodeString("Creating en_US@calendar=myanmar calendar")); + // Sanity check the calendar + UDate timeB = Calendar::getNow(); + UDate timeCal = cal->getTime(status); + + if(!(timeA <= timeCal) || !(timeCal <= timeB)) { + errln((UnicodeString)"Error: Calendar time " + timeCal + + " is not within sampled times [" + timeA + " to " + timeB + "]!"); + } + + // Test thingyan in recent years + int32_t data[] = { + 2024, 6, 12, 1386, 6, 12, // 2024 + 2019, 4, 17, 1381, 4, 17, // start of 1381 + 2019, 1, 1, 1380, 1, 1, + 2018,12, 31, 1380,12, 31, + + 2018, 4, 17, 1380, 4, 17, // start year in kason + 2018, 4, 16, 1379, 4, 16, // late kason + 2017, 4, 17, 1379, 4, 17, // start year in tagu + 2017, 4, 16, 1378, 4, 16, // late tagu + + 2016, 4, 17, 1378, 4, 17, // first day of 1378, tagu + 2016, 4, 16, 1377, 4, 16, // last day of 1377, late tagu + + 2015, 4, 17, 1377, 4, 17, // first day of year is single-day tagu + 2015, 4, 3, 1376, 4, 3, // late dagu + + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + + Calendar *grego = Calendar::createInstance("en_US@calendar=gregorian", status); + for (int32_t i=0; data[i]!=-1; ) { + int32_t gregYear = data[i++]; + int32_t gregMonth = data[i++]-1; + int32_t gregDay = data[i++]; + int32_t myanYear = data[i++]; + int32_t myanMonth = data[i++]-1; + int32_t myanDay = data[i++]; + + // Test conversion from Myanmar dates + grego->clear(); + grego->set(gregYear, gregMonth, gregDay); + + cal->clear(); + cal->set(myanYear, myanMonth, myanDay); + + UDate myanTime = cal->getTime(status); + UDate gregTime = grego->getTime(status); + + if (myanTime != gregTime) { + errln(UnicodeString("Expected ") + gregTime + " but got " + myanTime); + } + + // Test conversion to Myanmar dates + cal->clear(); + cal->setTime(gregTime, status); + + int32_t computedYear = cal->get(UCAL_YEAR, status); + int32_t computedMonth = cal->get(UCAL_MONTH, status); + int32_t computedDay = cal->get(UCAL_DATE, status); + + if ((myanYear != computedYear) || + (myanMonth != computedMonth) || + (myanDay != computedDay)) { + errln(UnicodeString("Expected ") + myanYear + "/" + (myanMonth+1) + "/" + myanDay + + " but got " + computedYear + "/" + (computedMonth+1) + "/" + computedDay); + } + + } + + delete cal; + delete grego; +} + +void IntlCalendarTest::TestMyanmarFormat() { + UErrorCode status = U_ZERO_ERROR; + SimpleDateFormat fmt(UnicodeString("MMMM d, yyyy G"), Locale("en_US@calendar=myanmar"), status); + CHECK(status, "creating date format instance"); + SimpleDateFormat fmt2(UnicodeString("MMMM d, yyyy G"), Locale("en_US@calendar=gregorian"), status); + CHECK(status, "creating gregorian date format instance"); + UnicodeString gregorianDate("April 15, 1989 AD"); + UDate aDate = fmt2.parse(gregorianDate, status); + UnicodeString str; + fmt.format(aDate, str); + logln(UnicodeString() + "as Myanmar Calendar: " + escape(str)); + UnicodeString expected("April 15, 1350 BC"); // TODO : correct to ME + if(str != expected) { + errln("Expected " + escape(expected) + " but got " + escape(str)); + } + UDate otherDate = fmt.parse(expected, status); + if(otherDate != aDate) { + UnicodeString str3; + fmt.format(otherDate, str3); + errln("Parse incorrect of " + escape(expected) + " - wanted " + aDate + " but got " + otherDate + ", " + escape(str3)); + } else { + logln("Parsed OK: " + expected); + } + + CHECK(status, "Error occurred testing Myanmar Calendar in English "); +} + + /** * Verify the Persian Calendar. */ diff --git a/icu4c/source/test/intltest/incaltst.h b/icu4c/source/test/intltest/incaltst.h index a1f7d11bfc40..d56ab24001b4 100644 --- a/icu4c/source/test/intltest/incaltst.h +++ b/icu4c/source/test/intltest/incaltst.h @@ -47,6 +47,9 @@ class IntlCalendarTest: public CalendarTimeZoneTest { void TestGregorianToPersian(); void TestPersianFormat(); + void TestMyanmar(); + void TestMyanmarFormat(); + void TestConsistencyGregorian(); void TestConsistencyCoptic(); void TestConsistencyEthiopic(); diff --git a/icu4c/source/test/intltest/uobjtest.cpp b/icu4c/source/test/intltest/uobjtest.cpp index a10a71b32759..d7f7872f87dd 100644 --- a/icu4c/source/test/intltest/uobjtest.cpp +++ b/icu4c/source/test/intltest/uobjtest.cpp @@ -243,6 +243,7 @@ UObject *UObjectTest::testClassNoClassID(UObject *obj, const char *className, co #include "taiwncal.h" #include "indiancal.h" #include "chnsecal.h" +#include "myancal.h" #include "windtfmt.h" #include "winnmfmt.h" #include "ustrenum.h" @@ -389,6 +390,7 @@ void UObjectTest::testIDs() TESTCLASSID_FACTORY(IndianCalendar, Calendar::createInstance(Locale("@calendar=indian"), status)); TESTCLASSID_FACTORY(ChineseCalendar, Calendar::createInstance(Locale("@calendar=chinese"), status)); TESTCLASSID_FACTORY(TaiwanCalendar, Calendar::createInstance(Locale("@calendar=roc"), status)); + TESTCLASSID_FACTORY(MyanmarCalendar, Calendar::createInstance(Locale("@calendar=myanmar"), status)); #if U_PLATFORM_USES_ONLY_WIN32_API TESTCLASSID_FACTORY(Win32DateFormat, DateFormat::createDateInstance(DateFormat::kFull, Locale("@compat=host"))); TESTCLASSID_FACTORY(Win32NumberFormat, NumberFormat::createInstance(Locale("@compat=host"), status)); diff --git a/icu4c/source/test/testdata/structLocale.txt b/icu4c/source/test/testdata/structLocale.txt index 99f3cdc0d3a3..11bff13d9376 100644 --- a/icu4c/source/test/testdata/structLocale.txt +++ b/icu4c/source/test/testdata/structLocale.txt @@ -34535,6 +34535,7 @@ structLocale:table(nofallback){ islamic-umalqura{""} iso8601{""} japanese{""} + myanmar{""} persian{""} roc{""} }