Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS][non-icu] HybridGlobalization implement japanese calendar data #92471

Merged
merged 12 commits into from
Oct 9, 2023
11 changes: 11 additions & 0 deletions src/libraries/Common/src/Interop/Interop.Calendar.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<EraInfo> eras = new List<EraInfo>();
int lastMaxYear = GregorianCalendar.MaxYear;

int latestEra = Interop.Globalization.GetLatestJapaneseEra();

for (int i = latestEra; i >= 0; i--)
{
DateTime dt;
Expand All @@ -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.
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions src/native/libs/System.Globalization.Native/entrypoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 0 additions & 10 deletions src/native/libs/System.Globalization.Native/pal_calendarData.c
Original file line number Diff line number Diff line change
Expand Up @@ -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'};
Expand Down
21 changes: 21 additions & 0 deletions src/native/libs/System.Globalization.Native/pal_calendarData.h
Original file line number Diff line number Diff line change
Expand Up @@ -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*);

Expand Down Expand Up @@ -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,
mkhamoyan marked this conversation as resolved.
Show resolved Hide resolved
int32_t* startYear,
int32_t* startMonth,
int32_t* startDay);
#endif
147 changes: 146 additions & 1 deletion src/native/libs/System.Globalization.Native/pal_calendarData.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
mdh1418 marked this conversation as resolved.
Show resolved Hide resolved
{
for (int day = 0; day < 31; day++)
{
// subtract 1 day at a time until we get out of the specified Era
ilonatommy marked this conversation as resolved.
Show resolved Hide resolved
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
Loading