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 @@ -368,6 +368,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\ISOWeek.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\JapaneseCalendar.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\JapaneseCalendar.Icu.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\JapaneseCalendar.iOS.cs" Condition="'$(IsiOSLike)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\JapaneseCalendar.Nls.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\JapaneseLunisolarCalendar.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\JulianCalendar.cs" />
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 @@ -66,18 +66,25 @@ public partial class JapaneseCalendar : Calendar
// english name, and abbreviated english names.
internal static EraInfo[] GetEraInfo()
{
// See if we need to build it
if (s_japaneseEraInfo == null)
{
#if TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
s_japaneseEraInfo = GlobalizationMode.UseNls ? NlsGetJapaneseEras() : (GlobalizationMode.Hybrid ? GetJapaneseErasNative() : IcuGetJapaneseEras());
#else
s_japaneseEraInfo = GlobalizationMode.UseNls ? NlsGetJapaneseEras() : IcuGetJapaneseEras();
#endif
}

return s_japaneseEraInfo ??
(s_japaneseEraInfo = GlobalizationMode.UseNls ? NlsGetJapaneseEras() : IcuGetJapaneseEras()) ??
// See if we have to use the built-in eras
(s_japaneseEraInfo = new EraInfo[]
new EraInfo[]
{
new EraInfo(5, 2019, 5, 1, 2018, 1, GregorianCalendar.MaxYear - 2018, "\x4ee4\x548c", "\x4ee4", "R"),
new EraInfo(4, 1989, 1, 8, 1988, 1, 2019 - 1988, "\x5e73\x6210", "\x5e73", "H"),
new EraInfo(3, 1926, 12, 25, 1925, 1, 1989 - 1925, "\x662d\x548c", "\x662d", "S"),
new EraInfo(2, 1912, 7, 30, 1911, 1, 1926 - 1911, "\x5927\x6b63", "\x5927", "T"),
new EraInfo(1, 1868, 1, 1, 1867, 1, 1912 - 1867, "\x660e\x6cbb", "\x660e", "M")
});
};
}

internal static volatile Calendar? s_defaultInstance;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;

namespace System.Globalization
{
public partial class JapaneseCalendar : Calendar
{
private static EraInfo[]? GetJapaneseErasNative()
{
if (GlobalizationMode.Invariant)
{
return null;
}

Debug.Assert(!GlobalizationMode.UseNls);

string[]? eraNames;
eraNames = Interop.Globalization.GetCalendarInfoNative("ja-JP", CalendarId.JAPAN, CalendarDataType.EraNames).Split("||");
if (eraNames.Length == 0)
{
return null;
}

List<EraInfo> eras = new List<EraInfo>();
int lastMaxYear = GregorianCalendar.MaxYear;
int latestEra = Interop.Globalization.GetLatestJapaneseEraNative();

for (int i = latestEra; i >= 0; i--)
{
DateTime dt;
if (!GetJapaneseEraStartDateNative(i, out dt))
{
return null;
}

if (dt < s_calendarMinValue)
{
// only populate the Eras that are valid JapaneseCalendar date times
break;
}

eras.Add(new EraInfo(i, dt.Year, dt.Month, dt.Day, dt.Year - 1, 1, lastMaxYear - dt.Year + 1, eraNames![i], GetAbbreviatedEraName(eraNames, i), ""));

lastMaxYear = dt.Year;
}

string[] abbrevEnglishEraNames = Interop.Globalization.GetCalendarInfoNative("ja", CalendarId.JAPAN, CalendarDataType.AbbrevEraNames).Split("||");
if (abbrevEnglishEraNames.Length == 0)
{
// Failed to get English names. fallback to hardcoded data.
abbrevEnglishEraNames = s_abbreviatedEnglishEraNames;
}

// Check if we are getting the English Name at the end of the returned list.
if (abbrevEnglishEraNames[abbrevEnglishEraNames.Length - 1].Length == 0 || abbrevEnglishEraNames[abbrevEnglishEraNames.Length - 1][0] > '\u007F')
{
// Couldn't get English names.
abbrevEnglishEraNames = s_abbreviatedEnglishEraNames;
}

int startIndex = abbrevEnglishEraNames == s_abbreviatedEnglishEraNames ? eras.Count - 1 : abbrevEnglishEraNames.Length - 1;

Debug.Assert(abbrevEnglishEraNames == s_abbreviatedEnglishEraNames || eras.Count <= abbrevEnglishEraNames.Length);

// remap the Era numbers, now that we know how many there will be
for (int i = 0; i < eras.Count; i++)
{
eras[i].era = eras.Count - i;
if (startIndex < abbrevEnglishEraNames.Length)
{
eras[i].englishEraName = abbrevEnglishEraNames[startIndex];
}
startIndex--;
}

return eras.ToArray();
}

private static bool GetJapaneseEraStartDateNative(int era, out DateTime dateTime)
{
dateTime = default;

int startYear;
int startMonth;
int startDay;
bool result = Interop.Globalization.GetJapaneseEraStartDateNative(era, out startYear, out startMonth, out startDay);
if (result)
{
dateTime = new DateTime(startYear, startMonth, startDay);
}

return result;
}
}
}
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: 10 additions & 0 deletions src/native/libs/System.Globalization.Native/pal_calendarData.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,14 @@ 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
159 changes: 158 additions & 1 deletion src/native/libs/System.Globalization.Native/pal_calendarData.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@
#endif

#if defined(TARGET_MACCATALYST) || defined(TARGET_IOS) || defined(TARGET_TVOS)
#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"

/*
Function:
Expand Down Expand Up @@ -50,6 +59,35 @@
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, DANGI_NAME) == 0)
return KOREA;
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 +117,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 +179,123 @@
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];
NSDate *startDate = date;
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;
startDate = [japaneseCalendar dateFromComponents:startDateComponents];
NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *components = [gregorianCalendar components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:startDate];
*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
{
NSString *locName = [NSString stringWithFormat:@"%s", localeName];
NSLocale *currentLocale = [[NSLocale alloc] initWithLocaleIdentifier:locName];
NSArray *calendarIdentifiers = @[
NSCalendarIdentifierGregorian,
NSCalendarIdentifierBuddhist,
NSCalendarIdentifierChinese,
NSCalendarIdentifierCoptic,
NSCalendarIdentifierEthiopicAmeteMihret,
NSCalendarIdentifierEthiopicAmeteAlem,
NSCalendarIdentifierHebrew,
NSCalendarIdentifierISO8601,
NSCalendarIdentifierIndian,
NSCalendarIdentifierIslamicCivil,
NSCalendarIdentifierIslamicTabular,
NSCalendarIdentifierIslamicUmmAlQura,
NSCalendarIdentifierIslamic,
NSCalendarIdentifierJapanese,
NSCalendarIdentifierPersian,
NSCalendarIdentifierRepublicOfChina,
];
int32_t calendarCount = MIN(calendarIdentifiers.count, calendarsCapacity);
for (int i = 0; i < calendarCount; i++)
{
NSString *calendarIdentifier = calendarIdentifiers[i];
calendars[i] = GetCalendarId([calendarIdentifier UTF8String]);
}
return calendarCount;
}
}
#endif