From a5461cbc8ed20e0981fd4e846a180f35b07dcc0a Mon Sep 17 00:00:00 2001 From: Meri Khamoyan <96171496+mkhamoyan@users.noreply.github.com> Date: Mon, 9 Oct 2023 15:42:27 +0200 Subject: [PATCH] [iOS][non-icu] HybridGlobalization implement japanese calendar data (#92471) Add japanese calendar function and GetCalendars for hybrid mode --- .../src/Interop/Interop.Calendar.iOS.cs | 11 ++ .../System/Globalization/CalendarTestBase.cs | 2 +- .../System/Globalization/CalendarData.Icu.cs | 10 +- .../Globalization/JapaneseCalendar.Icu.cs | 63 +++++++- .../System.Globalization.Native/entrypoints.c | 3 + .../pal_calendarData.c | 10 -- .../pal_calendarData.h | 21 +++ .../pal_calendarData.m | 147 +++++++++++++++++- 8 files changed, 246 insertions(+), 21 deletions(-) diff --git a/src/libraries/Common/src/Interop/Interop.Calendar.iOS.cs b/src/libraries/Common/src/Interop/Interop.Calendar.iOS.cs index 11a6246a1d700..985e55b604e4f 100644 --- a/src/libraries/Common/src/Interop/Interop.Calendar.iOS.cs +++ b/src/libraries/Common/src/Interop/Interop.Calendar.iOS.cs @@ -9,7 +9,18 @@ internal static partial class Interop { internal static partial class Globalization { + [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetCalendarsNative", StringMarshalling = StringMarshalling.Utf8)] + internal static partial int GetCalendarsNative(string localeName, CalendarId[] calendars, int calendarsCapacity); + [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetCalendarInfoNative", StringMarshalling = StringMarshalling.Utf8)] internal static partial string GetCalendarInfoNative(string localeName, CalendarId calendarId, CalendarDataType calendarDataType); + + [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetLatestJapaneseEraNative")] + internal static partial int GetLatestJapaneseEraNative(); + + [LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_GetJapaneseEraStartDateNative", StringMarshalling = StringMarshalling.Utf8)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool GetJapaneseEraStartDateNative(int era, out int startYear, out int startMonth, out int startDay); + } } diff --git a/src/libraries/System.Globalization.Calendars/tests/System/Globalization/CalendarTestBase.cs b/src/libraries/System.Globalization.Calendars/tests/System/Globalization/CalendarTestBase.cs index 0d67b2ec9fd34..061d07108e526 100644 --- a/src/libraries/System.Globalization.Calendars/tests/System/Globalization/CalendarTestBase.cs +++ b/src/libraries/System.Globalization.Calendars/tests/System/Globalization/CalendarTestBase.cs @@ -430,7 +430,7 @@ public void GetEra_Invalid_ThrowsArgumentOutOfRangeException() Assert.All(DateTime_TestData(calendar), dt => { // JapaneseCalendar throws on ICU, but not on NLS or in HybridGlobalization on Browser - if ((calendar is JapaneseCalendar && (PlatformDetection.IsNlsGlobalization || PlatformDetection.IsHybridGlobalizationOnBrowser || PlatformDetection.IsHybridGlobalizationOnOSX)) || calendar is HebrewCalendar || calendar is TaiwanLunisolarCalendar || calendar is JapaneseLunisolarCalendar) + if ((calendar is JapaneseCalendar && (PlatformDetection.IsNlsGlobalization || PlatformDetection.IsHybridGlobalizationOnBrowser)) || calendar is HebrewCalendar || calendar is TaiwanLunisolarCalendar || calendar is JapaneseLunisolarCalendar) { calendar.GetEra(dt); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CalendarData.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CalendarData.Icu.cs index 5bc85b88384ec..f3a71c6bc9910 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CalendarData.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CalendarData.Icu.cs @@ -89,7 +89,15 @@ internal static int IcuGetCalendars(string localeName, CalendarId[] calendars) Debug.Assert(!GlobalizationMode.UseNls); // NOTE: there are no 'user overrides' on Linux - int count = Interop.Globalization.GetCalendars(localeName, calendars, calendars.Length); + int count; +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) + count = Interop.Globalization.GetCalendarsNative(localeName, calendars, calendars.Length); + else + count = Interop.Globalization.GetCalendars(localeName, calendars, calendars.Length); +#else + count = Interop.Globalization.GetCalendars(localeName, calendars, calendars.Length); +#endif // ensure there is at least 1 calendar returned if (count == 0 && calendars.Length > 0) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.Icu.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.Icu.cs index 5ac43a99e56c0..dc9204fa99c8c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.Icu.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/JapaneseCalendar.Icu.cs @@ -20,16 +20,36 @@ public partial class JapaneseCalendar : Calendar Debug.Assert(!GlobalizationMode.UseNls); string[]? eraNames; + int latestEra; +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) + { + eraNames = Interop.Globalization.GetCalendarInfoNative("ja-JP", CalendarId.JAPAN, CalendarDataType.EraNames).Split("||"); + if (eraNames.Length == 0) + { + return null; + } + latestEra = Interop.Globalization.GetLatestJapaneseEraNative(); + } + else + { + if (!CalendarData.EnumCalendarInfo("ja-JP", CalendarId.JAPAN, CalendarDataType.EraNames, out eraNames)) + { + return null; + } + latestEra = Interop.Globalization.GetLatestJapaneseEra(); + } +#else if (!CalendarData.EnumCalendarInfo("ja-JP", CalendarId.JAPAN, CalendarDataType.EraNames, out eraNames)) { return null; } + latestEra = Interop.Globalization.GetLatestJapaneseEra(); +#endif List eras = new List(); int lastMaxYear = GregorianCalendar.MaxYear; - int latestEra = Interop.Globalization.GetLatestJapaneseEra(); - for (int i = latestEra; i >= 0; i--) { DateTime dt; @@ -50,11 +70,35 @@ public partial class JapaneseCalendar : Calendar } string[] abbrevEnglishEraNames; +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) + { + var abbrevEraNames = Interop.Globalization.GetCalendarInfoNative("ja", CalendarId.JAPAN, CalendarDataType.AbbrevEraNames); + if (abbrevEraNames == null) + { + // Failed to get English names. fallback to hardcoded data. + abbrevEnglishEraNames = s_abbreviatedEnglishEraNames; + } + else + { + abbrevEnglishEraNames = abbrevEraNames.Split("||"); + } + } + else + { + if (!CalendarData.EnumCalendarInfo("ja", CalendarId.JAPAN, CalendarDataType.AbbrevEraNames, out abbrevEnglishEraNames!)) + { + // Failed to get English names. fallback to hardcoded data. + abbrevEnglishEraNames = s_abbreviatedEnglishEraNames; + } + } +#else if (!CalendarData.EnumCalendarInfo("ja", CalendarId.JAPAN, CalendarDataType.AbbrevEraNames, out abbrevEnglishEraNames!)) { // Failed to get English names. fallback to hardcoded data. abbrevEnglishEraNames = s_abbreviatedEnglishEraNames; } +#endif // Check if we are getting the English Name at the end of the returned list. // ICU usually return long list including all Era names written in Japanese characters except the recent eras which actually we support will be returned in English. @@ -101,12 +145,15 @@ private static bool GetJapaneseEraStartDate(int era, out DateTime dateTime) int startYear; int startMonth; int startDay; - bool result = Interop.Globalization.GetJapaneseEraStartDate( - era, - out startYear, - out startMonth, - out startDay); - + bool result; +#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS + if (GlobalizationMode.Hybrid) + result = Interop.Globalization.GetJapaneseEraStartDateNative(era, out startYear, out startMonth, out startDay); + else + result = Interop.Globalization.GetJapaneseEraStartDate(era, out startYear, out startMonth, out startDay); +#else + result = Interop.Globalization.GetJapaneseEraStartDate(era, out startYear, out startMonth, out startDay); +#endif if (result) { dateTime = new DateTime(startYear, startMonth, startDay); diff --git a/src/native/libs/System.Globalization.Native/entrypoints.c b/src/native/libs/System.Globalization.Native/entrypoints.c index 799b15ed2ca76..1cae8c378b828 100644 --- a/src/native/libs/System.Globalization.Native/entrypoints.c +++ b/src/native/libs/System.Globalization.Native/entrypoints.c @@ -64,6 +64,9 @@ static const Entry s_globalizationNative[] = DllImportEntry(GlobalizationNative_CompareStringNative) DllImportEntry(GlobalizationNative_EndsWithNative) DllImportEntry(GlobalizationNative_GetCalendarInfoNative) + DllImportEntry(GlobalizationNative_GetCalendarsNative) + DllImportEntry(GlobalizationNative_GetJapaneseEraStartDateNative) + DllImportEntry(GlobalizationNative_GetLatestJapaneseEraNative) DllImportEntry(GlobalizationNative_GetLocaleInfoIntNative) DllImportEntry(GlobalizationNative_GetLocaleInfoPrimaryGroupingSizeNative) DllImportEntry(GlobalizationNative_GetLocaleInfoSecondaryGroupingSizeNative) diff --git a/src/native/libs/System.Globalization.Native/pal_calendarData.c b/src/native/libs/System.Globalization.Native/pal_calendarData.c index ba4070252bdcd..e4d01d9426dfd 100644 --- a/src/native/libs/System.Globalization.Native/pal_calendarData.c +++ b/src/native/libs/System.Globalization.Native/pal_calendarData.c @@ -21,16 +21,6 @@ #define STRING_COPY(destination, numberOfElements, source) strncpy_s(destination, numberOfElements, source, _TRUNCATE); #endif -#define GREGORIAN_NAME "gregorian" -#define JAPANESE_NAME "japanese" -#define BUDDHIST_NAME "buddhist" -#define HEBREW_NAME "hebrew" -#define DANGI_NAME "dangi" -#define PERSIAN_NAME "persian" -#define ISLAMIC_NAME "islamic" -#define ISLAMIC_UMALQURA_NAME "islamic-umalqura" -#define ROC_NAME "roc" - #define JAPANESE_LOCALE_AND_CALENDAR "ja_JP@calendar=japanese" static const UChar UDAT_MONTH_DAY_UCHAR[] = {'M', 'M', 'M', 'M', 'd', '\0'}; diff --git a/src/native/libs/System.Globalization.Native/pal_calendarData.h b/src/native/libs/System.Globalization.Native/pal_calendarData.h index 50458ac471a60..ab725f7f65df1 100644 --- a/src/native/libs/System.Globalization.Native/pal_calendarData.h +++ b/src/native/libs/System.Globalization.Native/pal_calendarData.h @@ -66,6 +66,16 @@ typedef enum CalendarData_AbbrevEraNames = 14, } CalendarDataType; +#define GREGORIAN_NAME "gregorian" +#define JAPANESE_NAME "japanese" +#define BUDDHIST_NAME "buddhist" +#define HEBREW_NAME "hebrew" +#define DANGI_NAME "dangi" +#define PERSIAN_NAME "persian" +#define ISLAMIC_NAME "islamic" +#define ISLAMIC_UMALQURA_NAME "islamic-umalqura" +#define ROC_NAME "roc" + // the function pointer definition for the callback used in EnumCalendarInfo typedef void (PAL_CALLBACK_CALLTYPE *EnumCalendarInfoCallback)(const UChar*, const void*); @@ -96,4 +106,15 @@ PALEXPORT int32_t GlobalizationNative_GetJapaneseEraStartDate(int32_t era, PALEXPORT const char* GlobalizationNative_GetCalendarInfoNative(const char* localeName, CalendarId calendarId, CalendarDataType dataType); + +PALEXPORT int32_t GlobalizationNative_GetCalendarsNative(const char* localeName, + CalendarId* calendars, + int32_t calendarsCapacity); + +PALEXPORT int32_t GlobalizationNative_GetLatestJapaneseEraNative(void); + +PALEXPORT int32_t GlobalizationNative_GetJapaneseEraStartDateNative(int32_t era, + int32_t* startYear, + int32_t* startMonth, + int32_t* startDay); #endif diff --git a/src/native/libs/System.Globalization.Native/pal_calendarData.m b/src/native/libs/System.Globalization.Native/pal_calendarData.m index c17c8f343dd22..f401a03e4afc0 100644 --- a/src/native/libs/System.Globalization.Native/pal_calendarData.m +++ b/src/native/libs/System.Globalization.Native/pal_calendarData.m @@ -50,6 +50,33 @@ return calendarIdentifier; } +/* +Function: +GetCalendarId + +Gets the associated CalendarId for the calendar name. +*/ +static CalendarId GetCalendarId(const char* calendarName) +{ + if (strcasecmp(calendarName, GREGORIAN_NAME) == 0) + return GREGORIAN; + else if (strcasecmp(calendarName, JAPANESE_NAME) == 0) + return JAPAN; + else if (strcasecmp(calendarName, BUDDHIST_NAME) == 0) + return THAI; + else if (strcasecmp(calendarName, HEBREW_NAME) == 0) + return HEBREW; + else if (strcasecmp(calendarName, PERSIAN_NAME) == 0) + return PERSIAN; + else if (strcasecmp(calendarName, ISLAMIC_NAME) == 0) + return HIJRI; + else if (strcasecmp(calendarName, ISLAMIC_UMALQURA_NAME) == 0) + return UMALQURA; + else if (strcasecmp(calendarName, ROC_NAME) == 0) + return TAIWAN; + else + return UNINITIALIZED_VALUE; +} /* Function: GlobalizationNative_GetCalendarInfoNative @@ -79,7 +106,7 @@ NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:calendarIdentifier]; if (dataType == CalendarData_NativeName) - return calendar ? strdup([[calendar calendarIdentifier] UTF8String]) : NULL; + return strdup([[currentLocale localizedStringForCalendarIdentifier:calendarIdentifier] UTF8String]); NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init]; dateFormat.locale = currentLocale; @@ -141,4 +168,122 @@ return arrayToString ? strdup([arrayToString UTF8String]) : NULL; } } + +/* +Function: +GetLatestJapaneseEraNative + +Gets the latest era in the Japanese calendar. +*/ +int32_t GlobalizationNative_GetLatestJapaneseEraNative(void) +{ + @autoreleasepool + { + // Create an NSCalendar with the Japanese calendar identifier + NSCalendar *japaneseCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierJapanese]; + // Get the latest era + NSDateComponents *latestEraComponents = [japaneseCalendar components:NSCalendarUnitEra fromDate:[NSDate date]]; + // Extract the era component + NSInteger latestEra = [latestEraComponents era]; + return (int32_t)latestEra; + } +} + +/* +Function: +GetJapaneseEraStartDateNative + +Gets the starting Gregorian date of the specified Japanese Era. +*/ +int32_t GlobalizationNative_GetJapaneseEraStartDateNative(int32_t era, int32_t* startYear, int32_t* startMonth, int32_t* startDay) +{ + @autoreleasepool + { + NSCalendar *japaneseCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierJapanese]; + NSDateComponents *startDateComponents = [[NSDateComponents alloc] init]; + startDateComponents.era = era; + // set the date to Jan 1, 1 + startDateComponents.month = 1; + startDateComponents.day = 1; + startDateComponents.year = 1; + NSDate *date = [japaneseCalendar dateFromComponents:startDateComponents]; + int32_t currentEra; + + for (int month = 0; month <= 12; month++) + { + NSDateComponents *eraComponents = [japaneseCalendar components:NSCalendarUnitEra fromDate:date]; + currentEra = [eraComponents era]; + if (currentEra == era) + { + for (int day = 0; day < 31; day++) + { + // subtract 1 day at a time until we get out of the specified Era + startDateComponents.day = startDateComponents.day - 1; + date = [japaneseCalendar dateFromComponents:startDateComponents]; + eraComponents = [japaneseCalendar components:NSCalendarUnitEra fromDate:date]; + currentEra = [eraComponents era]; + if (currentEra != era) + { + // add back 1 day to get back into the specified Era + startDateComponents.day = startDateComponents.day + 1; + date = [japaneseCalendar dateFromComponents:startDateComponents]; + NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; + NSDateComponents *components = [gregorianCalendar components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:date]; + *startYear = [components year]; + *startMonth = [components month]; + *startDay = [components day]; + return 1; + } + } + } + // add 1 month at a time until we get into the specified Era + startDateComponents.month = startDateComponents.month + 1; + date = [japaneseCalendar dateFromComponents:startDateComponents]; + eraComponents = [japaneseCalendar components:NSCalendarUnitEra fromDate:date]; + currentEra = [eraComponents era]; + } + + return 0; + } +} + +/* +Function: +GetCalendarsNative + +Returns the list of CalendarIds that are available for the specified locale. +*/ +int32_t GlobalizationNative_GetCalendarsNative(const char* localeName, CalendarId* calendars, int32_t calendarsCapacity) +{ + @autoreleasepool + { + NSArray *calendarIdentifiers = @[ + NSCalendarIdentifierGregorian, + NSCalendarIdentifierBuddhist, + NSCalendarIdentifierHebrew, + NSCalendarIdentifierIslamicUmmAlQura, + NSCalendarIdentifierIslamic, + NSCalendarIdentifierJapanese, + NSCalendarIdentifierPersian, + NSCalendarIdentifierRepublicOfChina, + ]; + + NSString *locName = [NSString stringWithFormat:@"%s", localeName]; + NSLocale *currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:locName]; + NSString *defaultCalendarIdentifier = [currentLocale calendarIdentifier]; + int32_t calendarCount = MIN(calendarIdentifiers.count, calendarsCapacity); + int32_t calendarIndex = 0; + CalendarId defaultCalendarId = GetCalendarId([defaultCalendarIdentifier UTF8String]); + // If the default calendar is not supported, return the Gregorian calendar as the default. + calendars[calendarIndex++] = defaultCalendarId == UNINITIALIZED_VALUE ? GREGORIAN : defaultCalendarId; + for (int i = 0; i < calendarCount; i++) + { + CalendarId calendarId = GetCalendarId([calendarIdentifiers[i] UTF8String]); + if (calendarId == UNINITIALIZED_VALUE || calendarId == defaultCalendarId) + continue; + calendars[calendarIndex++] = calendarId; + } + return calendarCount; + } +} #endif