* Copyright (c) 2021-2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "number_format.h"
#include "ohos/init_data.h"
#include "locale_config.h"
#include "utils.h"
namespace OHOS {
namespace Global {
namespace I18n {
bool NumberFormat::icuInitialized = NumberFormat::Init();
std::unordered_map<std::string, UNumberUnitWidth> NumberFormat::unitStyle = {
{ "long", UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME },
{ "short", UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT },
{ "narrow", UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW }
};
std::unordered_map<std::string, UNumberUnitWidth> NumberFormat::currencyStyle = {
{ "symbol", UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT },
{ "code", UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE },
{ "name", UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME },
{ "narrowSymbol", UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW }
};
std::unordered_map<std::string, UNumberSignDisplay> NumberFormat::signAutoStyle = {
{ "auto", UNumberSignDisplay::UNUM_SIGN_AUTO },
{ "never", UNumberSignDisplay::UNUM_SIGN_NEVER },
{ "always", UNumberSignDisplay::UNUM_SIGN_ALWAYS },
{ "exceptZero", UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO }
};
std::unordered_map<std::string, UNumberSignDisplay> NumberFormat::signAccountingStyle = {
{ "auto", UNumberSignDisplay::UNUM_SIGN_ACCOUNTING },
{ "never", UNumberSignDisplay::UNUM_SIGN_NEVER },
{ "always", UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS },
{ "exceptZero", UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO }
};
std::unordered_map<UMeasurementSystem, std::string> NumberFormat::measurementSystem = {
{ UMeasurementSystem::UMS_SI, "SI" },
{ UMeasurementSystem::UMS_US, "US" },
{ UMeasurementSystem::UMS_UK, "UK" },
};
std::unordered_map<std::string, UNumberUnitWidth> NumberFormat::defaultUnitStyle = {
{ "tablet", UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME },
{ "2in1", UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME },
{ "tv", UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME },
{ "pc", UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME },
{ "wearable", UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW },
{ "liteWearable", UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW },
{ "watch", UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW }
};
std::unordered_map<std::string, UNumberUnitWidth> NumberFormat::defaultCurrencyStyle = {
{ "wearable", UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW },
{ "liteWearable", UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW },
{ "watch", UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW }
};
std::map<std::string, std::string> NumberFormat::RelativeTimeFormatConfigs = {
{ "numeric", "auto" }
};
NumberFormat::NumberFormat(const std::vector<std::string> &localeTags, std::map<std::string, std::string> &configs)
{
SetDefaultStyle();
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::LocaleBuilder> builder = nullptr;
builder = std::make_unique<icu::LocaleBuilder>();
ParseConfigs(configs);
for (size_t i = 0; i < localeTags.size(); i++) {
std::string curLocale = localeTags[i];
locale = icu::Locale::forLanguageTag(icu::StringPiece(curLocale), status);
if (status != U_ZERO_ERROR) {
status = U_ZERO_ERROR;
continue;
}
if (LocaleInfo::GetValidLocales().count(locale.getLanguage()) > 0) {
localeInfo = std::make_unique<LocaleInfo>(curLocale, configs);
CreateRelativeTimeFormat(curLocale);
if (!localeInfo->InitSuccess()) {
continue;
}
locale = localeInfo->GetLocale();
localeBaseName = localeInfo->GetBaseName();
numberFormat = icu::number::NumberFormatter::withLocale(locale);
icu::MeasureUnit::getAvailable(unitArray, MAX_UNIT_NUM, status);
if (!U_SUCCESS(status)) {
status = U_ZERO_ERROR;
continue;
}
createSuccess = true;
break;
}
}
if (!createSuccess) {
std::string systemLocale = LocaleConfig::GetSystemLocaleWithExtParam();
localeInfo = std::make_unique<LocaleInfo>(systemLocale, configs);
CreateRelativeTimeFormat(systemLocale);
if (localeInfo->InitSuccess()) {
locale = localeInfo->GetLocale();
localeBaseName = localeInfo->GetBaseName();
numberFormat = icu::number::NumberFormatter::withLocale(locale);
icu::MeasureUnit::getAvailable(unitArray, MAX_UNIT_NUM, status);
if (U_SUCCESS(status)) {
createSuccess = true;
}
}
}
if (createSuccess) {
InitProperties();
}
}
NumberFormat::~NumberFormat()
{
}
void NumberFormat::CreateRelativeTimeFormat(const std::string& locale)
{
if (unitUsage == "elapsed-time-second") {
std::vector<std::string> locales = { locale };
relativeTimeFormat = std::make_unique<RelativeTimeFormat>(locales,
RelativeTimeFormatConfigs);
}
}
void NumberFormat::InitProperties()
{
if (!currency.empty()) {
UErrorCode status = U_ZERO_ERROR;
numberFormat =
numberFormat.unit(icu::CurrencyUnit(icu::UnicodeString(currency.c_str()).getBuffer(), status));
if (currencyDisplay != UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT) {
numberFormat = numberFormat.unitWidth(currencyDisplay);
}
}
if (!styleString.empty() && styleString == "percent") {
numberFormat = numberFormat.unit(icu::NoUnit::percent())
.scale(icu::number::Scale::powerOfTen(2))
.precision(icu::number::Precision::fixedFraction(0));
}
if (!styleString.empty() && styleString == "unit") {
for (icu::MeasureUnit curUnit : unitArray) {
if (!strcmp(curUnit.getSubtype(), unit.c_str())) {
numberFormat = numberFormat.unit(curUnit);
unitType = curUnit.getType();
}
}
UErrorCode status = U_ZERO_ERROR;
UMeasurementSystem measSys = ulocdata_getMeasurementSystem(localeBaseName.c_str(), &status);
if (U_SUCCESS(status) && measSys >= 0) {
unitMeasSys = measurementSystem[measSys];
}
numberFormat = numberFormat.unitWidth(unitDisplay);
numberFormat = numberFormat.precision(icu::number::Precision::maxFraction(DEFAULT_FRACTION_DIGITS));
}
if (!useGrouping.empty()) {
numberFormat = numberFormat.grouping((useGrouping == "true") ?
UNumberGroupingStrategy::UNUM_GROUPING_AUTO : UNumberGroupingStrategy::UNUM_GROUPING_OFF);
}
if (!currencySign.empty() || !signDisplayString.empty()) {
numberFormat = numberFormat.sign(signDisplay);
}
if (!notationString.empty()) {
numberFormat = numberFormat.notation(notation);
}
InitDigitsProperties();
}
void NumberFormat::InitDigitsProperties()
{
int32_t status = 0;
if (!maximumSignificantDigits.empty() || !minimumSignificantDigits.empty()) {
int32_t maxSignificantDigits = ConvertString2Int(maximumSignificantDigits, status);
if (status == 0) {
numberFormat = numberFormat.precision(icu::number::Precision::maxSignificantDigits(maxSignificantDigits));
}
status = 0;
int32_t minSignificantDigits = ConvertString2Int(minimumSignificantDigits, status);
if (status == 0) {
numberFormat = numberFormat.precision(icu::number::Precision::minSignificantDigits(minSignificantDigits));
}
} else {
int32_t minIntegerDigits = ConvertString2Int(minimumIntegerDigits, status);
if (status == 0 && minIntegerDigits > 1) {
numberFormat =
numberFormat.integerWidth(icu::number::IntegerWidth::zeroFillTo(minIntegerDigits));
}
status = 0;
int32_t minFractionDigits = ConvertString2Int(minimumFractionDigits, status);
if (status == 0) {
numberFormat =
numberFormat.precision(icu::number::Precision::minFraction(minFractionDigits));
}
status = 0;
int32_t maxFractionDigits = ConvertString2Int(maximumFractionDigits, status);
if (status == 0) {
numberFormat =
numberFormat.precision(icu::number::Precision::maxFraction(maxFractionDigits));
}
}
}
void NumberFormat::ParseConfigs(std::map<std::string, std::string> &configs)
{
if (configs.count("signDisplay") > 0) {
signDisplayString = configs["signDisplay"];
}
if (signAutoStyle.count(signDisplayString) > 0) {
signDisplay = signAutoStyle[signDisplayString];
}
if (configs.count("style") > 0) {
styleString = configs["style"];
}
if (styleString == "unit" && configs.count("unit") > 0) {
unit = configs["unit"];
if (configs.count("unitDisplay") > 0) {
unitDisplayString = configs["unitDisplay"];
if (unitStyle.count(unitDisplayString) > 0) {
unitDisplay = unitStyle[unitDisplayString];
}
}
if (configs.count("unitUsage") > 0) {
unitUsage = configs["unitUsage"];
}
}
if (styleString == "currency" && configs.count("currency") > 0) {
currency = configs["currency"];
if (configs.count("currencySign") > 0) {
currencySign = configs["currencySign"];
}
if (currencySign.compare("accounting") == 0 && signAccountingStyle.count(signDisplayString) > 0) {
signDisplay = signAccountingStyle[signDisplayString];
}
if (configs.count("currencyDisplay") > 0 && currencyStyle.count(configs["currencyDisplay"]) > 0) {
currencyDisplayString = configs["currencyDisplay"];
currencyDisplay = currencyStyle[currencyDisplayString];
}
}
ParseDigitsConfigs(configs);
}
void NumberFormat::ParseDigitsConfigs(std::map<std::string, std::string> &configs)
{
if (configs.count("notation") > 0) {
notationString = configs["notation"];
if (notationString == "scientific") {
notation = icu::number::Notation::scientific();
} else if (notationString == "engineering") {
notation = icu::number::Notation::engineering();
}
if (notationString == "compact" && configs.count("compactDisplay") > 0) {
compactDisplay = configs["compactDisplay"];
if (compactDisplay == "long") {
notation = icu::number::Notation::compactLong();
} else {
notation = icu::number::Notation::compactShort();
}
}
}
if (configs.count("minimumIntegerDigits") > 0) {
minimumIntegerDigits = configs["minimumIntegerDigits"];
}
if (configs.count("minimumFractionDigits") > 0) {
minimumFractionDigits = configs["minimumFractionDigits"];
}
if (configs.count("maximumFractionDigits") > 0) {
maximumFractionDigits = configs["maximumFractionDigits"];
}
if (configs.count("minimumSignificantDigits") > 0) {
minimumSignificantDigits = configs["minimumSignificantDigits"];
}
if (configs.count("maximumSignificantDigits") > 0) {
maximumSignificantDigits = configs["maximumSignificantDigits"];
}
if (configs.count("numberingSystem") > 0) {
numberingSystem = configs["numberingSystem"];
}
if (configs.count("useGrouping") > 0) {
useGrouping = configs["useGrouping"];
}
if (configs.count("localeMatcher") > 0) {
localeMatcher = configs["localeMatcher"];
}
}
void NumberFormat::SetUnit(std::string &preferredUnit)
{
if (preferredUnit.empty()) {
return;
}
for (icu::MeasureUnit curUnit : unitArray) {
if (!strcmp(curUnit.getSubtype(), preferredUnit.c_str())) {
numberFormat = numberFormat.unit(curUnit);
}
}
}
std::string NumberFormat::Format(double number)
{
if (!createSuccess) {
return "";
}
double finalNumber = number;
std::string finalUnit = unit;
if ((unitUsage == "size-file-byte" || unitUsage == "size-shortfile-byte") && ConvertByte(finalNumber, finalUnit)) {
SetUnit(finalUnit);
SetPrecisionWithByte(finalNumber, finalUnit);
} else if (unitUsage == "elapsed-time-second" && ConvertDate(finalNumber, finalUnit) &&
relativeTimeFormat != nullptr) {
return relativeTimeFormat->Format(finalNumber, finalUnit);
} else if (!unitUsage.empty()) {
std::vector<std::string> preferredUnits;
if (unitUsage == "default") {
GetDefaultPreferredUnit(localeInfo->GetRegion(), unitType, preferredUnits);
} else {
GetPreferredUnit(localeInfo->GetRegion(), unitUsage, preferredUnits);
}
std::map<double, std::string> preferredValuesOverOne;
std::map<double, std::string> preferredValuesUnderOne;
double num = number;
for (size_t i = 0; i < preferredUnits.size(); i++) {
int status = Convert(num, unit, unitMeasSys, preferredUnits[i], unitMeasSys);
if (!status) {
continue;
}
if (num >= 1) {
preferredValuesOverOne.insert(std::make_pair(num, preferredUnits[i]));
} else {
preferredValuesUnderOne.insert(std::make_pair(num, preferredUnits[i]));
}
}
std::string preferredUnit;
if (preferredValuesOverOne.size() > 0) {
finalNumber = preferredValuesOverOne.begin()->first;
preferredUnit = preferredValuesOverOne.begin()->second;
} else if (preferredValuesUnderOne.size() > 0) {
finalNumber = preferredValuesUnderOne.rbegin()->first;
preferredUnit = preferredValuesUnderOne.rbegin()->second;
}
SetUnit(preferredUnit);
}
std::string result;
UErrorCode status = U_ZERO_ERROR;
numberFormat.formatDouble(finalNumber, status).toString(status).toUTF8String(result);
return result;
}
void NumberFormat::GetResolvedOptions(std::map<std::string, std::string> &map)
{
map.insert(std::make_pair("locale", localeBaseName));
if (!styleString.empty()) {
map.insert(std::make_pair("style", styleString));
}
if (!currency.empty()) {
map.insert(std::make_pair("currency", currency));
}
if (!currencySign.empty()) {
map.insert(std::make_pair("currencySign", currencySign));
}
if (!currencyDisplayString.empty()) {
map.insert(std::make_pair("currencyDisplay", currencyDisplayString));
}
if (!signDisplayString.empty()) {
map.insert(std::make_pair("signDisplay", signDisplayString));
}
if (!compactDisplay.empty()) {
map.insert(std::make_pair("compactDisplay", compactDisplay));
}
if (!unitDisplayString.empty()) {
map.insert(std::make_pair("unitDisplay", unitDisplayString));
}
if (!unitUsage.empty()) {
map.insert(std::make_pair("unitUsage", unitUsage));
}
if (!unit.empty()) {
map.insert(std::make_pair("unit", unit));
}
GetDigitsResolvedOptions(map);
}
void NumberFormat::GetDigitsResolvedOptions(std::map<std::string, std::string> &map)
{
if (!numberingSystem.empty()) {
map.insert(std::make_pair("numberingSystem", numberingSystem));
} else if (!(localeInfo->GetNumberingSystem()).empty()) {
map.insert(std::make_pair("numberingSystem", localeInfo->GetNumberingSystem()));
} else {
UErrorCode status = U_ZERO_ERROR;
auto numSys = std::unique_ptr<icu::NumberingSystem>(icu::NumberingSystem::createInstance(locale, status));
if (U_SUCCESS(status)) {
map.insert(std::make_pair("numberingSystem", numSys->getName()));
}
}
if (!useGrouping.empty()) {
map.insert(std::make_pair("useGrouping", useGrouping));
}
if (!minimumIntegerDigits.empty()) {
map.insert(std::make_pair("minimumIntegerDigits", minimumIntegerDigits));
}
if (!minimumFractionDigits.empty()) {
map.insert(std::make_pair("minimumFractionDigits", minimumFractionDigits));
}
if (!maximumFractionDigits.empty()) {
map.insert(std::make_pair("maximumFractionDigits", maximumFractionDigits));
}
if (!minimumSignificantDigits.empty()) {
map.insert(std::make_pair("minimumSignificantDigits", minimumSignificantDigits));
}
if (!maximumSignificantDigits.empty()) {
map.insert(std::make_pair("maximumSignificantDigits", maximumSignificantDigits));
}
if (!localeMatcher.empty()) {
map.insert(std::make_pair("localeMatcher", localeMatcher));
}
if (!notationString.empty()) {
map.insert(std::make_pair("notation", notationString));
}
}
void NumberFormat::SetPrecisionWithByte(double number, const std::string& finalUnit)
{
int32_t FractionDigits = -1;
if (finalUnit == "byte" || number >= 100) {
FractionDigits = 0;
} else if (number < 1) {
FractionDigits = 2;
} else if (number < 10) {
if (unitUsage == "size-shortfile-byte") {
FractionDigits = 1;
} else {
FractionDigits = 2;
}
} else {
if (unitUsage == "size-shortfile-byte") {
FractionDigits = 0;
} else {
FractionDigits = 2;
}
}
if (FractionDigits != -1) {
numberFormat = numberFormat.precision(icu::number::Precision::minMaxFraction(FractionDigits, FractionDigits));
}
}
std::string NumberFormat::GetCurrency() const
{
return currency;
}
std::string NumberFormat::GetCurrencySign() const
{
return currencySign;
}
std::string NumberFormat::GetStyle() const
{
return styleString;
}
std::string NumberFormat::GetNumberingSystem() const
{
return numberingSystem;
}
std::string NumberFormat::GetUseGrouping() const
{
return useGrouping;
}
std::string NumberFormat::GetMinimumIntegerDigits() const
{
return minimumIntegerDigits;
}
std::string NumberFormat::GetMinimumFractionDigits() const
{
return minimumFractionDigits;
}
std::string NumberFormat::GetMaximumFractionDigits() const
{
return maximumFractionDigits;
}
std::string NumberFormat::GetMinimumSignificantDigits() const
{
return minimumSignificantDigits;
}
std::string NumberFormat::GetMaximumSignificantDigits() const
{
return maximumSignificantDigits;
}
std::string NumberFormat::GetLocaleMatcher() const
{
return localeMatcher;
}
bool NumberFormat::Init()
{
return true;
}
void NumberFormat::SetDefaultStyle()
{
auto plugin = Plugin::INTL::Create();
if (!plugin) {
return;
}
std::string deviceType = plugin->GetDeviceType();
if (defaultUnitStyle.find(deviceType) != defaultUnitStyle.end()) {
unitDisplay = defaultUnitStyle[deviceType];
}
if (defaultCurrencyStyle.find(deviceType) != defaultCurrencyStyle.end()) {
currencyDisplay = defaultCurrencyStyle[deviceType];
}
}
}
}
}