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