diff --git a/build/azure-nuget.yml b/build/azure-nuget.yml index 707f1536705..5477b3b24fd 100644 --- a/build/azure-nuget.yml +++ b/build/azure-nuget.yml @@ -15,7 +15,7 @@ stages: - job: BuildLinux pool: name: Azure Pipelines - vmImage: 'ubuntu-18.04' + vmImage: 'ubuntu-latest' workspace: clean: all diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 7ebe2c50f79..172e91b31ab 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -1,21 +1,16 @@ jobs: #------------------------------------------------------------------------- -- job: ICU4C_MakeDist_Clang_Ubuntu_1804 - displayName: 'C: MakeDist Linux Clang (Ubuntu 18.04)' +- job: ICU4C_MakeDist_Clang_Ubuntu_2204 + displayName: 'C: MakeDist Linux Clang (Ubuntu 22.04)' timeoutInMinutes: 30 workspace: clean: all pool: - vmImage: 'ubuntu-18.04' + vmImage: 'ubuntu-22.04' steps: - checkout: self lfs: true fetchDepth: 1 - # This is to work-around issue: https://github.com/actions/virtual-environments/issues/3376 - # Once the Ubuntu 18.04 build bot image is fixed, we can remove this work-around. - - script: | - sudo apt remove libgcc-11-dev gcc-11 - displayName: Remove GCC 11 (work-around) - task: PowerShell@2 displayName: 'Set ICU Version' inputs: @@ -51,13 +46,13 @@ jobs: displayName: 'Build and Test MakeDist using source tarball' #------------------------------------------------------------------------- -- job: ICU4C_Clang_Ubuntu_1604_WarningsAsErrors - displayName: 'C: Linux Clang WarningsAsErrors (Ubuntu 16.04)' +- job: ICU4C_Clang_Ubuntu_2204_WarningsAsErrors + displayName: 'C: Linux Clang WarningsAsErrors (Ubuntu 22.04)' timeoutInMinutes: 30 workspace: clean: all pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-22.04' steps: - checkout: self lfs: true @@ -76,13 +71,13 @@ jobs: CXX: clang++ #------------------------------------------------------------------------- -- job: ICU4C_Clang_Ubuntu_TestDataFilter_1604 - displayName: 'C: Linux Clang TestDataFilter (Ubuntu 16.04)' +- job: ICU4C_Clang_Ubuntu_TestDataFilter_2204 + displayName: 'C: Linux Clang TestDataFilter (Ubuntu 22.04)' timeoutInMinutes: 30 workspace: clean: all pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-22.04' steps: - checkout: self lfs: true @@ -105,22 +100,17 @@ jobs: CXX: clang++ #------------------------------------------------------------------------- -- job: ICU4C_Clang_Cpp14_Debug_Ubuntu_1804 - displayName: 'C: Linux Clang C++14 Debug (Ubuntu 18.04)' +- job: ICU4C_Clang_Cpp14_Debug_Ubuntu_2204 + displayName: 'C: Linux Clang C++14 Debug (Ubuntu 22.04)' timeoutInMinutes: 30 workspace: clean: all pool: - vmImage: 'ubuntu-18.04' + vmImage: 'ubuntu-22.04' steps: - checkout: self lfs: true fetchDepth: 1 - # This is to work-around issue: https://github.com/actions/virtual-environments/issues/3376 - # Once the Ubuntu 18.04 build bot image is fixed, we can remove this work-around. - - script: | - sudo apt remove libgcc-11-dev gcc-11 - displayName: Remove GCC 11 (work-around) - task: PowerShell@2 displayName: 'Set ICU Version' inputs: @@ -135,11 +125,11 @@ jobs: CXX: clang++ #------------------------------------------------------------------------- -- job: ICU4C_GCC_Ubuntu_2004 - displayName: 'C: Linux GCC (Ubuntu 20.04)' +- job: ICU4C_GCC_Ubuntu_2204 + displayName: 'C: Linux GCC (Ubuntu 22.04)' timeoutInMinutes: 30 pool: - vmImage: 'ubuntu-20.04' + vmImage: 'ubuntu-22.04' steps: - checkout: self lfs: true @@ -152,11 +142,11 @@ jobs: CXX: g++ #------------------------------------------------------------------------- -- job: ICU4C_Clang_Ubuntu_2004_LANG - displayName: 'C: Linux Clang (Ubuntu 20.04) - LANG has extension tags' +- job: ICU4C_Clang_Ubuntu_2204_LANG + displayName: 'C: Linux Clang (Ubuntu 22.04) - LANG has extension tags' timeoutInMinutes: 30 pool: - vmImage: 'ubuntu-20.04' + vmImage: 'ubuntu-22.04' steps: - checkout: self lfs: true @@ -170,15 +160,15 @@ jobs: LANG: "en_US@calendar=gregorian;hours=h12" #------------------------------------------------------------------------- -# VS 2019 Builds +# VS 2022 Builds #------------------------------------------------------------------------- - job: ICU4C_MSVC_x64_Release_Distrelease - displayName: 'C: MSVC 64-bit Release (VS 2019) + Distrelease' + displayName: 'C: MSVC 64-bit Release (VS 2022) + Distrelease' timeoutInMinutes: 30 workspace: clean: all pool: - vmImage: 'windows-2019' + vmImage: 'windows-2022' demands: - msbuild - visualstudio @@ -219,12 +209,12 @@ jobs: #------------------------------------------------------------------------- - job: ICU4C_MSVC_x86_Release_Distrelease - displayName: 'C: MSVC 32-bit Release (VS 2019) + Distrelease' + displayName: 'C: MSVC 32-bit Release (VS 2022) + Distrelease' timeoutInMinutes: 30 workspace: clean: all pool: - vmImage: 'windows-2019' + vmImage: 'windows-2022' demands: - msbuild - visualstudio @@ -313,12 +303,12 @@ jobs: #------------------------------------------------------------------------- - job: ICU4C_MSVC_x64_Release_TestDataFilter - displayName: 'C: MSVC 64-bit Release TestDataFilter (VS 2019)' + displayName: 'C: MSVC 64-bit Release TestDataFilter (VS 2022)' timeoutInMinutes: 30 workspace: clean: all pool: - vmImage: 'windows-2019' + vmImage: 'windows-2022' demands: - msbuild - visualstudio @@ -347,12 +337,12 @@ jobs: #------------------------------------------------------------------------- - job: ICU4C_MSVC_x86_Debug - displayName: 'C: MSVC 32-bit Debug (VS 2019)' + displayName: 'C: MSVC 32-bit Debug (VS 2022)' timeoutInMinutes: 60 workspace: clean: all pool: - vmImage: 'windows-2019' + vmImage: 'windows-2022' demands: - msbuild - visualstudio @@ -386,7 +376,7 @@ jobs: workspace: clean: all pool: - vmImage: 'vs2017-win2016' + vmImage: 'windows-2022' demands: - Cmd steps: @@ -425,12 +415,12 @@ jobs: #------------------------------------------------------------------------- - job: ICU4C_Clang_MacOSX_WarningsAsErrors - displayName: 'C: macOSX Clang WarningsAsErrors (Mojave 10.14)' + displayName: 'C: macOSX Clang WarningsAsErrors' timeoutInMinutes: 30 workspace: clean: all pool: - vmImage: 'macOS-10.14' + vmImage: 'macOS-latest' steps: - checkout: self lfs: true @@ -444,19 +434,19 @@ jobs: #------------------------------------------------------------------------- -- job: ICU4C_Clang_Valgrind_Ubuntu_1604 - displayName: 'C: Linux Clang Valgrind (Ubuntu 16.04)' - timeoutInMinutes: 60 +- job: ICU4C_Clang_Valgrind_Ubuntu_2004 + displayName: 'C: Linux Clang Valgrind (Ubuntu 20.04)' + timeoutInMinutes: 75 pool: - vmImage: 'Ubuntu 16.04' + vmImage: 'ubuntu-20.04' steps: - checkout: self lfs: true fetchDepth: 10 - script: | set -ex - sudo apt -y update - sudo apt install -y valgrind + sudo apt-get -y update + sudo apt-get install -y valgrind displayName: 'Install valgrind' timeoutInMinutes: 5 - script: | @@ -469,7 +459,7 @@ jobs: - script: | cd icu/icu4c/source/test/intltest && LD_LIBRARY_PATH=../../lib:../../stubdata:../../tools/ctestfw:$LD_LIBRARY_PATH valgrind --tool=memcheck --error-exitcode=1 --leak-check=full --show-reachable=yes ./intltest displayName: 'Valgrind intltest' - timeoutInMinutes: 45 + timeoutInMinutes: 60 - script: | cd icu/icu4c/source/test/cintltst && LD_LIBRARY_PATH=../../lib:../../stubdata:../../tools/ctestfw:$LD_LIBRARY_PATH valgrind --tool=memcheck --error-exitcode=1 --leak-check=full --show-reachable=yes ./cintltst displayName: 'Valgrind cintltst' diff --git a/changelog.md b/changelog.md index eb5f93541b4..460667095f4 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,8 @@ # Changelog +## ICU 68.2.0.10 +#### Misc changes: +- Add uprefs library to ICU to obtain the default locale as a full BCP47 tag [#112](https://github.com/microsoft/icu/pull/112) + ## ICU 68.2.0.9 #### Misc changes: - Migrate from PackageES build agents to public Microsoft hosted agents [#113](https://github.com/microsoft/icu/pull/113) diff --git a/icu-patches/patches/020-MSFT-Patch_ICU_Add_uprefs_library_to_obtain_default_locale_as_full_BCP47_tag.patch b/icu-patches/patches/020-MSFT-Patch_ICU_Add_uprefs_library_to_obtain_default_locale_as_full_BCP47_tag.patch new file mode 100644 index 00000000000..502beeb9f04 --- /dev/null +++ b/icu-patches/patches/020-MSFT-Patch_ICU_Add_uprefs_library_to_obtain_default_locale_as_full_BCP47_tag.patch @@ -0,0 +1,1319 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Erik Torres <26077674+erik0686@users.noreply.github.com> +Date: Wed, 6 Oct 2021 15:06:30 -0700 +Subject: Add uprefs library to ICU to obtain the default locale as a full + BCP47 tag (#112) + +Currently, for many processes and tasks, ICU gets the default locale and caches it. This means that when needed, ICU will get something like "en-US" and that will not change even if you were to change your language or region in your device. +Furthermore, ICU has currently no way of getting other globalization settings such as currency, calendar, hour cycle, first day of week, sorting method and measurement system. +We have decided to add a way to solve these two problems. +By adding the uprefs library (only to the Windows implementation of uprv_getDefaultLocaleID()), we are adding the Uprefs_getBCP47Tag() internal API, which obtains a full, canonical and valid BCP47Tag containing all of the settings. + +This means we also change the way we get the default locale. We go from getting only the locale and region, to getting the full thing. + +diff --git a/icu/icu4c/source/common/common.vcxproj b/icu/icu4c/source/common/common.vcxproj +index f8f28ad768ca2b4e7ec93893b213c3b426f294c1..305b705430837c9c928690bf1fd5aa46de81d5fd 100644 +--- a/icu/icu4c/source/common/common.vcxproj ++++ b/icu/icu4c/source/common/common.vcxproj +@@ -282,6 +282,7 @@ + + + ++ + + + +@@ -397,6 +398,7 @@ + + + ++ + + + +diff --git a/icu/icu4c/source/common/common_uwp.vcxproj b/icu/icu4c/source/common/common_uwp.vcxproj +index a57917292a7405b0a55fd8a34f9edd970c2951ef..0e346ccec772f6d37777505638ec7b242e21da5c 100644 +--- a/icu/icu4c/source/common/common_uwp.vcxproj ++++ b/icu/icu4c/source/common/common_uwp.vcxproj +@@ -404,6 +404,7 @@ + + + ++ + + + +@@ -520,6 +521,7 @@ + + + ++ + + + +diff --git a/icu/icu4c/source/common/putil.cpp b/icu/icu4c/source/common/putil.cpp +index 3ed6a05d22d83972e3fdf2c356bc87a17babda27..4d80d514f84639e9aab7d1109ee28b45e2cf4d42 100644 +--- a/icu/icu4c/source/common/putil.cpp ++++ b/icu/icu4c/source/common/putil.cpp +@@ -71,6 +71,7 @@ + #include "locmap.h" + #include "ucln_cmn.h" + #include "charstr.h" ++#include "uprefs.h" + + /* Include standard headers. */ + #include +@@ -1776,10 +1777,37 @@ The leftmost codepage (.xxx) wins. + return posixID; + + #elif U_PLATFORM_USES_ONLY_WIN32_API +-#define POSIX_LOCALE_CAPACITY 64 + UErrorCode status = U_ZERO_ERROR; + char *correctedPOSIXLocale = nullptr; + ++#if UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY ++ ++ int32_t neededBufferSize = uprefs_getBCP47Tag(nullptr, 0, &status); ++ MaybeStackArray windowsLocale(neededBufferSize, status); ++ int32_t length = uprefs_getBCP47Tag(windowsLocale.getAlias(), neededBufferSize, &status); ++ ++ if (length > 0) // If length is 0, then the call to uprefs_getBCP47Tag failed. ++ { ++ // Now normalize the resulting name ++ correctedPOSIXLocale = static_cast(uprv_malloc(length * 2)); ++ /* TODO: Should we just exit on memory allocation failure? */ ++ if (correctedPOSIXLocale) ++ { ++ int32_t posixLen = uloc_canonicalize(windowsLocale.getAlias(), correctedPOSIXLocale, length * 2, &status); ++ if (U_SUCCESS(status)) ++ { ++ *(correctedPOSIXLocale + posixLen) = 0; ++ gCorrectedPOSIXLocale = correctedPOSIXLocale; ++ gCorrectedPOSIXLocaleHeapAllocated = true; ++ ucln_common_registerCleanup(UCLN_COMMON_PUTIL, putil_cleanup); ++ } ++ else ++ { ++ uprv_free(correctedPOSIXLocale); ++ } ++ } ++ } ++#else + // If we have already figured this out just use the cached value + if (gCorrectedPOSIXLocale != nullptr) { + return gCorrectedPOSIXLocale; +@@ -1821,11 +1849,11 @@ The leftmost codepage (.xxx) wins. + } + + // Now normalize the resulting name +- correctedPOSIXLocale = static_cast(uprv_malloc(POSIX_LOCALE_CAPACITY + 1)); ++ correctedPOSIXLocale = static_cast(uprv_malloc(length * 2)); + /* TODO: Should we just exit on memory allocation failure? */ + if (correctedPOSIXLocale) + { +- int32_t posixLen = uloc_canonicalize(modifiedWindowsLocale, correctedPOSIXLocale, POSIX_LOCALE_CAPACITY, &status); ++ int32_t posixLen = uloc_canonicalize(modifiedWindowsLocale, correctedPOSIXLocale, length * 2, &status); + if (U_SUCCESS(status)) + { + *(correctedPOSIXLocale + posixLen) = 0; +@@ -1839,6 +1867,7 @@ The leftmost codepage (.xxx) wins. + } + } + } ++#endif + + // If unable to find a locale we can agree upon, use en-US by default + if (gCorrectedPOSIXLocale == nullptr) { +diff --git a/icu/icu4c/source/common/sources.txt b/icu/icu4c/source/common/sources.txt +index e0410daaa475fad0b76587cec3e2dc4d124814f2..3ebc3c301130465cec837233540253110e8479cc 100644 +--- a/icu/icu4c/source/common/sources.txt ++++ b/icu/icu4c/source/common/sources.txt +@@ -157,6 +157,7 @@ unistr_titlecase_brkiter.cpp + unorm.cpp + unormcmp.cpp + uobject.cpp ++uprefs.cpp + uprops.cpp + ures_cnv.cpp + uresbund.cpp +diff --git a/icu/icu4c/source/common/unicode/uconfig.h b/icu/icu4c/source/common/unicode/uconfig.h +index c4239fc9997028fb050be43740bb1cb368f514ba..da702d2d812fc4859ac6aa460bb8b0735ee28c29 100644 +--- a/icu/icu4c/source/common/unicode/uconfig.h ++++ b/icu/icu4c/source/common/unicode/uconfig.h +@@ -390,6 +390,22 @@ + # define UCONFIG_USE_WINDOWS_LCID_MAPPING_API 1 + #endif + ++/** ++ * \def UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY ++ * On Windows platforms (ie: U_PLATFORM_HAS_WIN32_API is true), this switch enables ICU to ++ * detect additional user preferences by setting BCP47 Unicode extension within the default locale. ++ * This includes information such as calendar, currency, hour cycle, among others. ++ * ++ * If this switch is off (or set to 0) then the default behavior of only detecting the language ++ * and country/region occurs. ++ * ++ * For example, the default locale may be detected as "es-MX-u-hc-h24", instead of "es-MX", ++ * if the user has selected a 24 hour clock option. ++*/ ++#ifndef UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY ++# define UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY 1 ++#endif ++ + //IGNORE_WINDOWS_HEADERS_END + + /* i18n library switches ---------------------------------------------------- */ +diff --git a/icu/icu4c/source/common/uprefs.cpp b/icu/icu4c/source/common/uprefs.cpp +new file mode 100644 +index 0000000000000000000000000000000000000000..b055cbe86f6ef16843444c1ea80667441a26bfa2 +--- /dev/null ++++ b/icu/icu4c/source/common/uprefs.cpp +@@ -0,0 +1,553 @@ ++// © 2016 and later: Unicode, Inc. and others. ++// License & terms of use: http://www.unicode.org/copyright.html ++ ++#include "uprefs.h" ++#if U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY ++#include "unicode/ustring.h" ++#include "cmemory.h" ++#include "charstr.h" ++#include "cstring.h" ++#include "cwchar.h" ++#include ++ ++U_NAMESPACE_USE ++ ++// Older versions of the Windows SDK don’t have definitions for calendar types that were added later on. ++// (For example, the Windows 7 SDK doesn’t have CAL_PERSIAN). ++// So we’ll need to provide our own definitions for some of them. ++// Note that on older versions of the OS these values won't ever be returned by the platform APIs, so providing our own definitions is fine. ++#ifndef CAL_PERSIAN ++#define CAL_PERSIAN 22 // Persian (Solar Hijri) calendar ++#endif ++ ++#define RETURN_FAILURE_STRING_WITH_STATUS_IF(condition, error, status) \ ++ if (condition) \ ++ { \ ++ *status = error; \ ++ return CharString(); \ ++ } ++ ++#define RETURN_FAILURE_WITH_STATUS_IF(condition, error, status) \ ++ if (condition) \ ++ { \ ++ *status = error; \ ++ return 0; \ ++ } ++ ++#define RETURN_VALUE_IF(condition, value) \ ++ if (condition) \ ++ { \ ++ return value; \ ++ } ++ ++#define RETURN_WITH_ALLOCATION_ERROR_IF_FAILED(status) \ ++ if (U_FAILURE(*status)) \ ++ { \ ++ *status = U_MEMORY_ALLOCATION_ERROR; \ ++ return CharString(); \ ++ } \ ++// ------------------------------------------------------- ++// ----------------- MAPPING FUNCTIONS-------------------- ++// ------------------------------------------------------- ++ ++// Maps from a NLS Calendar ID (CALID) to a BCP47 Unicode Extension calendar identifier. ++// ++// We map the NLS CALID from GetLocaleInfoEx to the calendar identifier ++// used in BCP47 tag with Unicode Extensions. ++// ++// This does not return a full nor valid BCP47Tag, it only returns the option that the BCP47 tag ++// would return after the "ca-" part ++// ++// For example: ++// CAL_GREGORIAN would return "gregory". ++// CAL_HIJRI would return "islamic". ++// ++// These could be used in a BCP47 tag like this: "en-US-u-ca-gregory". ++// Note that there are some NLS calendars that are not supported with the BCP47 U extensions, ++// and vice-versa. ++// ++// NLS CALID reference:https://docs.microsoft.com/en-us/windows/win32/intl/calendar-identifiers ++CharString getCalendarBCP47FromNLSType(int32_t calendar, UErrorCode* status) ++{ ++ switch(calendar) ++ { ++ case CAL_GREGORIAN: ++ case CAL_GREGORIAN_US: ++ case CAL_GREGORIAN_ME_FRENCH: ++ case CAL_GREGORIAN_ARABIC: ++ case CAL_GREGORIAN_XLIT_ENGLISH: ++ case CAL_GREGORIAN_XLIT_FRENCH: ++ return CharString("gregory", *status); ++ ++ case CAL_JAPAN: ++ return CharString("japanese", *status); ++ ++ case CAL_TAIWAN: ++ return CharString("roc", *status); ++ ++ case CAL_KOREA: ++ return CharString("dangi", *status); ++ ++ case CAL_HIJRI: ++ return CharString("islamic", *status); ++ ++ case CAL_THAI: ++ return CharString("buddhist", *status); ++ ++ case CAL_HEBREW: ++ return CharString("hebrew", *status); ++ ++ case CAL_PERSIAN: ++ return CharString("persian", *status); ++ ++ case CAL_UMALQURA: ++ return CharString("islamic-umalqura", *status); ++ ++ default: ++ return CharString(); ++ } ++} ++ ++// Maps from a NLS Alternate sorting system to a BCP47 U extension sorting system. ++// ++// We map the alternate sorting method from GetLocaleInfoEx to the sorting method ++// used in BCP47 tag with Unicode Extensions. ++// ++// This does not return a full nor valid BCP47Tag, it only returns the option that the BCP47 tag ++// would return after the "co-" part ++// ++// For example: ++// "phoneb" (parsed from "de-DE_phoneb") would return "phonebk". ++// "radstr" (parsed from "ja-JP_radstr") would return "unihan". ++// ++// These could be used in a BCP47 tag like this: "de-DE-u-co-phonebk". ++// Note that there are some NLS Alternate sort methods that are not supported with the BCP47 U extensions, ++// and vice-versa. ++CharString getSortingSystemBCP47FromNLSType(const wchar_t* sortingSystem, UErrorCode* status) ++{ ++ if (wcscmp(sortingSystem, L"phoneb") == 0) // Phonebook style ordering (such as in German) ++ { ++ return CharString("phonebk", *status); ++ } ++ else if (wcscmp(sortingSystem, L"tradnl") == 0) // Traditional style ordering (such as in Spanish) ++ { ++ return CharString("trad", *status); ++ } ++ else if (wcscmp(sortingSystem, L"stroke") == 0) // Pinyin ordering for Latin, stroke order for CJK characters (used in Chinese) ++ { ++ return CharString("stroke", *status); ++ } ++ else if (wcscmp(sortingSystem, L"radstr") == 0) // Pinyin ordering for Latin, Unihan radical-stroke ordering for CJK characters (used in Chinese) ++ { ++ return CharString("unihan", *status); ++ } ++ else if (wcscmp(sortingSystem, L"pronun") == 0) // Phonetic ordering (sorting based on pronunciation) ++ { ++ return CharString("phonetic", *status); ++ } ++ else ++ { ++ return CharString(); ++ } ++} ++ ++// Maps from a NLS first day of week value to a BCP47 U extension first day of week. ++// ++// NLS defines: ++// 0 -> Monday, 1 -> Tuesday, ... 5 -> Saturday, 6 -> Sunday ++// ++// We map the first day of week from GetLocaleInfoEx to the first day of week ++// used in BCP47 tag with Unicode Extensions. ++// ++// This does not return a full nor valid BCP47Tag, it only returns the option that the BCP47 tag ++// would return after the "fw-" part ++// ++// For example: ++// 1 (Tuesday) would return "tue". ++// 6 (Sunday) would return "sun". ++// ++// These could be used in a BCP47 tag like this: "en-US-u-fw-sun". ++CharString getFirstDayBCP47FromNLSType(int32_t firstday, UErrorCode* status) ++{ ++ switch(firstday) ++ { ++ case 0: ++ return CharString("mon", *status); ++ ++ case 1: ++ return CharString("tue", *status); ++ ++ case 2: ++ return CharString("wed", *status); ++ ++ case 3: ++ return CharString("thu", *status); ++ ++ case 4: ++ return CharString("fri", *status); ++ ++ case 5: ++ return CharString("sat", *status); ++ ++ case 6: ++ return CharString("sun", *status); ++ ++ default: ++ return CharString(); ++ } ++} ++ ++// Maps from a NLS Measurement system to a BCP47 U extension measurement system. ++// ++// NLS defines: ++// 0 -> Metric system, 1 -> U.S. System ++// ++// This does not return a full nor valid BCP47Tag, it only returns the option that the BCP47 tag ++// would return after the "ms-" part ++// ++// For example: ++// 0 (Metric) would return "metric". ++// 6 (U.S. System) would return "ussystem". ++// ++// These could be used in a BCP47 tag like this: "en-US-u-ms-metric". ++CharString getMeasureSystemBCP47FromNLSType(int32_t measureSystem, UErrorCode *status) ++{ ++ switch(measureSystem) ++ { ++ case 0: ++ return CharString("metric", *status); ++ case 1: ++ return CharString("ussystem", *status); ++ default: ++ return CharString(); ++ } ++} ++ ++// ------------------------------------------------------- ++// --------------- END OF MAPPING FUNCTIONS -------------- ++// ------------------------------------------------------- ++ ++// ------------------------------------------------------- ++// ------------------ HELPER FUCTIONS ------------------- ++// ------------------------------------------------------- ++ ++// Return the CLDR "h12" or "h23" format for the 12 or 24 hour clock. ++// NLS only gives us a "time format" of a form similar to "h:mm:ss tt" ++// The NLS "h" is 12 hour, and "H" is 24 hour, so we'll scan for the ++// first h or H. ++// Note that the NLS string could have sections escaped with single ++// quotes, so be sure to skip those parts. Eg: "'Hours:' h:mm:ss" ++// would skip the "H" in 'Hours' and use the h in the actual pattern. ++CharString get12_or_24hourFormat(wchar_t* hourFormat, UErrorCode* status) ++{ ++ bool isInEscapedString = false; ++ const int32_t hourLength = static_cast(uprv_wcslen(hourFormat)); ++ for (int32_t i = 0; i < hourLength; i++) ++ { ++ // Toggle escaped flag if in ' quoted portion ++ if (hourFormat[i] == L'\'') ++ { ++ isInEscapedString = !isInEscapedString; ++ } ++ ++ if (!isInEscapedString) ++ { ++ // Check for both so we can escape early ++ if (hourFormat[i] == L'H') ++ { ++ return CharString("h23", *status); ++ } ++ ++ if (hourFormat[i] == L'h') ++ { ++ return CharString("h12", *status); ++ } ++ } ++ } ++ // default to a 24 hour clock as that's more common worldwide ++ return CharString("h23", *status); ++} ++ ++UErrorCode getUErrorCodeFromLastError() ++{ ++ DWORD error = GetLastError(); ++ switch(error) ++ { ++ case ERROR_INSUFFICIENT_BUFFER: ++ return U_BUFFER_OVERFLOW_ERROR; ++ ++ case ERROR_INVALID_FLAGS: ++ case ERROR_INVALID_PARAMETER: ++ return U_ILLEGAL_ARGUMENT_ERROR; ++ ++ case ERROR_OUTOFMEMORY: ++ return U_MEMORY_ALLOCATION_ERROR; ++ ++ default: ++ return U_INTERNAL_PROGRAM_ERROR; ++ } ++} ++ ++int32_t GetLocaleInfoExWrapper(LPCWSTR lpLocaleName, LCTYPE LCType, LPWSTR lpLCData, int cchData, UErrorCode* status) ++{ ++ RETURN_VALUE_IF(U_FAILURE(*status), 0); ++ ++#ifndef UPREFS_TEST ++ *status = U_ZERO_ERROR; ++ int32_t result = GetLocaleInfoEx(lpLocaleName, LCType, lpLCData, cchData); ++ ++ if (result == 0) ++ { ++ *status = getUErrorCodeFromLastError(); ++ } ++ return result; ++#else ++ #include "uprefstest.h" ++ UPrefsTest prefTests; ++ return prefTests.MockGetLocaleInfoEx(lpLocaleName, LCType, lpLCData, cchData, status); ++#endif ++} ++ ++// Copies a string to a buffer if its size allows it and returns the size. ++// The returned needed buffer size includes the terminating \0 null character. ++// If the buffer's size is set to 0, the needed buffer size is returned before copying the string. ++int32_t checkBufferCapacityAndCopy(const char* uprefsString, char* uprefsBuffer, int32_t bufferSize, UErrorCode* status) ++{ ++ int32_t neededBufferSize = static_cast(uprv_strlen(uprefsString) + 1); ++ ++ RETURN_VALUE_IF(bufferSize == 0, neededBufferSize); ++ RETURN_FAILURE_WITH_STATUS_IF(neededBufferSize > bufferSize, U_BUFFER_OVERFLOW_ERROR, status); ++ ++ uprv_strcpy(uprefsBuffer, uprefsString); ++ ++ return neededBufferSize; ++} ++ ++CharString getLocaleBCP47Tag_impl(UErrorCode* status, bool getSorting) ++{ ++ // First part of a bcp47 tag looks like an NLS user locale, so we get the NLS user locale. ++ int32_t neededBufferSize = GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_SNAME, nullptr, 0, status); ++ ++ RETURN_VALUE_IF(U_FAILURE(*status), CharString()); ++ ++ MaybeStackArray NLSLocale(neededBufferSize, *status); ++ RETURN_WITH_ALLOCATION_ERROR_IF_FAILED(status); ++ ++ int32_t result = GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_SNAME, NLSLocale.getAlias(), neededBufferSize, status); ++ ++ RETURN_VALUE_IF(U_FAILURE(*status), CharString()); ++ ++ if (getSorting) //We determine if we want the locale (for example, en-US) or the sorting method (for example, phonebk) ++ { ++ // We use LOCALE_SNAME to get the sorting method (if any). So we need to keep ++ // only the sorting bit after the _, removing the locale name. ++ // Example: from "de-DE_phoneb" we only want "phoneb" ++ const wchar_t * startPosition = wcschr(NLSLocale.getAlias(), L'_'); ++ ++ // Note: not finding a "_" is not an error, it means the user has not selected an alternate sorting method, which is fine. ++ if (startPosition != nullptr) ++ { ++ CharString sortingSystem = getSortingSystemBCP47FromNLSType(startPosition + 1, status); ++ ++ if (sortingSystem.length() == 0) ++ { ++ *status = U_UNSUPPORTED_ERROR; ++ return CharString(); ++ } ++ return sortingSystem; ++ } ++ } ++ else ++ { ++ // The NLS locale may include a non-default sort, such as de-DE_phoneb. We only want the locale name before the _. ++ wchar_t * position = wcschr(NLSLocale.getAlias(), L'_'); ++ if (position != nullptr) ++ { ++ *position = L'\0'; ++ } ++ ++ CharString languageTag; ++ int32_t resultCapacity = 0; ++ languageTag.getAppendBuffer(neededBufferSize, neededBufferSize, resultCapacity, *status); ++ RETURN_WITH_ALLOCATION_ERROR_IF_FAILED(status); ++ ++ int32_t unitsWritten = 0; ++ u_strToUTF8(languageTag.data(), neededBufferSize, &unitsWritten, reinterpret_cast(NLSLocale.getAlias()), neededBufferSize, status); ++ RETURN_VALUE_IF(U_FAILURE(*status), CharString()); ++ return languageTag; ++ } ++ ++ return CharString(); ++} ++ ++CharString getCalendarSystem_impl(UErrorCode* status) ++{ ++ int32_t NLSCalendar = 0; ++ ++ GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_ICALENDARTYPE | LOCALE_RETURN_NUMBER, reinterpret_cast(&NLSCalendar), sizeof(NLSCalendar) / sizeof(wchar_t), status); ++ ++ RETURN_VALUE_IF(U_FAILURE(*status), CharString()); ++ ++ CharString calendar(getCalendarBCP47FromNLSType(NLSCalendar, status), *status); ++ RETURN_FAILURE_STRING_WITH_STATUS_IF(calendar.length() == 0, U_UNSUPPORTED_ERROR, status); ++ ++ return calendar; ++} ++ ++CharString getCurrencyCode_impl(UErrorCode* status) ++{ ++ int32_t neededBufferSize = GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_SINTLSYMBOL, nullptr, 0, status); ++ ++ RETURN_VALUE_IF(U_FAILURE(*status), CharString()); ++ ++ MaybeStackArray NLScurrencyData(neededBufferSize, *status); ++ RETURN_WITH_ALLOCATION_ERROR_IF_FAILED(status); ++ ++ int32_t result = GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_SINTLSYMBOL, NLScurrencyData.getAlias(), neededBufferSize, status); ++ ++ RETURN_VALUE_IF(U_FAILURE(*status), CharString()); ++ ++ MaybeStackArray currency(neededBufferSize, *status); ++ RETURN_WITH_ALLOCATION_ERROR_IF_FAILED(status); ++ ++ int32_t unitsWritten = 0; ++ u_strToUTF8(currency.getAlias(), neededBufferSize, &unitsWritten, reinterpret_cast(NLScurrencyData.getAlias()), neededBufferSize, status); ++ RETURN_VALUE_IF(U_FAILURE(*status), CharString()); ++ ++ if (unitsWritten == 0) ++ { ++ *status = U_INTERNAL_PROGRAM_ERROR; ++ return CharString(); ++ } ++ ++ // Since we retreived the currency code in caps, we need to make it lowercase for it to be in CLDR BCP47 U extensions format. ++ T_CString_toLowerCase(currency.getAlias()); ++ ++ return CharString(currency.getAlias(), neededBufferSize, *status); ++} ++ ++CharString getFirstDayOfWeek_impl(UErrorCode* status) ++{ ++ int32_t NLSfirstDay = 0; ++ GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK | LOCALE_RETURN_NUMBER, reinterpret_cast(&NLSfirstDay), sizeof(NLSfirstDay) / sizeof(wchar_t), status); ++ ++ RETURN_VALUE_IF(U_FAILURE(*status), CharString()); ++ ++ CharString firstDay = getFirstDayBCP47FromNLSType(NLSfirstDay, status); ++ RETURN_FAILURE_STRING_WITH_STATUS_IF(firstDay.length() == 0, U_UNSUPPORTED_ERROR, status); ++ ++ return firstDay; ++} ++ ++CharString getHourCycle_impl(UErrorCode* status) ++{ ++ int32_t neededBufferSize = GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_STIMEFORMAT, nullptr, 0, status); ++ ++ RETURN_VALUE_IF(U_FAILURE(*status), CharString()); ++ ++ MaybeStackArray NLShourCycle(neededBufferSize, *status); ++ RETURN_WITH_ALLOCATION_ERROR_IF_FAILED(status); ++ ++ int32_t result = GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_STIMEFORMAT, NLShourCycle.getAlias(), neededBufferSize, status); ++ ++ RETURN_VALUE_IF(U_FAILURE(*status), CharString()); ++ ++ CharString hourCycle = get12_or_24hourFormat(NLShourCycle.getAlias(), status); ++ if (hourCycle.length() == 0) ++ { ++ *status = U_INTERNAL_PROGRAM_ERROR; ++ return CharString(); ++ } ++ return hourCycle; ++} ++ ++CharString getMeasureSystem_impl(UErrorCode* status) ++{ ++ int32_t NLSmeasureSystem = 0; ++ GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_IMEASURE | LOCALE_RETURN_NUMBER, reinterpret_cast(&NLSmeasureSystem), sizeof(NLSmeasureSystem) / sizeof(wchar_t), status); ++ ++ RETURN_VALUE_IF(U_FAILURE(*status), CharString()); ++ ++ CharString measureSystem = getMeasureSystemBCP47FromNLSType(NLSmeasureSystem, status); ++ RETURN_FAILURE_STRING_WITH_STATUS_IF(measureSystem.length() == 0, U_UNSUPPORTED_ERROR, status); ++ ++ return measureSystem; ++} ++ ++void appendIfDataNotEmpty(CharString& dest, const char* firstData, const char* secondData, bool& warningGenerated, UErrorCode* status) ++{ ++ if (*status == U_UNSUPPORTED_ERROR) ++ { ++ warningGenerated = true; ++ *status = U_ZERO_ERROR; ++ } ++ ++ if (uprv_strlen(secondData) != 0) ++ { ++ dest.append(firstData, *status); ++ dest.append(secondData, *status); ++ } ++} ++// ------------------------------------------------------- ++// --------------- END OF HELPER FUNCTIONS --------------- ++// ------------------------------------------------------- ++ ++ ++// ------------------------------------------------------- ++// ---------------------- APIs --------------------------- ++// ------------------------------------------------------- ++ ++// Gets the valid and canonical BCP47 tag with the user settings for Language, Calendar, Sorting, Currency, ++// First day of week, Hour cycle, and Measurement system. ++// Calls all of the other APIs ++// Returns the needed buffer size for the BCP47 Tag. ++int32_t uprefs_getBCP47Tag(char* uprefsBuffer, int32_t bufferSize, UErrorCode* status) ++{ ++ RETURN_FAILURE_WITH_STATUS_IF(uprefsBuffer == nullptr && bufferSize != 0, U_ILLEGAL_ARGUMENT_ERROR, status); ++ ++ *status = U_ZERO_ERROR; ++ CharString BCP47Tag; ++ bool warningGenerated = false; ++ ++ CharString languageTag = getLocaleBCP47Tag_impl(status, false); ++ RETURN_VALUE_IF(U_FAILURE(*status), 0); ++ BCP47Tag.append(languageTag.data(), *status); ++ BCP47Tag.append("-u", *status); ++ ++ CharString calendar = getCalendarSystem_impl(status); ++ RETURN_VALUE_IF(U_FAILURE(*status) && *status != U_UNSUPPORTED_ERROR, 0); ++ appendIfDataNotEmpty(BCP47Tag, "-ca-", calendar.data(), warningGenerated, status); ++ ++ CharString sortingSystem = getLocaleBCP47Tag_impl(status, true); ++ RETURN_VALUE_IF(U_FAILURE(*status) && *status != U_UNSUPPORTED_ERROR, 0); ++ appendIfDataNotEmpty(BCP47Tag, "-co-", sortingSystem.data(), warningGenerated, status); ++ ++ CharString currency = getCurrencyCode_impl(status); ++ RETURN_VALUE_IF(U_FAILURE(*status) && *status != U_UNSUPPORTED_ERROR, 0); ++ appendIfDataNotEmpty(BCP47Tag, "-cu-", currency.data(), warningGenerated, status); ++ ++ CharString firstDay = getFirstDayOfWeek_impl(status); ++ RETURN_VALUE_IF(U_FAILURE(*status) && *status != U_UNSUPPORTED_ERROR, 0); ++ appendIfDataNotEmpty(BCP47Tag, "-fw-", firstDay.data(), warningGenerated, status); ++ ++ CharString hourCycle = getHourCycle_impl(status); ++ RETURN_VALUE_IF(U_FAILURE(*status) && *status != U_UNSUPPORTED_ERROR, 0); ++ appendIfDataNotEmpty(BCP47Tag, "-hc-", hourCycle.data(), warningGenerated, status); ++ ++ CharString measureSystem = getMeasureSystem_impl(status); ++ RETURN_VALUE_IF(U_FAILURE(*status) && *status != U_UNSUPPORTED_ERROR, 0); ++ appendIfDataNotEmpty(BCP47Tag, "-ms-", measureSystem.data(), warningGenerated, status); ++ ++ if (warningGenerated) ++ { ++ *status = U_USING_FALLBACK_WARNING; ++ } ++ ++ return checkBufferCapacityAndCopy(BCP47Tag.data(), uprefsBuffer, bufferSize, status); ++} ++ ++// ------------------------------------------------------- ++// ---------------------- END OF APIs -------------------- ++// ------------------------------------------------------- ++ ++#endif // U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY +\ No newline at end of file +diff --git a/icu/icu4c/source/common/uprefs.h b/icu/icu4c/source/common/uprefs.h +new file mode 100644 +index 0000000000000000000000000000000000000000..2d77d0c9e7f8bdf9287b77d55741160f1c10a1fe +--- /dev/null ++++ b/icu/icu4c/source/common/uprefs.h +@@ -0,0 +1,29 @@ ++// © 2016 and later: Unicode, Inc. and others. ++// License & terms of use: http://www.unicode.org/copyright.html ++ ++#ifndef UPREFS_H ++#define UPREFS_H ++ ++#include "unicode/platform.h" ++#include "unicode/utypes.h" ++#if U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY ++ ++/** ++* Gets the valid and canonical BCP47 tag with the user settings for Language, Calendar, Sorting, Currency, ++* First day of week, Hour cycle, and Measurement system when available. ++* ++* @param uprefsBuffer Pointer to a buffer in which this function retrieves the BCP47 tag. ++* This pointer is not used if bufferSize is set to 0. ++* @param bufferSize Size, in characters, of the data buffer indicated by uprefsBuffer. Alternatively, the application ++* can set this parameter to 0. In this case, the function does not use the uprefsBuffer parameter ++* and returns the required buffer size, including the terminating null character. ++* @param status: Pointer to a UErrorCode. The resulting value will be U_ZERO_ERROR if the call was successful or will ++* contain an error or warning code. If the status is U_USING_FALLBACK_WARNING, it means at least one of the ++ settings was not succesfully mapped between NLS and CLDR, so it will not be shown on the BCP47 tag. ++* @return The needed buffer size, including the terminating \0 null character if the call was successful, should be ignored ++* if status was not U_ZERO_ERROR. ++*/ ++int32_t uprefs_getBCP47Tag(char* uprefsBuffer, int32_t bufferSize, UErrorCode* status); ++ ++#endif //U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY ++#endif //UPREFS_H +\ No newline at end of file +diff --git a/icu/icu4c/source/test/intltest/Makefile.in b/icu/icu4c/source/test/intltest/Makefile.in +index 13d3ea86dc9bd7c17c944b2fb7b04a143879421f..5f8822cedac4a5ddc9428cb85941b668f612c5a0 100644 +--- a/icu/icu4c/source/test/intltest/Makefile.in ++++ b/icu/icu4c/source/test/intltest/Makefile.in +@@ -69,7 +69,7 @@ string_segment_test.o \ + numbertest_parse.o numbertest_doubleconversion.o numbertest_skeletons.o \ + static_unisets_test.o numfmtdatadriventest.o numbertest_range.o erarulestest.o \ + formattedvaluetest.o formatted_string_builder_test.o numbertest_permutation.o \ +-units_data_test.o units_router_test.o units_test.o ++units_data_test.o units_router_test.o units_test.o uprefstest.o + + DEPS = $(OBJECTS:.o=.d) + +diff --git a/icu/icu4c/source/test/intltest/intltest.vcxproj b/icu/icu4c/source/test/intltest/intltest.vcxproj +index 319c3ab58f68f70f3da6ead4ca6cf5ca81da617b..20da05e608aeb4f472ef378d222732c863ec04aa 100644 +--- a/icu/icu4c/source/test/intltest/intltest.vcxproj ++++ b/icu/icu4c/source/test/intltest/intltest.vcxproj +@@ -288,6 +288,7 @@ + + + ++ + + + +@@ -419,6 +420,7 @@ + + + ++ + + + +diff --git a/icu/icu4c/source/test/intltest/itutil.cpp b/icu/icu4c/source/test/intltest/itutil.cpp +index 228dbf2f218aa1a6ac6860ec54ed67303b243699..2f7ba22278596980f172617c2db9df55c28ee680 100644 +--- a/icu/icu4c/source/test/intltest/itutil.cpp ++++ b/icu/icu4c/source/test/intltest/itutil.cpp +@@ -33,6 +33,9 @@ + #include "uvectest.h" + #include "aliastst.h" + #include "usettest.h" ++#if U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY ++ #include "uprefstest.h" ++#endif + + extern IntlTest *createBytesTrieTest(); + extern IntlTest *createLocaleMatcherTest(); +@@ -67,6 +70,9 @@ void IntlTestUtilities::runIndexedTest( int32_t index, UBool exec, const char* & + TESTCASE_AUTO_CLASS(LocaleAliasTest); + TESTCASE_AUTO_CLASS(UnicodeSetTest); + TESTCASE_AUTO_CLASS(ErrorCodeTest); ++#if U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY ++ TESTCASE_AUTO_CLASS(UPrefsTest); ++#endif + TESTCASE_AUTO_CREATE_CLASS(LocalPointerTest); + TESTCASE_AUTO_CREATE_CLASS(BytesTrieTest); + TESTCASE_AUTO_CREATE_CLASS(UCharsTrieTest); +diff --git a/icu/icu4c/source/test/intltest/uprefstest.cpp b/icu/icu4c/source/test/intltest/uprefstest.cpp +new file mode 100644 +index 0000000000000000000000000000000000000000..f76997c66c45a694398c62ed23e94d0d1d509561 +--- /dev/null ++++ b/icu/icu4c/source/test/intltest/uprefstest.cpp +@@ -0,0 +1,437 @@ ++// © 2016 and later: Unicode, Inc. and others. ++// License & terms of use: http://www.unicode.org/copyright.html ++#include "uprefstest.h" ++#if U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY ++ ++#define ARRAY_SIZE 512 ++ ++ std::wstring UPrefsTest::language = L""; ++ std::wstring UPrefsTest::currency = L""; ++ std::wstring UPrefsTest::hourCycle = L""; ++ int32_t UPrefsTest::firstday = 0; ++ int32_t UPrefsTest::measureSystem = 0; ++ CALID UPrefsTest::calendar = 0; ++ ++void UPrefsTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) ++{ ++ if (exec) logln("TestSuite UPrefsTest: "); ++ TESTCASE_AUTO_BEGIN; ++ TESTCASE_AUTO(TestGetDefaultLocaleAsBCP47Tag); ++ TESTCASE_AUTO(TestBCP47TagWithSorting); ++ TESTCASE_AUTO(TestBCP47TagChineseSimplified); ++ TESTCASE_AUTO(TestBCP47TagChineseSortingStroke); ++ TESTCASE_AUTO(TestBCP47TagJapanCalendar); ++ TESTCASE_AUTO(TestUseNeededBuffer); ++ TESTCASE_AUTO(TestGetNeededBuffer); ++ TESTCASE_AUTO(TestGetUnsupportedSorting); ++ TESTCASE_AUTO(Get24HourCycleMixed); ++ TESTCASE_AUTO(Get12HourCycleMixed); ++ TESTCASE_AUTO(Get12HourCycleMixed2); ++ TESTCASE_AUTO(Get12HourCycle); ++ TESTCASE_AUTO(Get12HourCycle2); ++ TESTCASE_AUTO_END; ++} ++ ++int32_t UPrefsTest::MockGetLocaleInfoEx(LPCWSTR lpLocaleName, LCTYPE LCType, LPWSTR lpLCData, int cchData, UErrorCode* status) ++{ ++ switch (LCType) ++ { ++ case LOCALE_SNAME: ++ if (cchData == 0) ++ { ++ *status = U_ZERO_ERROR; ++ return language.length() + 1; ++ } ++ ++ if (language.length() + 1 > cchData) ++ { ++ *status = U_BUFFER_OVERFLOW_ERROR; ++ return 0; ++ } ++ wcsncpy(lpLCData, language.c_str(), cchData); ++ *status = U_ZERO_ERROR; ++ return language.length(); ++ ++ case LOCALE_ICALENDARTYPE | LOCALE_RETURN_NUMBER: ++ if (cchData == 0) ++ { ++ *status = U_ZERO_ERROR; ++ return 2; ++ } ++ if (cchData < 2) ++ { ++ *status = U_BUFFER_OVERFLOW_ERROR; ++ return 0; ++ } ++ *(reinterpret_cast(lpLCData)) = calendar; ++ *status = U_ZERO_ERROR; ++ return 2; ++ ++ case LOCALE_SINTLSYMBOL: ++ if (cchData == 0) ++ { ++ *status = U_ZERO_ERROR; ++ return currency.length() + 1; ++ } ++ if (currency.length() + 1 > cchData) ++ { ++ *status = U_BUFFER_OVERFLOW_ERROR; ++ return 0; ++ } ++ wcsncpy(lpLCData, currency.c_str(), cchData); ++ *status = U_ZERO_ERROR; ++ return currency.length(); ++ ++ case LOCALE_IFIRSTDAYOFWEEK | LOCALE_RETURN_NUMBER: ++ if (cchData == 0) ++ { ++ *status = U_ZERO_ERROR; ++ return 2; ++ } ++ if (cchData < 2) ++ { ++ *status = U_BUFFER_OVERFLOW_ERROR; ++ return 0; ++ } ++ ++ *(reinterpret_cast(lpLCData)) = firstday; ++ *status = U_ZERO_ERROR; ++ return 2; ++ ++ case LOCALE_STIMEFORMAT: ++ if (cchData == 0) ++ { ++ *status = U_ZERO_ERROR; ++ return hourCycle.length() + 1; ++ } ++ ++ if (hourCycle.length() + 1 > cchData) ++ { ++ *status = U_BUFFER_OVERFLOW_ERROR; ++ return 0; ++ } ++ wcsncpy(lpLCData, hourCycle.c_str(), cchData); ++ *status = U_ZERO_ERROR; ++ return 0; ++ ++ case LOCALE_IMEASURE | LOCALE_RETURN_NUMBER: ++ if (cchData == 0) ++ { ++ *status = U_ZERO_ERROR; ++ return 2; ++ } ++ ++ if (cchData < 2) ++ { ++ *status = U_BUFFER_OVERFLOW_ERROR; ++ return 0; ++ } ++ *(reinterpret_cast(lpLCData)) = measureSystem; ++ *status = U_ZERO_ERROR; ++ return 2; ++ ++ default: ++ *status = U_INTERNAL_PROGRAM_ERROR; ++ return 0; ++ } ++} ++ ++// The code above is independent of the library itself, but for the code below this point, ++// we need to include the library to be able to use the definitions of the API uprefs_getBCP47Tag ++#include "uprefs.cpp" ++ ++void UPrefsTest::TestGetDefaultLocaleAsBCP47Tag() ++{ ++ char languageBuffer[ARRAY_SIZE] = {0}; ++ language = L"en-US"; ++ currency = L"USD"; ++ hourCycle = L"HH:mm:ss"; ++ firstday = 0; ++ measureSystem = 1; ++ calendar = CAL_GREGORIAN; ++ UErrorCode status = U_ZERO_ERROR; ++ const char* expectedValue = "en-US-u-ca-gregory-cu-usd-fw-mon-hc-h23-ms-ussystem"; ++ ++ if ( uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 52) ++ { ++ errln("Expected length to be 52, but got: %d\n",uprv_strlen(languageBuffer)); ++ } ++ if ( uprv_strcmp(expectedValue, languageBuffer) != 0) ++ { ++ errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); ++ } ++} ++ ++void UPrefsTest::TestBCP47TagWithSorting() ++{ ++ char languageBuffer[ARRAY_SIZE] = {0}; ++ language = L"de-DE_phoneb"; ++ currency = L"EUR"; ++ hourCycle = L"HH:mm:ss"; ++ firstday = 0; ++ measureSystem = 1; ++ calendar = CAL_GREGORIAN; ++ UErrorCode status = U_ZERO_ERROR; ++ char* expectedValue = "de-DE-u-ca-gregory-co-phonebk-cu-eur-fw-mon-hc-h23-ms-ussystem"; ++ ++ if ( uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 63) ++ { ++ errln("Expected length to be 63, but got: %d\n",uprv_strlen(languageBuffer)); ++ } ++ if ( uprv_strcmp(expectedValue, languageBuffer) != 0) ++ { ++ errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); ++ } ++} ++ ++void UPrefsTest::TestBCP47TagChineseSimplified() ++{ ++ char languageBuffer[ARRAY_SIZE] = {0}; ++ language = L"zh-Hans-HK"; ++ currency = L"EUR"; ++ hourCycle = L"hh:mm:ss"; ++ firstday = 2; ++ measureSystem = 1; ++ calendar = CAL_GREGORIAN; ++ UErrorCode status = U_ZERO_ERROR; ++ char* expectedValue = "zh-Hans-HK-u-ca-gregory-cu-eur-fw-wed-hc-h12-ms-ussystem"; ++ ++ if ( uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 57) ++ { ++ errln("Expected length to be 57, but got: %d\n",uprv_strlen(languageBuffer)); ++ } ++ if ( uprv_strcmp(expectedValue, languageBuffer) != 0) ++ { ++ errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); ++ } ++} ++ ++void UPrefsTest::TestBCP47TagChineseSortingStroke() ++{ ++ char languageBuffer[ARRAY_SIZE] = {0}; ++ language = L"zh-SG_stroke"; ++ currency = L"EUR"; ++ hourCycle = L"hh:mm:ss"; ++ firstday = 2; ++ measureSystem = 0; ++ calendar = CAL_GREGORIAN; ++ UErrorCode status = U_ZERO_ERROR; ++ char* expectedValue = "zh-SG-u-ca-gregory-co-stroke-cu-eur-fw-wed-hc-h12-ms-metric"; ++ ++ if ( uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 60) ++ { ++ errln("Expected length to be 60, but got: %d\n",uprv_strlen(languageBuffer)); ++ } ++ if ( uprv_strcmp(expectedValue, languageBuffer) != 0) ++ { ++ errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); ++ } ++} ++ ++void UPrefsTest::TestBCP47TagJapanCalendar() ++{ ++ char languageBuffer[ARRAY_SIZE] = {0}; ++ language = L"ja-JP"; ++ currency = L"MXN"; ++ hourCycle = L"hh:mm:ss"; ++ firstday = 1; ++ measureSystem = 0; ++ calendar = CAL_JAPAN; ++ UErrorCode status = U_ZERO_ERROR; ++ char* expectedValue = "ja-JP-u-ca-japanese-cu-mxn-fw-tue-hc-h12-ms-metric"; ++ ++ if ( uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 51) ++ { ++ errln("Expected length to be 51, but got: %d\n",uprv_strlen(languageBuffer)); ++ } ++ if ( uprv_strcmp(expectedValue, languageBuffer) != 0) ++ { ++ errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); ++ } ++} ++ ++void UPrefsTest::TestUseNeededBuffer() ++{ ++ char languageBuffer[ARRAY_SIZE] = {0}; ++ language = L"ja-JP"; ++ currency = L"MXN"; ++ hourCycle = L"hh:mm:ss"; ++ firstday = 1; ++ measureSystem = 0; ++ calendar = CAL_THAI; ++ UErrorCode status = U_ZERO_ERROR; ++ char* expectedValue = "ja-JP-u-ca-buddhist-cu-mxn-fw-tue-hc-h12-ms-metric"; ++ ++ int32_t neededBufferSize = uprefs_getBCP47Tag(nullptr, 0, &status); ++ ++ if ( uprefs_getBCP47Tag(languageBuffer, neededBufferSize, &status) != 51) ++ { ++ errln("Expected length to be 51, but got: %d\n",uprv_strlen(languageBuffer)); ++ } ++ if ( uprv_strcmp(expectedValue, languageBuffer) != 0) ++ { ++ errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); ++ } ++} ++ ++void UPrefsTest::TestGetNeededBuffer() ++{ ++ char languageBuffer[ARRAY_SIZE] = {0}; ++ language = L"zh-SG_stroke"; ++ currency = L"MXN"; ++ hourCycle = L"hh:mm:ss"; ++ firstday = 1; ++ measureSystem = 0; ++ calendar = CAL_THAI; ++ UErrorCode status = U_ZERO_ERROR; ++ char* expectedValue = "zh-SG-u-ca-buddhist-co-stroke-cu-mxn-fw-tue-hc-h12-ms-metric"; ++ ++ int32_t neededBufferSize = uprefs_getBCP47Tag(nullptr, 0, &status); ++ ++ if ( neededBufferSize != 61) ++ { ++ errln("Expected buffer size to be 61, but got: %d\n",uprv_strlen(languageBuffer)); ++ } ++ if ( uprefs_getBCP47Tag(languageBuffer, neededBufferSize, &status) != 61) ++ { ++ errln("Expected length to be 61, but got: %d\n",uprv_strlen(languageBuffer)); ++ } ++ if ( uprv_strcmp(expectedValue, languageBuffer) != 0) ++ { ++ errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); ++ } ++} ++ ++void UPrefsTest::TestGetUnsupportedSorting() ++{ ++ char languageBuffer[ARRAY_SIZE] = {0}; ++ language = L"hu-HU_technl"; ++ currency = L"MXN"; ++ hourCycle = L"hh:mm:ss"; ++ firstday = 1; ++ measureSystem = 0; ++ calendar = CAL_THAI; ++ UErrorCode status = U_ZERO_ERROR; ++ char* expectedValue = "hu-HU-u-ca-buddhist-cu-mxn-fw-tue-hc-h12-ms-metric"; ++ ++ if ( uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 51) ++ { ++ errln("Expected length to be 51, but got: %d\n",uprv_strlen(languageBuffer)); ++ } ++ if ( uprv_strcmp(expectedValue, languageBuffer) != 0) ++ { ++ errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); ++ } ++} ++ ++void UPrefsTest::Get24HourCycleMixed() ++{ ++ char languageBuffer[ARRAY_SIZE] = {0}; ++ language = L"ja-JP"; ++ currency = L"MXN"; ++ hourCycle = L"HHhh:mm:ss"; ++ firstday = 1; ++ measureSystem = 0; ++ calendar = CAL_THAI; ++ UErrorCode status = U_ZERO_ERROR; ++ char* expectedValue = "ja-JP-u-ca-buddhist-cu-mxn-fw-tue-hc-h23-ms-metric"; ++ ++ if (uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 51) ++ { ++ errln("Expected length to be 51, but got: %d\n", uprv_strlen(languageBuffer)); ++ } ++ if (uprv_strcmp(expectedValue, languageBuffer) != 0) ++ { ++ errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); ++ } ++} ++ ++void UPrefsTest::Get12HourCycleMixed() ++{ ++ char languageBuffer[ARRAY_SIZE] = {0}; ++ language = L"ja-JP"; ++ currency = L"MXN"; ++ hourCycle = L"hHhH:mm:ss"; ++ firstday = 1; ++ measureSystem = 0; ++ calendar = CAL_THAI; ++ UErrorCode status = U_ZERO_ERROR; ++ char* expectedValue = "ja-JP-u-ca-buddhist-cu-mxn-fw-tue-hc-h12-ms-metric"; ++ ++ if (uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 51) ++ { ++ errln("Expected length to be 51, but got: %d\n", uprv_strlen(languageBuffer)); ++ } ++ if (uprv_strcmp(expectedValue, languageBuffer) != 0) ++ { ++ errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); ++ } ++} ++ ++ ++void UPrefsTest::Get12HourCycleMixed2() ++{ ++ char languageBuffer[ARRAY_SIZE] = {0}; ++ language = L"ja-JP"; ++ currency = L"MXN"; ++ hourCycle = L"hH''h'H'H:mm:ss"; ++ firstday = 1; ++ measureSystem = 0; ++ calendar = CAL_THAI; ++ UErrorCode status = U_ZERO_ERROR; ++ char* expectedValue = "ja-JP-u-ca-buddhist-cu-mxn-fw-tue-hc-h12-ms-metric"; ++ ++ if (uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 51) ++ { ++ errln("Expected length to be 51, but got: %d\n", uprv_strlen(languageBuffer)); ++ } ++ if (uprv_strcmp(expectedValue, languageBuffer) != 0) ++ { ++ errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); ++ } ++} ++ ++void UPrefsTest::Get12HourCycle() ++{ ++ char languageBuffer[ARRAY_SIZE] = {0}; ++ language = L"ja-JP"; ++ currency = L"MXN"; ++ hourCycle = L"h'H'h:mm:ss"; ++ firstday = 1; ++ measureSystem = 0; ++ calendar = CAL_THAI; ++ UErrorCode status = U_ZERO_ERROR; ++ char* expectedValue = "ja-JP-u-ca-buddhist-cu-mxn-fw-tue-hc-h12-ms-metric"; ++ ++ if (uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 51) ++ { ++ errln("Expected length to be 51, but got: %d\n", uprv_strlen(languageBuffer)); ++ } ++ if (uprv_strcmp(expectedValue, languageBuffer) != 0) ++ { ++ errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); ++ } ++} ++ ++void UPrefsTest::Get12HourCycle2() ++{ ++ char languageBuffer[ARRAY_SIZE] = {0}; ++ language = L"ja-JP"; ++ currency = L"MXN"; ++ hourCycle = L"'H'h'H'h:mm:ss"; ++ firstday = 1; ++ measureSystem = 0; ++ calendar = CAL_THAI; ++ UErrorCode status = U_ZERO_ERROR; ++ char* expectedValue = "ja-JP-u-ca-buddhist-cu-mxn-fw-tue-hc-h12-ms-metric"; ++ ++ if (uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 51) ++ { ++ errln("Expected length to be 51, but got: %d\n", uprv_strlen(languageBuffer)); ++ } ++ if (uprv_strcmp(expectedValue, languageBuffer) != 0) ++ { ++ errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); ++ } ++} ++#endif //U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY +\ No newline at end of file +diff --git a/icu/icu4c/source/test/intltest/uprefstest.h b/icu/icu4c/source/test/intltest/uprefstest.h +new file mode 100644 +index 0000000000000000000000000000000000000000..d87452e6bbde65a03df43d549e01abd3890c557c +--- /dev/null ++++ b/icu/icu4c/source/test/intltest/uprefstest.h +@@ -0,0 +1,50 @@ ++// © 2016 and later: Unicode, Inc. and others. ++// License & terms of use: http://www.unicode.org/copyright.html ++#ifndef UPREFSTEST_H ++#define UPREFSTEST_H ++ ++#include "unicode/platform.h" ++#if U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY ++// We define UPREFS_TEST to use the mock version of GetLocaleInfoEx(), which ++// allows us to simulate its behaviour and determine if the results given by the ++// API align with what we expect to receive ++#define UPREFS_TEST 1 ++ ++ ++#include "windows.h" ++#include "intltest.h" ++#include "uprefs.h" ++ ++class UPrefsTest: public IntlTest { ++private: ++ static std::wstring language; ++ static std::wstring currency; ++ static std::wstring hourCycle; ++ static int32_t firstday; ++ static int32_t measureSystem; ++ static CALID calendar; ++ ++public: ++ UPrefsTest(){}; ++ virtual ~UPrefsTest(){}; ++ ++ virtual void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = NULL) override; ++ int32_t MockGetLocaleInfoEx(LPCWSTR lpLocaleName, LCTYPE LCType, LPWSTR lpLCData, int cchData, UErrorCode* status); ++ void TestGetDefaultLocaleAsBCP47Tag(); ++ void TestBCP47TagWithSorting(); ++ void TestBCP47TagChineseSimplified(); ++ void TestBCP47TagChineseSortingStroke(); ++ void TestBCP47TagJapanCalendar(); ++ void TestUseNeededBuffer(); ++ void TestGetNeededBuffer(); ++ void TestGetUnsupportedSorting(); ++ void Get24HourCycleMixed(); ++ void Get12HourCycleMixed(); ++ void Get12HourCycleMixed2(); ++ void Get12HourCycle(); ++ void Get12HourCycle2(); ++}; ++ ++ ++#endif //U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY ++#endif //UPREFSTEST_H +\ No newline at end of file diff --git a/icu/icu4c/source/common/common.vcxproj b/icu/icu4c/source/common/common.vcxproj index f8f28ad768c..305b7054308 100644 --- a/icu/icu4c/source/common/common.vcxproj +++ b/icu/icu4c/source/common/common.vcxproj @@ -282,6 +282,7 @@ + @@ -397,6 +398,7 @@ + diff --git a/icu/icu4c/source/common/common_uwp.vcxproj b/icu/icu4c/source/common/common_uwp.vcxproj index a57917292a7..0e346ccec77 100644 --- a/icu/icu4c/source/common/common_uwp.vcxproj +++ b/icu/icu4c/source/common/common_uwp.vcxproj @@ -404,6 +404,7 @@ + @@ -520,6 +521,7 @@ + diff --git a/icu/icu4c/source/common/putil.cpp b/icu/icu4c/source/common/putil.cpp index 3ed6a05d22d..4d80d514f84 100644 --- a/icu/icu4c/source/common/putil.cpp +++ b/icu/icu4c/source/common/putil.cpp @@ -71,6 +71,7 @@ #include "locmap.h" #include "ucln_cmn.h" #include "charstr.h" +#include "uprefs.h" /* Include standard headers. */ #include @@ -1776,10 +1777,37 @@ The leftmost codepage (.xxx) wins. return posixID; #elif U_PLATFORM_USES_ONLY_WIN32_API -#define POSIX_LOCALE_CAPACITY 64 UErrorCode status = U_ZERO_ERROR; char *correctedPOSIXLocale = nullptr; +#if UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY + + int32_t neededBufferSize = uprefs_getBCP47Tag(nullptr, 0, &status); + MaybeStackArray windowsLocale(neededBufferSize, status); + int32_t length = uprefs_getBCP47Tag(windowsLocale.getAlias(), neededBufferSize, &status); + + if (length > 0) // If length is 0, then the call to uprefs_getBCP47Tag failed. + { + // Now normalize the resulting name + correctedPOSIXLocale = static_cast(uprv_malloc(length * 2)); + /* TODO: Should we just exit on memory allocation failure? */ + if (correctedPOSIXLocale) + { + int32_t posixLen = uloc_canonicalize(windowsLocale.getAlias(), correctedPOSIXLocale, length * 2, &status); + if (U_SUCCESS(status)) + { + *(correctedPOSIXLocale + posixLen) = 0; + gCorrectedPOSIXLocale = correctedPOSIXLocale; + gCorrectedPOSIXLocaleHeapAllocated = true; + ucln_common_registerCleanup(UCLN_COMMON_PUTIL, putil_cleanup); + } + else + { + uprv_free(correctedPOSIXLocale); + } + } + } +#else // If we have already figured this out just use the cached value if (gCorrectedPOSIXLocale != nullptr) { return gCorrectedPOSIXLocale; @@ -1821,11 +1849,11 @@ The leftmost codepage (.xxx) wins. } // Now normalize the resulting name - correctedPOSIXLocale = static_cast(uprv_malloc(POSIX_LOCALE_CAPACITY + 1)); + correctedPOSIXLocale = static_cast(uprv_malloc(length * 2)); /* TODO: Should we just exit on memory allocation failure? */ if (correctedPOSIXLocale) { - int32_t posixLen = uloc_canonicalize(modifiedWindowsLocale, correctedPOSIXLocale, POSIX_LOCALE_CAPACITY, &status); + int32_t posixLen = uloc_canonicalize(modifiedWindowsLocale, correctedPOSIXLocale, length * 2, &status); if (U_SUCCESS(status)) { *(correctedPOSIXLocale + posixLen) = 0; @@ -1839,6 +1867,7 @@ The leftmost codepage (.xxx) wins. } } } +#endif // If unable to find a locale we can agree upon, use en-US by default if (gCorrectedPOSIXLocale == nullptr) { diff --git a/icu/icu4c/source/common/sources.txt b/icu/icu4c/source/common/sources.txt index e0410daaa47..3ebc3c30113 100644 --- a/icu/icu4c/source/common/sources.txt +++ b/icu/icu4c/source/common/sources.txt @@ -157,6 +157,7 @@ unistr_titlecase_brkiter.cpp unorm.cpp unormcmp.cpp uobject.cpp +uprefs.cpp uprops.cpp ures_cnv.cpp uresbund.cpp diff --git a/icu/icu4c/source/common/unicode/uconfig.h b/icu/icu4c/source/common/unicode/uconfig.h index c4239fc9997..da702d2d812 100644 --- a/icu/icu4c/source/common/unicode/uconfig.h +++ b/icu/icu4c/source/common/unicode/uconfig.h @@ -390,6 +390,22 @@ # define UCONFIG_USE_WINDOWS_LCID_MAPPING_API 1 #endif +/** + * \def UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY + * On Windows platforms (ie: U_PLATFORM_HAS_WIN32_API is true), this switch enables ICU to + * detect additional user preferences by setting BCP47 Unicode extension within the default locale. + * This includes information such as calendar, currency, hour cycle, among others. + * + * If this switch is off (or set to 0) then the default behavior of only detecting the language + * and country/region occurs. + * + * For example, the default locale may be detected as "es-MX-u-hc-h24", instead of "es-MX", + * if the user has selected a 24 hour clock option. +*/ +#ifndef UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY +# define UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY 1 +#endif + //IGNORE_WINDOWS_HEADERS_END /* i18n library switches ---------------------------------------------------- */ diff --git a/icu/icu4c/source/common/unicode/uvernum.h b/icu/icu4c/source/common/unicode/uvernum.h index 41610a33a82..59731aa116e 100644 --- a/icu/icu4c/source/common/unicode/uvernum.h +++ b/icu/icu4c/source/common/unicode/uvernum.h @@ -79,7 +79,7 @@ * @stable ICU 4.0 */ #ifndef U_ICU_VERSION_BUILDLEVEL_NUM -#define U_ICU_VERSION_BUILDLEVEL_NUM 9 +#define U_ICU_VERSION_BUILDLEVEL_NUM 10 #endif /** Glued version suffix for renamers @@ -139,7 +139,7 @@ * This value will change in the subsequent releases of ICU * @stable ICU 2.4 */ -#define U_ICU_VERSION "68.2.0.9" +#define U_ICU_VERSION "68.2.0.10" /** * The current ICU library major version number as a string, for library name suffixes. diff --git a/icu/icu4c/source/common/uprefs.cpp b/icu/icu4c/source/common/uprefs.cpp new file mode 100644 index 00000000000..b055cbe86f6 --- /dev/null +++ b/icu/icu4c/source/common/uprefs.cpp @@ -0,0 +1,553 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "uprefs.h" +#if U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY +#include "unicode/ustring.h" +#include "cmemory.h" +#include "charstr.h" +#include "cstring.h" +#include "cwchar.h" +#include + +U_NAMESPACE_USE + +// Older versions of the Windows SDK don’t have definitions for calendar types that were added later on. +// (For example, the Windows 7 SDK doesn’t have CAL_PERSIAN). +// So we’ll need to provide our own definitions for some of them. +// Note that on older versions of the OS these values won't ever be returned by the platform APIs, so providing our own definitions is fine. +#ifndef CAL_PERSIAN +#define CAL_PERSIAN 22 // Persian (Solar Hijri) calendar +#endif + +#define RETURN_FAILURE_STRING_WITH_STATUS_IF(condition, error, status) \ + if (condition) \ + { \ + *status = error; \ + return CharString(); \ + } + +#define RETURN_FAILURE_WITH_STATUS_IF(condition, error, status) \ + if (condition) \ + { \ + *status = error; \ + return 0; \ + } + +#define RETURN_VALUE_IF(condition, value) \ + if (condition) \ + { \ + return value; \ + } + +#define RETURN_WITH_ALLOCATION_ERROR_IF_FAILED(status) \ + if (U_FAILURE(*status)) \ + { \ + *status = U_MEMORY_ALLOCATION_ERROR; \ + return CharString(); \ + } \ +// ------------------------------------------------------- +// ----------------- MAPPING FUNCTIONS-------------------- +// ------------------------------------------------------- + +// Maps from a NLS Calendar ID (CALID) to a BCP47 Unicode Extension calendar identifier. +// +// We map the NLS CALID from GetLocaleInfoEx to the calendar identifier +// used in BCP47 tag with Unicode Extensions. +// +// This does not return a full nor valid BCP47Tag, it only returns the option that the BCP47 tag +// would return after the "ca-" part +// +// For example: +// CAL_GREGORIAN would return "gregory". +// CAL_HIJRI would return "islamic". +// +// These could be used in a BCP47 tag like this: "en-US-u-ca-gregory". +// Note that there are some NLS calendars that are not supported with the BCP47 U extensions, +// and vice-versa. +// +// NLS CALID reference:https://docs.microsoft.com/en-us/windows/win32/intl/calendar-identifiers +CharString getCalendarBCP47FromNLSType(int32_t calendar, UErrorCode* status) +{ + switch(calendar) + { + case CAL_GREGORIAN: + case CAL_GREGORIAN_US: + case CAL_GREGORIAN_ME_FRENCH: + case CAL_GREGORIAN_ARABIC: + case CAL_GREGORIAN_XLIT_ENGLISH: + case CAL_GREGORIAN_XLIT_FRENCH: + return CharString("gregory", *status); + + case CAL_JAPAN: + return CharString("japanese", *status); + + case CAL_TAIWAN: + return CharString("roc", *status); + + case CAL_KOREA: + return CharString("dangi", *status); + + case CAL_HIJRI: + return CharString("islamic", *status); + + case CAL_THAI: + return CharString("buddhist", *status); + + case CAL_HEBREW: + return CharString("hebrew", *status); + + case CAL_PERSIAN: + return CharString("persian", *status); + + case CAL_UMALQURA: + return CharString("islamic-umalqura", *status); + + default: + return CharString(); + } +} + +// Maps from a NLS Alternate sorting system to a BCP47 U extension sorting system. +// +// We map the alternate sorting method from GetLocaleInfoEx to the sorting method +// used in BCP47 tag with Unicode Extensions. +// +// This does not return a full nor valid BCP47Tag, it only returns the option that the BCP47 tag +// would return after the "co-" part +// +// For example: +// "phoneb" (parsed from "de-DE_phoneb") would return "phonebk". +// "radstr" (parsed from "ja-JP_radstr") would return "unihan". +// +// These could be used in a BCP47 tag like this: "de-DE-u-co-phonebk". +// Note that there are some NLS Alternate sort methods that are not supported with the BCP47 U extensions, +// and vice-versa. +CharString getSortingSystemBCP47FromNLSType(const wchar_t* sortingSystem, UErrorCode* status) +{ + if (wcscmp(sortingSystem, L"phoneb") == 0) // Phonebook style ordering (such as in German) + { + return CharString("phonebk", *status); + } + else if (wcscmp(sortingSystem, L"tradnl") == 0) // Traditional style ordering (such as in Spanish) + { + return CharString("trad", *status); + } + else if (wcscmp(sortingSystem, L"stroke") == 0) // Pinyin ordering for Latin, stroke order for CJK characters (used in Chinese) + { + return CharString("stroke", *status); + } + else if (wcscmp(sortingSystem, L"radstr") == 0) // Pinyin ordering for Latin, Unihan radical-stroke ordering for CJK characters (used in Chinese) + { + return CharString("unihan", *status); + } + else if (wcscmp(sortingSystem, L"pronun") == 0) // Phonetic ordering (sorting based on pronunciation) + { + return CharString("phonetic", *status); + } + else + { + return CharString(); + } +} + +// Maps from a NLS first day of week value to a BCP47 U extension first day of week. +// +// NLS defines: +// 0 -> Monday, 1 -> Tuesday, ... 5 -> Saturday, 6 -> Sunday +// +// We map the first day of week from GetLocaleInfoEx to the first day of week +// used in BCP47 tag with Unicode Extensions. +// +// This does not return a full nor valid BCP47Tag, it only returns the option that the BCP47 tag +// would return after the "fw-" part +// +// For example: +// 1 (Tuesday) would return "tue". +// 6 (Sunday) would return "sun". +// +// These could be used in a BCP47 tag like this: "en-US-u-fw-sun". +CharString getFirstDayBCP47FromNLSType(int32_t firstday, UErrorCode* status) +{ + switch(firstday) + { + case 0: + return CharString("mon", *status); + + case 1: + return CharString("tue", *status); + + case 2: + return CharString("wed", *status); + + case 3: + return CharString("thu", *status); + + case 4: + return CharString("fri", *status); + + case 5: + return CharString("sat", *status); + + case 6: + return CharString("sun", *status); + + default: + return CharString(); + } +} + +// Maps from a NLS Measurement system to a BCP47 U extension measurement system. +// +// NLS defines: +// 0 -> Metric system, 1 -> U.S. System +// +// This does not return a full nor valid BCP47Tag, it only returns the option that the BCP47 tag +// would return after the "ms-" part +// +// For example: +// 0 (Metric) would return "metric". +// 6 (U.S. System) would return "ussystem". +// +// These could be used in a BCP47 tag like this: "en-US-u-ms-metric". +CharString getMeasureSystemBCP47FromNLSType(int32_t measureSystem, UErrorCode *status) +{ + switch(measureSystem) + { + case 0: + return CharString("metric", *status); + case 1: + return CharString("ussystem", *status); + default: + return CharString(); + } +} + +// ------------------------------------------------------- +// --------------- END OF MAPPING FUNCTIONS -------------- +// ------------------------------------------------------- + +// ------------------------------------------------------- +// ------------------ HELPER FUCTIONS ------------------- +// ------------------------------------------------------- + +// Return the CLDR "h12" or "h23" format for the 12 or 24 hour clock. +// NLS only gives us a "time format" of a form similar to "h:mm:ss tt" +// The NLS "h" is 12 hour, and "H" is 24 hour, so we'll scan for the +// first h or H. +// Note that the NLS string could have sections escaped with single +// quotes, so be sure to skip those parts. Eg: "'Hours:' h:mm:ss" +// would skip the "H" in 'Hours' and use the h in the actual pattern. +CharString get12_or_24hourFormat(wchar_t* hourFormat, UErrorCode* status) +{ + bool isInEscapedString = false; + const int32_t hourLength = static_cast(uprv_wcslen(hourFormat)); + for (int32_t i = 0; i < hourLength; i++) + { + // Toggle escaped flag if in ' quoted portion + if (hourFormat[i] == L'\'') + { + isInEscapedString = !isInEscapedString; + } + + if (!isInEscapedString) + { + // Check for both so we can escape early + if (hourFormat[i] == L'H') + { + return CharString("h23", *status); + } + + if (hourFormat[i] == L'h') + { + return CharString("h12", *status); + } + } + } + // default to a 24 hour clock as that's more common worldwide + return CharString("h23", *status); +} + +UErrorCode getUErrorCodeFromLastError() +{ + DWORD error = GetLastError(); + switch(error) + { + case ERROR_INSUFFICIENT_BUFFER: + return U_BUFFER_OVERFLOW_ERROR; + + case ERROR_INVALID_FLAGS: + case ERROR_INVALID_PARAMETER: + return U_ILLEGAL_ARGUMENT_ERROR; + + case ERROR_OUTOFMEMORY: + return U_MEMORY_ALLOCATION_ERROR; + + default: + return U_INTERNAL_PROGRAM_ERROR; + } +} + +int32_t GetLocaleInfoExWrapper(LPCWSTR lpLocaleName, LCTYPE LCType, LPWSTR lpLCData, int cchData, UErrorCode* status) +{ + RETURN_VALUE_IF(U_FAILURE(*status), 0); + +#ifndef UPREFS_TEST + *status = U_ZERO_ERROR; + int32_t result = GetLocaleInfoEx(lpLocaleName, LCType, lpLCData, cchData); + + if (result == 0) + { + *status = getUErrorCodeFromLastError(); + } + return result; +#else + #include "uprefstest.h" + UPrefsTest prefTests; + return prefTests.MockGetLocaleInfoEx(lpLocaleName, LCType, lpLCData, cchData, status); +#endif +} + +// Copies a string to a buffer if its size allows it and returns the size. +// The returned needed buffer size includes the terminating \0 null character. +// If the buffer's size is set to 0, the needed buffer size is returned before copying the string. +int32_t checkBufferCapacityAndCopy(const char* uprefsString, char* uprefsBuffer, int32_t bufferSize, UErrorCode* status) +{ + int32_t neededBufferSize = static_cast(uprv_strlen(uprefsString) + 1); + + RETURN_VALUE_IF(bufferSize == 0, neededBufferSize); + RETURN_FAILURE_WITH_STATUS_IF(neededBufferSize > bufferSize, U_BUFFER_OVERFLOW_ERROR, status); + + uprv_strcpy(uprefsBuffer, uprefsString); + + return neededBufferSize; +} + +CharString getLocaleBCP47Tag_impl(UErrorCode* status, bool getSorting) +{ + // First part of a bcp47 tag looks like an NLS user locale, so we get the NLS user locale. + int32_t neededBufferSize = GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_SNAME, nullptr, 0, status); + + RETURN_VALUE_IF(U_FAILURE(*status), CharString()); + + MaybeStackArray NLSLocale(neededBufferSize, *status); + RETURN_WITH_ALLOCATION_ERROR_IF_FAILED(status); + + int32_t result = GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_SNAME, NLSLocale.getAlias(), neededBufferSize, status); + + RETURN_VALUE_IF(U_FAILURE(*status), CharString()); + + if (getSorting) //We determine if we want the locale (for example, en-US) or the sorting method (for example, phonebk) + { + // We use LOCALE_SNAME to get the sorting method (if any). So we need to keep + // only the sorting bit after the _, removing the locale name. + // Example: from "de-DE_phoneb" we only want "phoneb" + const wchar_t * startPosition = wcschr(NLSLocale.getAlias(), L'_'); + + // Note: not finding a "_" is not an error, it means the user has not selected an alternate sorting method, which is fine. + if (startPosition != nullptr) + { + CharString sortingSystem = getSortingSystemBCP47FromNLSType(startPosition + 1, status); + + if (sortingSystem.length() == 0) + { + *status = U_UNSUPPORTED_ERROR; + return CharString(); + } + return sortingSystem; + } + } + else + { + // The NLS locale may include a non-default sort, such as de-DE_phoneb. We only want the locale name before the _. + wchar_t * position = wcschr(NLSLocale.getAlias(), L'_'); + if (position != nullptr) + { + *position = L'\0'; + } + + CharString languageTag; + int32_t resultCapacity = 0; + languageTag.getAppendBuffer(neededBufferSize, neededBufferSize, resultCapacity, *status); + RETURN_WITH_ALLOCATION_ERROR_IF_FAILED(status); + + int32_t unitsWritten = 0; + u_strToUTF8(languageTag.data(), neededBufferSize, &unitsWritten, reinterpret_cast(NLSLocale.getAlias()), neededBufferSize, status); + RETURN_VALUE_IF(U_FAILURE(*status), CharString()); + return languageTag; + } + + return CharString(); +} + +CharString getCalendarSystem_impl(UErrorCode* status) +{ + int32_t NLSCalendar = 0; + + GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_ICALENDARTYPE | LOCALE_RETURN_NUMBER, reinterpret_cast(&NLSCalendar), sizeof(NLSCalendar) / sizeof(wchar_t), status); + + RETURN_VALUE_IF(U_FAILURE(*status), CharString()); + + CharString calendar(getCalendarBCP47FromNLSType(NLSCalendar, status), *status); + RETURN_FAILURE_STRING_WITH_STATUS_IF(calendar.length() == 0, U_UNSUPPORTED_ERROR, status); + + return calendar; +} + +CharString getCurrencyCode_impl(UErrorCode* status) +{ + int32_t neededBufferSize = GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_SINTLSYMBOL, nullptr, 0, status); + + RETURN_VALUE_IF(U_FAILURE(*status), CharString()); + + MaybeStackArray NLScurrencyData(neededBufferSize, *status); + RETURN_WITH_ALLOCATION_ERROR_IF_FAILED(status); + + int32_t result = GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_SINTLSYMBOL, NLScurrencyData.getAlias(), neededBufferSize, status); + + RETURN_VALUE_IF(U_FAILURE(*status), CharString()); + + MaybeStackArray currency(neededBufferSize, *status); + RETURN_WITH_ALLOCATION_ERROR_IF_FAILED(status); + + int32_t unitsWritten = 0; + u_strToUTF8(currency.getAlias(), neededBufferSize, &unitsWritten, reinterpret_cast(NLScurrencyData.getAlias()), neededBufferSize, status); + RETURN_VALUE_IF(U_FAILURE(*status), CharString()); + + if (unitsWritten == 0) + { + *status = U_INTERNAL_PROGRAM_ERROR; + return CharString(); + } + + // Since we retreived the currency code in caps, we need to make it lowercase for it to be in CLDR BCP47 U extensions format. + T_CString_toLowerCase(currency.getAlias()); + + return CharString(currency.getAlias(), neededBufferSize, *status); +} + +CharString getFirstDayOfWeek_impl(UErrorCode* status) +{ + int32_t NLSfirstDay = 0; + GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK | LOCALE_RETURN_NUMBER, reinterpret_cast(&NLSfirstDay), sizeof(NLSfirstDay) / sizeof(wchar_t), status); + + RETURN_VALUE_IF(U_FAILURE(*status), CharString()); + + CharString firstDay = getFirstDayBCP47FromNLSType(NLSfirstDay, status); + RETURN_FAILURE_STRING_WITH_STATUS_IF(firstDay.length() == 0, U_UNSUPPORTED_ERROR, status); + + return firstDay; +} + +CharString getHourCycle_impl(UErrorCode* status) +{ + int32_t neededBufferSize = GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_STIMEFORMAT, nullptr, 0, status); + + RETURN_VALUE_IF(U_FAILURE(*status), CharString()); + + MaybeStackArray NLShourCycle(neededBufferSize, *status); + RETURN_WITH_ALLOCATION_ERROR_IF_FAILED(status); + + int32_t result = GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_STIMEFORMAT, NLShourCycle.getAlias(), neededBufferSize, status); + + RETURN_VALUE_IF(U_FAILURE(*status), CharString()); + + CharString hourCycle = get12_or_24hourFormat(NLShourCycle.getAlias(), status); + if (hourCycle.length() == 0) + { + *status = U_INTERNAL_PROGRAM_ERROR; + return CharString(); + } + return hourCycle; +} + +CharString getMeasureSystem_impl(UErrorCode* status) +{ + int32_t NLSmeasureSystem = 0; + GetLocaleInfoExWrapper(LOCALE_NAME_USER_DEFAULT, LOCALE_IMEASURE | LOCALE_RETURN_NUMBER, reinterpret_cast(&NLSmeasureSystem), sizeof(NLSmeasureSystem) / sizeof(wchar_t), status); + + RETURN_VALUE_IF(U_FAILURE(*status), CharString()); + + CharString measureSystem = getMeasureSystemBCP47FromNLSType(NLSmeasureSystem, status); + RETURN_FAILURE_STRING_WITH_STATUS_IF(measureSystem.length() == 0, U_UNSUPPORTED_ERROR, status); + + return measureSystem; +} + +void appendIfDataNotEmpty(CharString& dest, const char* firstData, const char* secondData, bool& warningGenerated, UErrorCode* status) +{ + if (*status == U_UNSUPPORTED_ERROR) + { + warningGenerated = true; + *status = U_ZERO_ERROR; + } + + if (uprv_strlen(secondData) != 0) + { + dest.append(firstData, *status); + dest.append(secondData, *status); + } +} +// ------------------------------------------------------- +// --------------- END OF HELPER FUNCTIONS --------------- +// ------------------------------------------------------- + + +// ------------------------------------------------------- +// ---------------------- APIs --------------------------- +// ------------------------------------------------------- + +// Gets the valid and canonical BCP47 tag with the user settings for Language, Calendar, Sorting, Currency, +// First day of week, Hour cycle, and Measurement system. +// Calls all of the other APIs +// Returns the needed buffer size for the BCP47 Tag. +int32_t uprefs_getBCP47Tag(char* uprefsBuffer, int32_t bufferSize, UErrorCode* status) +{ + RETURN_FAILURE_WITH_STATUS_IF(uprefsBuffer == nullptr && bufferSize != 0, U_ILLEGAL_ARGUMENT_ERROR, status); + + *status = U_ZERO_ERROR; + CharString BCP47Tag; + bool warningGenerated = false; + + CharString languageTag = getLocaleBCP47Tag_impl(status, false); + RETURN_VALUE_IF(U_FAILURE(*status), 0); + BCP47Tag.append(languageTag.data(), *status); + BCP47Tag.append("-u", *status); + + CharString calendar = getCalendarSystem_impl(status); + RETURN_VALUE_IF(U_FAILURE(*status) && *status != U_UNSUPPORTED_ERROR, 0); + appendIfDataNotEmpty(BCP47Tag, "-ca-", calendar.data(), warningGenerated, status); + + CharString sortingSystem = getLocaleBCP47Tag_impl(status, true); + RETURN_VALUE_IF(U_FAILURE(*status) && *status != U_UNSUPPORTED_ERROR, 0); + appendIfDataNotEmpty(BCP47Tag, "-co-", sortingSystem.data(), warningGenerated, status); + + CharString currency = getCurrencyCode_impl(status); + RETURN_VALUE_IF(U_FAILURE(*status) && *status != U_UNSUPPORTED_ERROR, 0); + appendIfDataNotEmpty(BCP47Tag, "-cu-", currency.data(), warningGenerated, status); + + CharString firstDay = getFirstDayOfWeek_impl(status); + RETURN_VALUE_IF(U_FAILURE(*status) && *status != U_UNSUPPORTED_ERROR, 0); + appendIfDataNotEmpty(BCP47Tag, "-fw-", firstDay.data(), warningGenerated, status); + + CharString hourCycle = getHourCycle_impl(status); + RETURN_VALUE_IF(U_FAILURE(*status) && *status != U_UNSUPPORTED_ERROR, 0); + appendIfDataNotEmpty(BCP47Tag, "-hc-", hourCycle.data(), warningGenerated, status); + + CharString measureSystem = getMeasureSystem_impl(status); + RETURN_VALUE_IF(U_FAILURE(*status) && *status != U_UNSUPPORTED_ERROR, 0); + appendIfDataNotEmpty(BCP47Tag, "-ms-", measureSystem.data(), warningGenerated, status); + + if (warningGenerated) + { + *status = U_USING_FALLBACK_WARNING; + } + + return checkBufferCapacityAndCopy(BCP47Tag.data(), uprefsBuffer, bufferSize, status); +} + +// ------------------------------------------------------- +// ---------------------- END OF APIs -------------------- +// ------------------------------------------------------- + +#endif // U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY \ No newline at end of file diff --git a/icu/icu4c/source/common/uprefs.h b/icu/icu4c/source/common/uprefs.h new file mode 100644 index 00000000000..2d77d0c9e7f --- /dev/null +++ b/icu/icu4c/source/common/uprefs.h @@ -0,0 +1,29 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#ifndef UPREFS_H +#define UPREFS_H + +#include "unicode/platform.h" +#include "unicode/utypes.h" +#if U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY + +/** +* Gets the valid and canonical BCP47 tag with the user settings for Language, Calendar, Sorting, Currency, +* First day of week, Hour cycle, and Measurement system when available. +* +* @param uprefsBuffer Pointer to a buffer in which this function retrieves the BCP47 tag. +* This pointer is not used if bufferSize is set to 0. +* @param bufferSize Size, in characters, of the data buffer indicated by uprefsBuffer. Alternatively, the application +* can set this parameter to 0. In this case, the function does not use the uprefsBuffer parameter +* and returns the required buffer size, including the terminating null character. +* @param status: Pointer to a UErrorCode. The resulting value will be U_ZERO_ERROR if the call was successful or will +* contain an error or warning code. If the status is U_USING_FALLBACK_WARNING, it means at least one of the + settings was not succesfully mapped between NLS and CLDR, so it will not be shown on the BCP47 tag. +* @return The needed buffer size, including the terminating \0 null character if the call was successful, should be ignored +* if status was not U_ZERO_ERROR. +*/ +int32_t uprefs_getBCP47Tag(char* uprefsBuffer, int32_t bufferSize, UErrorCode* status); + +#endif //U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY +#endif //UPREFS_H \ No newline at end of file diff --git a/icu/icu4c/source/test/intltest/Makefile.in b/icu/icu4c/source/test/intltest/Makefile.in index 13d3ea86dc9..5f8822cedac 100644 --- a/icu/icu4c/source/test/intltest/Makefile.in +++ b/icu/icu4c/source/test/intltest/Makefile.in @@ -69,7 +69,7 @@ string_segment_test.o \ numbertest_parse.o numbertest_doubleconversion.o numbertest_skeletons.o \ static_unisets_test.o numfmtdatadriventest.o numbertest_range.o erarulestest.o \ formattedvaluetest.o formatted_string_builder_test.o numbertest_permutation.o \ -units_data_test.o units_router_test.o units_test.o +units_data_test.o units_router_test.o units_test.o uprefstest.o DEPS = $(OBJECTS:.o=.d) diff --git a/icu/icu4c/source/test/intltest/intltest.vcxproj b/icu/icu4c/source/test/intltest/intltest.vcxproj index 319c3ab58f6..20da05e608a 100644 --- a/icu/icu4c/source/test/intltest/intltest.vcxproj +++ b/icu/icu4c/source/test/intltest/intltest.vcxproj @@ -288,6 +288,7 @@ + @@ -419,6 +420,7 @@ + diff --git a/icu/icu4c/source/test/intltest/itutil.cpp b/icu/icu4c/source/test/intltest/itutil.cpp index 228dbf2f218..2f7ba222785 100644 --- a/icu/icu4c/source/test/intltest/itutil.cpp +++ b/icu/icu4c/source/test/intltest/itutil.cpp @@ -33,6 +33,9 @@ #include "uvectest.h" #include "aliastst.h" #include "usettest.h" +#if U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY + #include "uprefstest.h" +#endif extern IntlTest *createBytesTrieTest(); extern IntlTest *createLocaleMatcherTest(); @@ -67,6 +70,9 @@ void IntlTestUtilities::runIndexedTest( int32_t index, UBool exec, const char* & TESTCASE_AUTO_CLASS(LocaleAliasTest); TESTCASE_AUTO_CLASS(UnicodeSetTest); TESTCASE_AUTO_CLASS(ErrorCodeTest); +#if U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY + TESTCASE_AUTO_CLASS(UPrefsTest); +#endif TESTCASE_AUTO_CREATE_CLASS(LocalPointerTest); TESTCASE_AUTO_CREATE_CLASS(BytesTrieTest); TESTCASE_AUTO_CREATE_CLASS(UCharsTrieTest); diff --git a/icu/icu4c/source/test/intltest/uprefstest.cpp b/icu/icu4c/source/test/intltest/uprefstest.cpp new file mode 100644 index 00000000000..f76997c66c4 --- /dev/null +++ b/icu/icu4c/source/test/intltest/uprefstest.cpp @@ -0,0 +1,437 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html +#include "uprefstest.h" +#if U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY + +#define ARRAY_SIZE 512 + + std::wstring UPrefsTest::language = L""; + std::wstring UPrefsTest::currency = L""; + std::wstring UPrefsTest::hourCycle = L""; + int32_t UPrefsTest::firstday = 0; + int32_t UPrefsTest::measureSystem = 0; + CALID UPrefsTest::calendar = 0; + +void UPrefsTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) +{ + if (exec) logln("TestSuite UPrefsTest: "); + TESTCASE_AUTO_BEGIN; + TESTCASE_AUTO(TestGetDefaultLocaleAsBCP47Tag); + TESTCASE_AUTO(TestBCP47TagWithSorting); + TESTCASE_AUTO(TestBCP47TagChineseSimplified); + TESTCASE_AUTO(TestBCP47TagChineseSortingStroke); + TESTCASE_AUTO(TestBCP47TagJapanCalendar); + TESTCASE_AUTO(TestUseNeededBuffer); + TESTCASE_AUTO(TestGetNeededBuffer); + TESTCASE_AUTO(TestGetUnsupportedSorting); + TESTCASE_AUTO(Get24HourCycleMixed); + TESTCASE_AUTO(Get12HourCycleMixed); + TESTCASE_AUTO(Get12HourCycleMixed2); + TESTCASE_AUTO(Get12HourCycle); + TESTCASE_AUTO(Get12HourCycle2); + TESTCASE_AUTO_END; +} + +int32_t UPrefsTest::MockGetLocaleInfoEx(LPCWSTR lpLocaleName, LCTYPE LCType, LPWSTR lpLCData, int cchData, UErrorCode* status) +{ + switch (LCType) + { + case LOCALE_SNAME: + if (cchData == 0) + { + *status = U_ZERO_ERROR; + return language.length() + 1; + } + + if (language.length() + 1 > cchData) + { + *status = U_BUFFER_OVERFLOW_ERROR; + return 0; + } + wcsncpy(lpLCData, language.c_str(), cchData); + *status = U_ZERO_ERROR; + return language.length(); + + case LOCALE_ICALENDARTYPE | LOCALE_RETURN_NUMBER: + if (cchData == 0) + { + *status = U_ZERO_ERROR; + return 2; + } + if (cchData < 2) + { + *status = U_BUFFER_OVERFLOW_ERROR; + return 0; + } + *(reinterpret_cast(lpLCData)) = calendar; + *status = U_ZERO_ERROR; + return 2; + + case LOCALE_SINTLSYMBOL: + if (cchData == 0) + { + *status = U_ZERO_ERROR; + return currency.length() + 1; + } + if (currency.length() + 1 > cchData) + { + *status = U_BUFFER_OVERFLOW_ERROR; + return 0; + } + wcsncpy(lpLCData, currency.c_str(), cchData); + *status = U_ZERO_ERROR; + return currency.length(); + + case LOCALE_IFIRSTDAYOFWEEK | LOCALE_RETURN_NUMBER: + if (cchData == 0) + { + *status = U_ZERO_ERROR; + return 2; + } + if (cchData < 2) + { + *status = U_BUFFER_OVERFLOW_ERROR; + return 0; + } + + *(reinterpret_cast(lpLCData)) = firstday; + *status = U_ZERO_ERROR; + return 2; + + case LOCALE_STIMEFORMAT: + if (cchData == 0) + { + *status = U_ZERO_ERROR; + return hourCycle.length() + 1; + } + + if (hourCycle.length() + 1 > cchData) + { + *status = U_BUFFER_OVERFLOW_ERROR; + return 0; + } + wcsncpy(lpLCData, hourCycle.c_str(), cchData); + *status = U_ZERO_ERROR; + return 0; + + case LOCALE_IMEASURE | LOCALE_RETURN_NUMBER: + if (cchData == 0) + { + *status = U_ZERO_ERROR; + return 2; + } + + if (cchData < 2) + { + *status = U_BUFFER_OVERFLOW_ERROR; + return 0; + } + *(reinterpret_cast(lpLCData)) = measureSystem; + *status = U_ZERO_ERROR; + return 2; + + default: + *status = U_INTERNAL_PROGRAM_ERROR; + return 0; + } +} + +// The code above is independent of the library itself, but for the code below this point, +// we need to include the library to be able to use the definitions of the API uprefs_getBCP47Tag +#include "uprefs.cpp" + +void UPrefsTest::TestGetDefaultLocaleAsBCP47Tag() +{ + char languageBuffer[ARRAY_SIZE] = {0}; + language = L"en-US"; + currency = L"USD"; + hourCycle = L"HH:mm:ss"; + firstday = 0; + measureSystem = 1; + calendar = CAL_GREGORIAN; + UErrorCode status = U_ZERO_ERROR; + const char* expectedValue = "en-US-u-ca-gregory-cu-usd-fw-mon-hc-h23-ms-ussystem"; + + if ( uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 52) + { + errln("Expected length to be 52, but got: %d\n",uprv_strlen(languageBuffer)); + } + if ( uprv_strcmp(expectedValue, languageBuffer) != 0) + { + errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); + } +} + +void UPrefsTest::TestBCP47TagWithSorting() +{ + char languageBuffer[ARRAY_SIZE] = {0}; + language = L"de-DE_phoneb"; + currency = L"EUR"; + hourCycle = L"HH:mm:ss"; + firstday = 0; + measureSystem = 1; + calendar = CAL_GREGORIAN; + UErrorCode status = U_ZERO_ERROR; + char* expectedValue = "de-DE-u-ca-gregory-co-phonebk-cu-eur-fw-mon-hc-h23-ms-ussystem"; + + if ( uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 63) + { + errln("Expected length to be 63, but got: %d\n",uprv_strlen(languageBuffer)); + } + if ( uprv_strcmp(expectedValue, languageBuffer) != 0) + { + errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); + } +} + +void UPrefsTest::TestBCP47TagChineseSimplified() +{ + char languageBuffer[ARRAY_SIZE] = {0}; + language = L"zh-Hans-HK"; + currency = L"EUR"; + hourCycle = L"hh:mm:ss"; + firstday = 2; + measureSystem = 1; + calendar = CAL_GREGORIAN; + UErrorCode status = U_ZERO_ERROR; + char* expectedValue = "zh-Hans-HK-u-ca-gregory-cu-eur-fw-wed-hc-h12-ms-ussystem"; + + if ( uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 57) + { + errln("Expected length to be 57, but got: %d\n",uprv_strlen(languageBuffer)); + } + if ( uprv_strcmp(expectedValue, languageBuffer) != 0) + { + errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); + } +} + +void UPrefsTest::TestBCP47TagChineseSortingStroke() +{ + char languageBuffer[ARRAY_SIZE] = {0}; + language = L"zh-SG_stroke"; + currency = L"EUR"; + hourCycle = L"hh:mm:ss"; + firstday = 2; + measureSystem = 0; + calendar = CAL_GREGORIAN; + UErrorCode status = U_ZERO_ERROR; + char* expectedValue = "zh-SG-u-ca-gregory-co-stroke-cu-eur-fw-wed-hc-h12-ms-metric"; + + if ( uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 60) + { + errln("Expected length to be 60, but got: %d\n",uprv_strlen(languageBuffer)); + } + if ( uprv_strcmp(expectedValue, languageBuffer) != 0) + { + errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); + } +} + +void UPrefsTest::TestBCP47TagJapanCalendar() +{ + char languageBuffer[ARRAY_SIZE] = {0}; + language = L"ja-JP"; + currency = L"MXN"; + hourCycle = L"hh:mm:ss"; + firstday = 1; + measureSystem = 0; + calendar = CAL_JAPAN; + UErrorCode status = U_ZERO_ERROR; + char* expectedValue = "ja-JP-u-ca-japanese-cu-mxn-fw-tue-hc-h12-ms-metric"; + + if ( uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 51) + { + errln("Expected length to be 51, but got: %d\n",uprv_strlen(languageBuffer)); + } + if ( uprv_strcmp(expectedValue, languageBuffer) != 0) + { + errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); + } +} + +void UPrefsTest::TestUseNeededBuffer() +{ + char languageBuffer[ARRAY_SIZE] = {0}; + language = L"ja-JP"; + currency = L"MXN"; + hourCycle = L"hh:mm:ss"; + firstday = 1; + measureSystem = 0; + calendar = CAL_THAI; + UErrorCode status = U_ZERO_ERROR; + char* expectedValue = "ja-JP-u-ca-buddhist-cu-mxn-fw-tue-hc-h12-ms-metric"; + + int32_t neededBufferSize = uprefs_getBCP47Tag(nullptr, 0, &status); + + if ( uprefs_getBCP47Tag(languageBuffer, neededBufferSize, &status) != 51) + { + errln("Expected length to be 51, but got: %d\n",uprv_strlen(languageBuffer)); + } + if ( uprv_strcmp(expectedValue, languageBuffer) != 0) + { + errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); + } +} + +void UPrefsTest::TestGetNeededBuffer() +{ + char languageBuffer[ARRAY_SIZE] = {0}; + language = L"zh-SG_stroke"; + currency = L"MXN"; + hourCycle = L"hh:mm:ss"; + firstday = 1; + measureSystem = 0; + calendar = CAL_THAI; + UErrorCode status = U_ZERO_ERROR; + char* expectedValue = "zh-SG-u-ca-buddhist-co-stroke-cu-mxn-fw-tue-hc-h12-ms-metric"; + + int32_t neededBufferSize = uprefs_getBCP47Tag(nullptr, 0, &status); + + if ( neededBufferSize != 61) + { + errln("Expected buffer size to be 61, but got: %d\n",uprv_strlen(languageBuffer)); + } + if ( uprefs_getBCP47Tag(languageBuffer, neededBufferSize, &status) != 61) + { + errln("Expected length to be 61, but got: %d\n",uprv_strlen(languageBuffer)); + } + if ( uprv_strcmp(expectedValue, languageBuffer) != 0) + { + errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); + } +} + +void UPrefsTest::TestGetUnsupportedSorting() +{ + char languageBuffer[ARRAY_SIZE] = {0}; + language = L"hu-HU_technl"; + currency = L"MXN"; + hourCycle = L"hh:mm:ss"; + firstday = 1; + measureSystem = 0; + calendar = CAL_THAI; + UErrorCode status = U_ZERO_ERROR; + char* expectedValue = "hu-HU-u-ca-buddhist-cu-mxn-fw-tue-hc-h12-ms-metric"; + + if ( uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 51) + { + errln("Expected length to be 51, but got: %d\n",uprv_strlen(languageBuffer)); + } + if ( uprv_strcmp(expectedValue, languageBuffer) != 0) + { + errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); + } +} + +void UPrefsTest::Get24HourCycleMixed() +{ + char languageBuffer[ARRAY_SIZE] = {0}; + language = L"ja-JP"; + currency = L"MXN"; + hourCycle = L"HHhh:mm:ss"; + firstday = 1; + measureSystem = 0; + calendar = CAL_THAI; + UErrorCode status = U_ZERO_ERROR; + char* expectedValue = "ja-JP-u-ca-buddhist-cu-mxn-fw-tue-hc-h23-ms-metric"; + + if (uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 51) + { + errln("Expected length to be 51, but got: %d\n", uprv_strlen(languageBuffer)); + } + if (uprv_strcmp(expectedValue, languageBuffer) != 0) + { + errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); + } +} + +void UPrefsTest::Get12HourCycleMixed() +{ + char languageBuffer[ARRAY_SIZE] = {0}; + language = L"ja-JP"; + currency = L"MXN"; + hourCycle = L"hHhH:mm:ss"; + firstday = 1; + measureSystem = 0; + calendar = CAL_THAI; + UErrorCode status = U_ZERO_ERROR; + char* expectedValue = "ja-JP-u-ca-buddhist-cu-mxn-fw-tue-hc-h12-ms-metric"; + + if (uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 51) + { + errln("Expected length to be 51, but got: %d\n", uprv_strlen(languageBuffer)); + } + if (uprv_strcmp(expectedValue, languageBuffer) != 0) + { + errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); + } +} + + +void UPrefsTest::Get12HourCycleMixed2() +{ + char languageBuffer[ARRAY_SIZE] = {0}; + language = L"ja-JP"; + currency = L"MXN"; + hourCycle = L"hH''h'H'H:mm:ss"; + firstday = 1; + measureSystem = 0; + calendar = CAL_THAI; + UErrorCode status = U_ZERO_ERROR; + char* expectedValue = "ja-JP-u-ca-buddhist-cu-mxn-fw-tue-hc-h12-ms-metric"; + + if (uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 51) + { + errln("Expected length to be 51, but got: %d\n", uprv_strlen(languageBuffer)); + } + if (uprv_strcmp(expectedValue, languageBuffer) != 0) + { + errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); + } +} + +void UPrefsTest::Get12HourCycle() +{ + char languageBuffer[ARRAY_SIZE] = {0}; + language = L"ja-JP"; + currency = L"MXN"; + hourCycle = L"h'H'h:mm:ss"; + firstday = 1; + measureSystem = 0; + calendar = CAL_THAI; + UErrorCode status = U_ZERO_ERROR; + char* expectedValue = "ja-JP-u-ca-buddhist-cu-mxn-fw-tue-hc-h12-ms-metric"; + + if (uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 51) + { + errln("Expected length to be 51, but got: %d\n", uprv_strlen(languageBuffer)); + } + if (uprv_strcmp(expectedValue, languageBuffer) != 0) + { + errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); + } +} + +void UPrefsTest::Get12HourCycle2() +{ + char languageBuffer[ARRAY_SIZE] = {0}; + language = L"ja-JP"; + currency = L"MXN"; + hourCycle = L"'H'h'H'h:mm:ss"; + firstday = 1; + measureSystem = 0; + calendar = CAL_THAI; + UErrorCode status = U_ZERO_ERROR; + char* expectedValue = "ja-JP-u-ca-buddhist-cu-mxn-fw-tue-hc-h12-ms-metric"; + + if (uprefs_getBCP47Tag(languageBuffer, ARRAY_SIZE, &status) != 51) + { + errln("Expected length to be 51, but got: %d\n", uprv_strlen(languageBuffer)); + } + if (uprv_strcmp(expectedValue, languageBuffer) != 0) + { + errln("Expected BCP47Tag to be %s, but got: %s\n", expectedValue, languageBuffer); + } +} +#endif //U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY \ No newline at end of file diff --git a/icu/icu4c/source/test/intltest/uprefstest.h b/icu/icu4c/source/test/intltest/uprefstest.h new file mode 100644 index 00000000000..d87452e6bbd --- /dev/null +++ b/icu/icu4c/source/test/intltest/uprefstest.h @@ -0,0 +1,50 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html +#ifndef UPREFSTEST_H +#define UPREFSTEST_H + +#include "unicode/platform.h" +#if U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY +// We define UPREFS_TEST to use the mock version of GetLocaleInfoEx(), which +// allows us to simulate its behaviour and determine if the results given by the +// API align with what we expect to receive +#define UPREFS_TEST 1 + + +#include "windows.h" +#include "intltest.h" +#include "uprefs.h" + +class UPrefsTest: public IntlTest { +private: + static std::wstring language; + static std::wstring currency; + static std::wstring hourCycle; + static int32_t firstday; + static int32_t measureSystem; + static CALID calendar; + +public: + UPrefsTest(){}; + virtual ~UPrefsTest(){}; + + virtual void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = NULL) override; + int32_t MockGetLocaleInfoEx(LPCWSTR lpLocaleName, LCTYPE LCType, LPWSTR lpLCData, int cchData, UErrorCode* status); + void TestGetDefaultLocaleAsBCP47Tag(); + void TestBCP47TagWithSorting(); + void TestBCP47TagChineseSimplified(); + void TestBCP47TagChineseSortingStroke(); + void TestBCP47TagJapanCalendar(); + void TestUseNeededBuffer(); + void TestGetNeededBuffer(); + void TestGetUnsupportedSorting(); + void Get24HourCycleMixed(); + void Get12HourCycleMixed(); + void Get12HourCycleMixed2(); + void Get12HourCycle(); + void Get12HourCycle2(); +}; + + +#endif //U_PLATFORM_USES_ONLY_WIN32_API && UCONFIG_USE_WINDOWS_PREFERENCES_LIBRARY +#endif //UPREFSTEST_H \ No newline at end of file diff --git a/version.txt b/version.txt index fe5a723278b..c10c1a245db 100644 --- a/version.txt +++ b/version.txt @@ -35,5 +35,5 @@ # in the header file "uvernum.h" whenever updated and/or changed. # -ICU_version = 68.2.0.9 +ICU_version = 68.2.0.10 ICU_upstream_hash = 84e1f26ea77152936e70d53178a816dbfbf69989