diff --git a/src/libraries/Common/src/Interop/Interop.TimeZoneInfo.cs b/src/libraries/Common/src/Interop/Interop.TimeZoneInfo.cs index d1ab917229f21c..faa7e4f3a90738 100644 --- a/src/libraries/Common/src/Interop/Interop.TimeZoneInfo.cs +++ b/src/libraries/Common/src/Interop/Interop.TimeZoneInfo.cs @@ -14,5 +14,11 @@ internal static extern unsafe ResultCode GetTimeZoneDisplayName( TimeZoneDisplayNameType type, char* result, int resultLength); + + [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_WindowsIdToIanaId")] + internal static extern unsafe int WindowsIdToIanaId(string windowsId, char* ianaId, int ianaIdLength); + + [DllImport(Libraries.GlobalizationNative, CharSet = CharSet.Unicode, EntryPoint = "GlobalizationNative_IanaIdToWindowsId")] + internal static extern unsafe int IanaIdToWindowsId(string ianaId, char* windowsId, int windowsIdLength); } } diff --git a/src/libraries/Native/Unix/System.Globalization.Native/entrypoints.c b/src/libraries/Native/Unix/System.Globalization.Native/entrypoints.c index 85a39085270b46..fced139e21958e 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/entrypoints.c +++ b/src/libraries/Native/Unix/System.Globalization.Native/entrypoints.c @@ -42,6 +42,7 @@ static const Entry s_globalizationNative[] = DllImportEntry(GlobalizationNative_GetSortKey) DllImportEntry(GlobalizationNative_GetSortVersion) DllImportEntry(GlobalizationNative_GetTimeZoneDisplayName) + DllImportEntry(GlobalizationNative_IanaIdToWindowsId) DllImportEntry(GlobalizationNative_IndexOf) DllImportEntry(GlobalizationNative_InitICUFunctions) DllImportEntry(GlobalizationNative_InitOrdinalCasingPage) @@ -53,6 +54,7 @@ static const Entry s_globalizationNative[] = DllImportEntry(GlobalizationNative_StartsWith) DllImportEntry(GlobalizationNative_ToAscii) DllImportEntry(GlobalizationNative_ToUnicode) + DllImportEntry(GlobalizationNative_WindowsIdToIanaId) }; EXTERN_C const void* GlobalizationResolveDllImport(const char* name); diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.c b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.c index 99a7899b848358..4f71b66c2678f8 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.c +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.c @@ -20,7 +20,7 @@ #include "pal_icushim.h" // Define pointers to all the used ICU functions -#define PER_FUNCTION_BLOCK(fn, lib) TYPEOF(fn)* fn##_ptr; +#define PER_FUNCTION_BLOCK(fn, lib, required) TYPEOF(fn)* fn##_ptr; FOR_ALL_ICU_FUNCTIONS #undef PER_FUNCTION_BLOCK @@ -41,14 +41,14 @@ static void* libicui18n = NULL; #if defined (TARGET_UNIX) -#define PER_FUNCTION_BLOCK(fn, lib) \ +#define PER_FUNCTION_BLOCK(fn, lib, required) \ c_static_assert_msg((sizeof(#fn) + MaxICUVersionStringWithSuffixLength + 1) <= sizeof(symbolName), "The symbolName is too small for symbol " #fn); \ sprintf(symbolName, #fn "%s", symbolVersion); \ fn##_ptr = (TYPEOF(fn)*)dlsym(lib, symbolName); \ - if (fn##_ptr == NULL) { fprintf(stderr, "Cannot get symbol %s from " #lib "\nError: %s\n", symbolName, dlerror()); abort(); } + if (fn##_ptr == NULL && required) { fprintf(stderr, "Cannot get symbol %s from " #lib "\nError: %s\n", symbolName, dlerror()); abort(); } static int FindSymbolVersion(int majorVer, int minorVer, int subVer, char* symbolName, char* symbolVersion, char* suffix) -{ +{ // Find out the format of the version string added to each symbol // First try just the unversioned symbol if (dlsym(libicuuc, "u_strlen") == NULL) @@ -89,10 +89,10 @@ static int FindSymbolVersion(int majorVer, int minorVer, int subVer, char* symbo #define sscanf sscanf_s -#define PER_FUNCTION_BLOCK(fn, lib) \ +#define PER_FUNCTION_BLOCK(fn, lib, required) \ sprintf_s(symbolName, SYMBOL_NAME_SIZE, #fn "%s", symbolVersion); \ fn##_ptr = (TYPEOF(fn)*)GetProcAddress((HMODULE)lib, symbolName); \ - if (fn##_ptr == NULL) { fprintf(stderr, "Cannot get symbol %s from " #lib "\nError: %u\n", symbolName, GetLastError()); abort(); } + if (fn##_ptr == NULL && required) { fprintf(stderr, "Cannot get symbol %s from " #lib "\nError: %u\n", symbolName, GetLastError()); abort(); } static int FindICULibs() { @@ -421,7 +421,7 @@ void GlobalizationNative_InitICUFunctions(void* icuuc, void* icuin, const char* assert(icuuc != NULL); assert(icuin != NULL); assert(version != NULL); - + libicuuc = icuuc; libicui18n = icuin; int major = -1; @@ -476,7 +476,7 @@ int32_t GlobalizationNative_GetICUVersion() { if (u_getVersion_ptr == NULL) return 0; - + UVersionInfo versionInfo; u_getVersion(versionInfo); diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal.h b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal.h index 081518b753deb5..836ba44ec182fd 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal.h +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal.h @@ -53,132 +53,148 @@ #include "pal_compiler.h" #if !defined(STATIC_ICU) + +#if !defined(TARGET_ANDROID) +// (U_ICU_VERSION_MAJOR_NUM < 52) +// The following APIs are not supported in the ICU versions less than 52. We need to define them manually. +// We have to do runtime check before using the pointers to these APIs. That is why these are listed in the FOR_ALL_OPTIONAL_ICU_FUNCTIONS list. +U_CAPI int32_t U_EXPORT2 ucal_getWindowsTimeZoneID(const UChar* id, int32_t len,UChar* winid, int32_t winidCapacity, UErrorCode* status); +U_CAPI int32_t U_EXPORT2 ucal_getTimeZoneIDForWindowsID(const UChar* winid, int32_t len, const char* region, UChar* id, int32_t idCapacity, UErrorCode* status); +#endif + // List of all functions from the ICU libraries that are used in the System.Globalization.Native.so #define FOR_ALL_UNCONDITIONAL_ICU_FUNCTIONS \ - PER_FUNCTION_BLOCK(u_charsToUChars, libicuuc) \ - PER_FUNCTION_BLOCK(u_getVersion, libicuuc) \ - PER_FUNCTION_BLOCK(u_strlen, libicuuc) \ - PER_FUNCTION_BLOCK(u_strncpy, libicuuc) \ - PER_FUNCTION_BLOCK(u_tolower, libicuuc) \ - PER_FUNCTION_BLOCK(u_toupper, libicuuc) \ - PER_FUNCTION_BLOCK(ucal_add, libicui18n) \ - PER_FUNCTION_BLOCK(ucal_close, libicui18n) \ - PER_FUNCTION_BLOCK(ucal_get, libicui18n) \ - PER_FUNCTION_BLOCK(ucal_getAttribute, libicui18n) \ - PER_FUNCTION_BLOCK(ucal_getKeywordValuesForLocale, libicui18n) \ - PER_FUNCTION_BLOCK(ucal_getLimit, libicui18n) \ - PER_FUNCTION_BLOCK(ucal_getTimeZoneDisplayName, libicui18n) \ - PER_FUNCTION_BLOCK(ucal_open, libicui18n) \ - PER_FUNCTION_BLOCK(ucal_set, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_close, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_closeElements, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_getOffset, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_getRules, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_getSortKey, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_getStrength, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_getVersion, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_next, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_previous, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_open, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_openElements, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_openRules, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_safeClone, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_setAttribute, libicui18n) \ - PER_FUNCTION_BLOCK(ucol_strcoll, libicui18n) \ - PER_FUNCTION_BLOCK(udat_close, libicui18n) \ - PER_FUNCTION_BLOCK(udat_countSymbols, libicui18n) \ - PER_FUNCTION_BLOCK(udat_getSymbols, libicui18n) \ - PER_FUNCTION_BLOCK(udat_open, libicui18n) \ - PER_FUNCTION_BLOCK(udat_setCalendar, libicui18n) \ - PER_FUNCTION_BLOCK(udat_toPattern, libicui18n) \ - PER_FUNCTION_BLOCK(udatpg_close, libicui18n) \ - PER_FUNCTION_BLOCK(udatpg_getBestPattern, libicui18n) \ - PER_FUNCTION_BLOCK(udatpg_open, libicui18n) \ - PER_FUNCTION_BLOCK(uenum_close, libicuuc) \ - PER_FUNCTION_BLOCK(uenum_count, libicuuc) \ - PER_FUNCTION_BLOCK(uenum_next, libicuuc) \ - PER_FUNCTION_BLOCK(uidna_close, libicuuc) \ - PER_FUNCTION_BLOCK(uidna_nameToASCII, libicuuc) \ - PER_FUNCTION_BLOCK(uidna_nameToUnicode, libicuuc) \ - PER_FUNCTION_BLOCK(uidna_openUTS46, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_canonicalize, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_countAvailable, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getAvailable, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getBaseName, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getCharacterOrientation, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getCountry, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getDefault, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getDisplayCountry, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getDisplayLanguage, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getDisplayName, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getISO3Country, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getISO3Language, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getKeywordValue, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getLanguage, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getLCID, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getName, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_getParent, libicuuc) \ - PER_FUNCTION_BLOCK(uloc_setKeywordValue, libicuuc) \ - PER_FUNCTION_BLOCK(ulocdata_getCLDRVersion, libicui18n) \ - PER_FUNCTION_BLOCK(ulocdata_getMeasurementSystem, libicui18n) \ - PER_FUNCTION_BLOCK(unorm2_getNFCInstance, libicuuc) \ - PER_FUNCTION_BLOCK(unorm2_getNFDInstance, libicuuc) \ - PER_FUNCTION_BLOCK(unorm2_getNFKCInstance, libicuuc) \ - PER_FUNCTION_BLOCK(unorm2_getNFKDInstance, libicuuc) \ - PER_FUNCTION_BLOCK(unorm2_isNormalized, libicuuc) \ - PER_FUNCTION_BLOCK(unorm2_normalize, libicuuc) \ - PER_FUNCTION_BLOCK(unum_close, libicui18n) \ - PER_FUNCTION_BLOCK(unum_getAttribute, libicui18n) \ - PER_FUNCTION_BLOCK(unum_getSymbol, libicui18n) \ - PER_FUNCTION_BLOCK(unum_open, libicui18n) \ - PER_FUNCTION_BLOCK(unum_toPattern, libicui18n) \ - PER_FUNCTION_BLOCK(ures_close, libicuuc) \ - PER_FUNCTION_BLOCK(ures_getByKey, libicuuc) \ - PER_FUNCTION_BLOCK(ures_getSize, libicuuc) \ - PER_FUNCTION_BLOCK(ures_getStringByIndex, libicuuc) \ - PER_FUNCTION_BLOCK(ures_open, libicuuc) \ - PER_FUNCTION_BLOCK(usearch_close, libicui18n) \ - PER_FUNCTION_BLOCK(usearch_first, libicui18n) \ - PER_FUNCTION_BLOCK(usearch_getMatchedLength, libicui18n) \ - PER_FUNCTION_BLOCK(usearch_last, libicui18n) \ - PER_FUNCTION_BLOCK(usearch_openFromCollator, libicui18n) \ - PER_FUNCTION_BLOCK(usearch_setPattern, libicui18n) \ - PER_FUNCTION_BLOCK(usearch_setText, libicui18n) + PER_FUNCTION_BLOCK(u_charsToUChars, libicuuc, true) \ + PER_FUNCTION_BLOCK(u_getVersion, libicuuc, true) \ + PER_FUNCTION_BLOCK(u_strlen, libicuuc, true) \ + PER_FUNCTION_BLOCK(u_strncpy, libicuuc, true) \ + PER_FUNCTION_BLOCK(u_tolower, libicuuc, true) \ + PER_FUNCTION_BLOCK(u_toupper, libicuuc, true) \ + PER_FUNCTION_BLOCK(ucal_add, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucal_close, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucal_get, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucal_getAttribute, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucal_getKeywordValuesForLocale, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucal_getLimit, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucal_getTimeZoneDisplayName, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucal_open, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucal_set, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_close, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_closeElements, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_getOffset, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_getRules, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_getSortKey, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_getStrength, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_getVersion, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_next, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_previous, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_open, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_openElements, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_openRules, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_safeClone, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_setAttribute, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucol_strcoll, libicui18n, true) \ + PER_FUNCTION_BLOCK(udat_close, libicui18n, true) \ + PER_FUNCTION_BLOCK(udat_countSymbols, libicui18n, true) \ + PER_FUNCTION_BLOCK(udat_getSymbols, libicui18n, true) \ + PER_FUNCTION_BLOCK(udat_open, libicui18n, true) \ + PER_FUNCTION_BLOCK(udat_setCalendar, libicui18n, true) \ + PER_FUNCTION_BLOCK(udat_toPattern, libicui18n, true) \ + PER_FUNCTION_BLOCK(udatpg_close, libicui18n, true) \ + PER_FUNCTION_BLOCK(udatpg_getBestPattern, libicui18n, true) \ + PER_FUNCTION_BLOCK(udatpg_open, libicui18n, true) \ + PER_FUNCTION_BLOCK(uenum_close, libicuuc, true) \ + PER_FUNCTION_BLOCK(uenum_count, libicuuc, true) \ + PER_FUNCTION_BLOCK(uenum_next, libicuuc, true) \ + PER_FUNCTION_BLOCK(uidna_close, libicuuc, true) \ + PER_FUNCTION_BLOCK(uidna_nameToASCII, libicuuc, true) \ + PER_FUNCTION_BLOCK(uidna_nameToUnicode, libicuuc, true) \ + PER_FUNCTION_BLOCK(uidna_openUTS46, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_canonicalize, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_countAvailable, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getAvailable, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getBaseName, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getCharacterOrientation, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getCountry, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getDefault, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getDisplayCountry, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getDisplayLanguage, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getDisplayName, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getISO3Country, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getISO3Language, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getKeywordValue, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getLanguage, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getLCID, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getName, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_getParent, libicuuc, true) \ + PER_FUNCTION_BLOCK(uloc_setKeywordValue, libicuuc, true) \ + PER_FUNCTION_BLOCK(ulocdata_getCLDRVersion, libicui18n, true) \ + PER_FUNCTION_BLOCK(ulocdata_getMeasurementSystem, libicui18n, true) \ + PER_FUNCTION_BLOCK(unorm2_getNFCInstance, libicuuc, true) \ + PER_FUNCTION_BLOCK(unorm2_getNFDInstance, libicuuc, true) \ + PER_FUNCTION_BLOCK(unorm2_getNFKCInstance, libicuuc, true) \ + PER_FUNCTION_BLOCK(unorm2_getNFKDInstance, libicuuc, true) \ + PER_FUNCTION_BLOCK(unorm2_isNormalized, libicuuc, true) \ + PER_FUNCTION_BLOCK(unorm2_normalize, libicuuc, true) \ + PER_FUNCTION_BLOCK(unum_close, libicui18n, true) \ + PER_FUNCTION_BLOCK(unum_getAttribute, libicui18n, true) \ + PER_FUNCTION_BLOCK(unum_getSymbol, libicui18n, true) \ + PER_FUNCTION_BLOCK(unum_open, libicui18n, true) \ + PER_FUNCTION_BLOCK(unum_toPattern, libicui18n, true) \ + PER_FUNCTION_BLOCK(ures_close, libicuuc, true) \ + PER_FUNCTION_BLOCK(ures_getByKey, libicuuc, true) \ + PER_FUNCTION_BLOCK(ures_getSize, libicuuc, true) \ + PER_FUNCTION_BLOCK(ures_getStringByIndex, libicuuc, true) \ + PER_FUNCTION_BLOCK(ures_open, libicuuc, true) \ + PER_FUNCTION_BLOCK(usearch_close, libicui18n, true) \ + PER_FUNCTION_BLOCK(usearch_first, libicui18n, true) \ + PER_FUNCTION_BLOCK(usearch_getMatchedLength, libicui18n, true) \ + PER_FUNCTION_BLOCK(usearch_last, libicui18n, true) \ + PER_FUNCTION_BLOCK(usearch_openFromCollator, libicui18n, true) \ + PER_FUNCTION_BLOCK(usearch_setPattern, libicui18n, true) \ + PER_FUNCTION_BLOCK(usearch_setText, libicui18n, true) #if HAVE_SET_MAX_VARIABLE #define FOR_ALL_SET_VARIABLE_ICU_FUNCTIONS \ - PER_FUNCTION_BLOCK(ucol_setMaxVariable, libicui18n) + PER_FUNCTION_BLOCK(ucol_setMaxVariable, libicui18n, true) #else #define FOR_ALL_SET_VARIABLE_ICU_FUNCTIONS \ - PER_FUNCTION_BLOCK(ucol_setVariableTop, libicui18n) + PER_FUNCTION_BLOCK(ucol_setVariableTop, libicui18n, true) #endif #if defined(TARGET_WINDOWS) #define FOR_ALL_OS_CONDITIONAL_ICU_FUNCTIONS \ - PER_FUNCTION_BLOCK(ucurr_forLocale, libicuuc) \ - PER_FUNCTION_BLOCK(ucurr_getName, libicuuc) \ - PER_FUNCTION_BLOCK(uldn_close, libicuuc) \ - PER_FUNCTION_BLOCK(uldn_keyValueDisplayName, libicuuc) \ - PER_FUNCTION_BLOCK(uldn_open, libicuuc) + PER_FUNCTION_BLOCK(ucurr_forLocale, libicuuc, true) \ + PER_FUNCTION_BLOCK(ucurr_getName, libicuuc, true) \ + PER_FUNCTION_BLOCK(uldn_close, libicuuc, true) \ + PER_FUNCTION_BLOCK(uldn_keyValueDisplayName, libicuuc, true) \ + PER_FUNCTION_BLOCK(uldn_open, libicuuc, true) #else // Unix ICU is dynamically resolved at runtime and these APIs in old versions // of ICU were in libicui18n #define FOR_ALL_OS_CONDITIONAL_ICU_FUNCTIONS \ - PER_FUNCTION_BLOCK(ucurr_forLocale, libicui18n) \ - PER_FUNCTION_BLOCK(ucurr_getName, libicui18n) \ - PER_FUNCTION_BLOCK(uldn_close, libicui18n) \ - PER_FUNCTION_BLOCK(uldn_keyValueDisplayName, libicui18n) \ - PER_FUNCTION_BLOCK(uldn_open, libicui18n) + PER_FUNCTION_BLOCK(ucurr_forLocale, libicui18n, true) \ + PER_FUNCTION_BLOCK(ucurr_getName, libicui18n, true) \ + PER_FUNCTION_BLOCK(uldn_close, libicui18n, true) \ + PER_FUNCTION_BLOCK(uldn_keyValueDisplayName, libicui18n, true) \ + PER_FUNCTION_BLOCK(uldn_open, libicui18n, true) #endif +// The following are the list of the ICU APIs which are optional. If these APIs exist in the ICU version we load at runtime, then we'll use it. +// Otherwise, we'll just not provide the functionality to users which needed these APIs. +#define FOR_ALL_OPTIONAL_ICU_FUNCTIONS \ + PER_FUNCTION_BLOCK(ucal_getWindowsTimeZoneID, libicui18n, false) \ + PER_FUNCTION_BLOCK(ucal_getTimeZoneIDForWindowsID, libicui18n, false) + #define FOR_ALL_ICU_FUNCTIONS \ FOR_ALL_UNCONDITIONAL_ICU_FUNCTIONS \ FOR_ALL_SET_VARIABLE_ICU_FUNCTIONS \ + FOR_ALL_OPTIONAL_ICU_FUNCTIONS \ FOR_ALL_OS_CONDITIONAL_ICU_FUNCTIONS // Declare pointers to all the used ICU functions -#define PER_FUNCTION_BLOCK(fn, lib) EXTERN_C TYPEOF(fn)* fn##_ptr; +#define PER_FUNCTION_BLOCK(fn, lib, required) EXTERN_C TYPEOF(fn)* fn##_ptr; FOR_ALL_ICU_FUNCTIONS #undef PER_FUNCTION_BLOCK @@ -197,6 +213,8 @@ FOR_ALL_ICU_FUNCTIONS #define ucal_getKeywordValuesForLocale(...) ucal_getKeywordValuesForLocale_ptr(__VA_ARGS__) #define ucal_getLimit(...) ucal_getLimit_ptr(__VA_ARGS__) #define ucal_getTimeZoneDisplayName(...) ucal_getTimeZoneDisplayName_ptr(__VA_ARGS__) +#define ucal_getTimeZoneIDForWindowsID(...) ucal_getTimeZoneIDForWindowsID_ptr(__VA_ARGS__) +#define ucal_getWindowsTimeZoneID(...) ucal_getWindowsTimeZoneID_ptr(__VA_ARGS__) #define ucal_open(...) ucal_open_ptr(__VA_ARGS__) #define ucal_set(...) ucal_set_ptr(__VA_ARGS__) #define ucol_close(...) ucol_close_ptr(__VA_ARGS__) @@ -285,4 +303,9 @@ FOR_ALL_ICU_FUNCTIONS #define usearch_setPattern(...) usearch_setPattern_ptr(__VA_ARGS__) #define usearch_setText(...) usearch_setText_ptr(__VA_ARGS__) +#else // !defined(STATIC_ICU) + +#define ucal_getWindowsTimeZoneID_ptr ucal_getWindowsTimeZoneID +#define ucal_getTimeZoneIDForWindowsID_ptr ucal_getTimeZoneIDForWindowsID + #endif // !defined(STATIC_ICU) diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal_android.h b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal_android.h index 2b1ed0929ff4f8..7e05fa35d03ca8 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal_android.h +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim_internal_android.h @@ -436,6 +436,8 @@ int32_t ucal_getLimit(const UCalendar * cal, UCalendarDateFields field, UCalenda int32_t ucal_getTimeZoneDisplayName(const UCalendar * cal, UCalendarDisplayNameType type, const char * locale, UChar * result, int32_t resultLength, UErrorCode * status); UCalendar * ucal_open(const UChar * zoneID, int32_t len, const char * locale, UCalendarType type, UErrorCode * status); void ucal_set(UCalendar * cal, UCalendarDateFields field, int32_t value); +int32_t ucal_getTimeZoneIDForWindowsID(const UChar * winid, int32_t len, const char * region, UChar * id, int32_t idCapacity, UErrorCode * status); +int32_t ucal_getWindowsTimeZoneID(const UChar * id, int32_t len, UChar * winid, int32_t winidCapacity, UErrorCode * status); void ucol_close(UCollator * coll); void ucol_closeElements(UCollationElements * elems); int32_t ucol_getOffset(const UCollationElements *elems); diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_timeZoneInfo.c b/src/libraries/Native/Unix/System.Globalization.Native/pal_timeZoneInfo.c index ce865cdf0c1ae9..09c2c1ca5092bc 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_timeZoneInfo.c +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_timeZoneInfo.c @@ -39,3 +39,44 @@ ResultCode GlobalizationNative_GetTimeZoneDisplayName(const UChar* localeName, ucal_close(calendar); return GetResultCode(err); } + +/* +Convert Windows Time Zone Id to IANA Id +*/ +int32_t GlobalizationNative_WindowsIdToIanaId(const UChar* windowsId, UChar* ianaId, int32_t ianaIdLength) +{ + UErrorCode status = U_ZERO_ERROR; + + if (ucal_getTimeZoneIDForWindowsID_ptr != NULL) + { + int32_t ianaIdFilledLength = ucal_getTimeZoneIDForWindowsID(windowsId, -1, NULL, ianaId, ianaIdLength, &status); + if (U_SUCCESS(status)) + { + return ianaIdFilledLength; + } + } + + // Failed + return 0; +} + +/* +Convert IANA Time Zone Id to Windows Id +*/ +int32_t GlobalizationNative_IanaIdToWindowsId(const UChar* ianaId, UChar* windowsId, int32_t windowsIdLength) +{ + UErrorCode status = U_ZERO_ERROR; + + if (ucal_getWindowsTimeZoneID_ptr != NULL) + { + int32_t windowsIdFilledLength = ucal_getWindowsTimeZoneID(ianaId, -1, windowsId, windowsIdLength, &status); + + if (U_SUCCESS(status)) + { + return windowsIdFilledLength; + } + } + + // Failed + return 0; +} diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_timeZoneInfo.h b/src/libraries/Native/Unix/System.Globalization.Native/pal_timeZoneInfo.h index 1c5369e9bfb176..c9fe188979c11f 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_timeZoneInfo.h +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_timeZoneInfo.h @@ -23,3 +23,6 @@ PALEXPORT ResultCode GlobalizationNative_GetTimeZoneDisplayName(const UChar* loc TimeZoneDisplayNameType type, UChar* result, int32_t resultLength); + +PALEXPORT int32_t GlobalizationNative_WindowsIdToIanaId(const UChar* windowsId, UChar* ianaId, int32_t ianaIdLength); +PALEXPORT int32_t GlobalizationNative_IanaIdToWindowsId(const UChar* ianaId, UChar* windowsId, int32_t windowsIdLength); diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs index 30d4ca75718cbd..fd2d2556527c6d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Unix.cs @@ -164,6 +164,36 @@ private static void PopulateAllSystemTimeZones(CachedData cachedData) } } + private static unsafe string? GetAlternativeId(string id) + { + if (!GlobalizationMode.Invariant) + { + if (id.Equals("utc", StringComparison.OrdinalIgnoreCase)) + { + //special case UTC as ICU will convert it to "Etc/GMT" which is incorrect name for UTC. + return "Etc/UTC"; + } + foreach (char c in id) + { + // ICU uses some characters as a separator and trim the id at that character. + // while we should fail if the Id contained one of these characters. + if (c == '\\' || c == '\n' || c == '\r') + { + return null; + } + } + + char* buffer = stackalloc char[100]; + int length = Interop.Globalization.WindowsIdToIanaId(id, buffer, 100); + if (length > 0) + { + return new string(buffer, 0, length); + } + } + + return null; + } + /// /// Helper function for retrieving the local system time zone. /// May throw COMException, TimeZoneNotFoundException, InvalidTimeZoneException. diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs index 93195932f052fc..6223e66e018ae1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.Win32.cs @@ -105,6 +105,31 @@ private static void PopulateAllSystemTimeZones(CachedData cachedData) } } + private static unsafe string? GetAlternativeId(string id) + { + if (!GlobalizationMode.Invariant && !GlobalizationMode.UseNls) + { + foreach (char c in id) + { + // ICU uses some characters as a separator and trim the id at that character. + // while we should fail if the Id contained one of these characters. + if (c == '\\' || c == '\n' || c == '\r') + { + return null; + } + } + + char* buffer = stackalloc char[100]; + int length = Interop.Globalization.IanaIdToWindowsId(id, buffer, 100); + if (length > 0) + { + return new string(buffer, 0, length); + } + } + + return null; + } + private TimeZoneInfo(in TIME_ZONE_INFORMATION zone, bool dstDisabled) { string standardName = zone.GetStandardName(); diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs index 2669b740835cae..771cef56a3a8cb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs @@ -46,6 +46,8 @@ private enum TimeZoneInfoResult private readonly TimeSpan _baseUtcOffset; private readonly bool _supportsDaylightSavingTime; private readonly AdjustmentRule[]? _adjustmentRules; + // As we support IANA and Windows Ids, it is possible we create equivalent zone objects which differ only in the Ids. + private List? _equivalentZones; // constants for TimeZoneInfo.Local and TimeZoneInfo.Utc private const string UtcId = "UTC"; @@ -1788,6 +1790,58 @@ internal static DateTime TransitionTimeToDateTime(int year, TransitionTime trans /// assumes cachedData lock is taken /// private static TimeZoneInfoResult TryGetTimeZone(string id, bool dstDisabled, out TimeZoneInfo? value, out Exception? e, CachedData cachedData, bool alwaysFallbackToLocalMachine = false) + { + TimeZoneInfoResult result = TryGetTimeZoneUsingId(id, dstDisabled, out value, out e, cachedData, alwaysFallbackToLocalMachine); + if (result != TimeZoneInfoResult.Success) + { + string? alternativeId = GetAlternativeId(id); + if (alternativeId != null) + { + result = TryGetTimeZoneUsingId(alternativeId, dstDisabled, out value, out e, cachedData, alwaysFallbackToLocalMachine); + if (result == TimeZoneInfoResult.Success) + { + TimeZoneInfo? zone = null; + if (value!._equivalentZones == null) + { + zone = new TimeZoneInfo(id, value!._baseUtcOffset, value!._displayName, value!._standardDisplayName, + value!._daylightDisplayName, value!._adjustmentRules, dstDisabled && value!._supportsDaylightSavingTime); + value!._equivalentZones = new List(); + lock (value!._equivalentZones) + { + value!._equivalentZones.Add(zone); + } + } + else + { + foreach (TimeZoneInfo tzi in value!._equivalentZones) + { + if (tzi.Id == id) + { + zone = tzi; + break; + } + } + if (zone == null) + { + zone = new TimeZoneInfo(id, value!._baseUtcOffset, value!._displayName, value!._standardDisplayName, + value!._daylightDisplayName, value!._adjustmentRules, dstDisabled && value!._supportsDaylightSavingTime); + lock (value!._equivalentZones) + { + value!._equivalentZones.Add(zone); + } + } + } + + Debug.Assert(zone != null); + value = zone; + } + } + } + + return result; + } + + private static TimeZoneInfoResult TryGetTimeZoneUsingId(string id, bool dstDisabled, out TimeZoneInfo? value, out Exception? e, CachedData cachedData, bool alwaysFallbackToLocalMachine) { Debug.Assert(Monitor.IsEntered(cachedData)); diff --git a/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs b/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs index 82ed2ed7c124be..61c6032d0a489c 100644 --- a/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System/TimeZoneInfoTests.cs @@ -106,7 +106,7 @@ public static IEnumerable Platform_TimeZoneNamesTestData() { s_NewfoundlandTz, "(UTC-03:30) NST", "NST", "NDT" }, { s_catamarcaTz, "(UTC-03:00) -03", "-03", "-02" } - }; + }; } [Theory] @@ -2341,6 +2341,39 @@ public static void EnsureUtcObjectSingleton() Assert.True(ReferenceEquals(TimeZoneInfo.FindSystemTimeZoneById("UTC"), TimeZoneInfo.Utc)); } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [InlineData("Pacific Standard Time", "America/Los_Angeles")] + [InlineData("AUS Eastern Standard Time", "Australia/Sydney")] + [InlineData("GMT Standard Time", "Europe/London")] + [InlineData("Tonga Standard Time", "Pacific/Tongatapu")] + [InlineData("W. Australia Standard Time", "Australia/Perth")] + [InlineData("E. South America Standard Time", "America/Sao_Paulo")] + [InlineData("E. Africa Standard Time", "Africa/Nairobi")] + [InlineData("W. Europe Standard Time", "Europe/Berlin")] + [InlineData("Russian Standard Time", "Europe/Moscow")] + [InlineData("Libya Standard Time", "Africa/Tripoli")] + [InlineData("South Africa Standard Time", "Africa/Johannesburg")] + [InlineData("Morocco Standard Time", "Africa/Casablanca")] + [InlineData("Argentina Standard Time", "America/Argentina/Catamarca")] + [InlineData("Newfoundland Standard Time", "America/St_Johns")] + [InlineData("Iran Standard Time", "Asia/Tehran")] + public static void UsingAlternativeTimeZoneIdsTest(string windowsId, string ianaId) + { + if (PlatformDetection.ICUVersion.Major >= 52) + { + TimeZoneInfo tzi1 = TimeZoneInfo.FindSystemTimeZoneById(ianaId); + TimeZoneInfo tzi2 = TimeZoneInfo.FindSystemTimeZoneById(windowsId); + + Assert.Equal(tzi1.BaseUtcOffset, tzi2.BaseUtcOffset); + Assert.NotEqual(tzi1.Id, tzi2.Id); + } + else + { + Assert.Throws(() => TimeZoneInfo.FindSystemTimeZoneById(s_isWindows ? ianaId : windowsId)); + TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById(s_isWindows ? windowsId : ianaId); + } + } + // We test the existence of a specific English time zone name to avoid failures on non-English platforms. [ConditionalFact(nameof(IsEnglishUILanguageAndRemoteExecutorSupported))] public static void TestNameWithInvariantCulture() @@ -2387,7 +2420,7 @@ public static void TestTimeZoneIdBackwardCompatibility(string oldId, string curr [InlineData("America/Jujuy")] [InlineData("America/Mendoza")] [InlineData("America/Indianapolis")] - public static void ChangeLocalTimeZone(string id) + public static void ChangeLocalTimeZone(string id) { string originalTZ = Environment.GetEnvironmentVariable("TZ"); try { @@ -2398,7 +2431,7 @@ public static void ChangeLocalTimeZone(string id) TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById(id); Assert.Equal(tz.StandardName, localtz.StandardName); - Assert.Equal(tz.DisplayName, localtz.DisplayName); + Assert.Equal(tz.DisplayName, localtz.DisplayName); } finally { TimeZoneInfo.ClearCachedData();