* 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 "locale_config.h"
#include "ohos/init_data.h"
#include "relative_time_format.h"
namespace OHOS {
namespace Global {
namespace I18n {
std::unordered_map<std::string, UDateRelativeDateTimeFormatterStyle> RelativeTimeFormat::relativeFormatStyle = {
{ "long", UDAT_STYLE_LONG },
{ "short", UDAT_STYLE_SHORT },
{ "narrow", UDAT_STYLE_NARROW }
};
std::unordered_map<std::string, std::string> RelativeTimeFormat::defaultFormatStyle = {
{ "wearable", "narrow" },
{ "liteWearable", "narrow" },
{ "watch", "narrow" }
};
std::unordered_map<std::string, URelativeDateTimeUnit> RelativeTimeFormat::relativeUnits = {
{ "second", UDAT_REL_UNIT_SECOND },
{ "seconds", UDAT_REL_UNIT_SECOND },
{ "minute", UDAT_REL_UNIT_MINUTE },
{ "minutes", UDAT_REL_UNIT_MINUTE },
{ "hour", UDAT_REL_UNIT_HOUR },
{ "hours", UDAT_REL_UNIT_HOUR },
{ "day", UDAT_REL_UNIT_DAY },
{ "days", UDAT_REL_UNIT_DAY },
{ "week", UDAT_REL_UNIT_WEEK },
{ "weeks", UDAT_REL_UNIT_WEEK },
{ "month", UDAT_REL_UNIT_MONTH },
{ "months", UDAT_REL_UNIT_MONTH },
{ "quarter", UDAT_REL_UNIT_QUARTER },
{ "quarters", UDAT_REL_UNIT_QUARTER },
{ "year", UDAT_REL_UNIT_YEAR },
{ "years", UDAT_REL_UNIT_YEAR },
};
RelativeTimeFormat::RelativeTimeFormat(const std::vector<std::string> &localeTags,
std::map<std::string, std::string> &configs)
{
SetDefaultStyle();
UErrorCode status = U_ZERO_ERROR;
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);
if (!localeInfo->InitSuccess()) {
continue;
}
locale = localeInfo->GetLocale();
localeBaseName = localeInfo->GetBaseName();
relativeTimeFormat = std::make_unique<icu::RelativeDateTimeFormatter>(locale, nullptr, style,
UDISPCTX_CAPITALIZATION_NONE, status);
if (!U_SUCCESS(status)) {
status = U_ZERO_ERROR;
continue;
}
createSuccess = true;
break;
}
}
if (!createSuccess) {
localeInfo = std::make_unique<LocaleInfo>(LocaleConfig::GetSystemLocaleWithExtParam(), configs);
if (localeInfo->InitSuccess()) {
locale = localeInfo->GetLocale();
localeBaseName = localeInfo->GetBaseName();
relativeTimeFormat = std::make_unique<icu::RelativeDateTimeFormatter>(locale, nullptr, style,
UDISPCTX_CAPITALIZATION_NONE, status);
if (U_SUCCESS(status)) {
createSuccess = true;
}
}
}
numberingSystem = localeInfo->GetNumberingSystem();
if (numberingSystem == "") {
numberingSystem = "latn";
}
}
RelativeTimeFormat::~RelativeTimeFormat()
{
}
void RelativeTimeFormat::ParseConfigs(std::map<std::string, std::string> &configs)
{
if (configs.count("style") > 0) {
styleString = configs["style"];
}
if (relativeFormatStyle.count(styleString) > 0) {
style = relativeFormatStyle[styleString];
}
if (configs.count("numeric") > 0) {
numeric = configs["numeric"];
}
}
std::string RelativeTimeFormat::Format(double number, const std::string &unit)
{
if (!createSuccess || !relativeUnits.count(unit)) {
return "";
}
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString formattedTime;
std::string result;
if (!strcmp(numeric.c_str(), "always")) {
formattedTime = relativeTimeFormat->formatNumericToValue(number, relativeUnits[unit], status).toString(status);
} else {
formattedTime = relativeTimeFormat->formatToValue(number, relativeUnits[unit], status).toString(status);
}
formattedTime.toUTF8String(result);
return result;
}
void RelativeTimeFormat::InsertInfo(std::vector<std::vector<std::string>> &timeVector,
const std::string &unit, bool isInteger, const std::string &value)
{
std::vector<std::string> info;
if (isInteger) {
info.push_back("integer");
info.push_back(value);
info.push_back(unit);
} else {
info.push_back("literal");
info.push_back(value);
}
timeVector.push_back(info);
}
void RelativeTimeFormat::ProcessIntegerField(const std::map<size_t, size_t> &indexMap,
std::vector<std::vector<std::string>> &timeVector, size_t &startIndex, const std::string &unit,
const std::string &result)
{
for (auto iter = indexMap.begin(); iter != indexMap.end(); iter++) {
if (iter->first > startIndex) {
InsertInfo(timeVector, unit, true, result.substr(startIndex, iter->first - startIndex));
InsertInfo(timeVector, unit, true, result.substr(iter->first, iter->second - iter->first));
startIndex = iter->second;
}
}
}
void RelativeTimeFormat::FormatToParts(double number, const std::string &unit,
std::vector<std::vector<std::string>> &timeVector)
{
if (!createSuccess || !relativeUnits.count(unit)) {
return;
}
UErrorCode status = U_ZERO_ERROR;
std::string result;
icu::FormattedRelativeDateTime fmtRelativeTime;
if (numeric.empty() || !strcmp(numeric.c_str(), "always")) {
fmtRelativeTime = relativeTimeFormat->formatNumericToValue(number, relativeUnits[unit], status);
} else {
fmtRelativeTime = relativeTimeFormat->formatToValue(number, relativeUnits[unit], status);
}
fmtRelativeTime.toString(status).toUTF8String(result);
icu::ConstrainedFieldPosition constrainedPos;
constrainedPos.constrainCategory(UFIELD_CATEGORY_NUMBER);
size_t prevIndex = 0;
size_t length = result.length();
std::map<size_t, size_t> indexMap;
while (fmtRelativeTime.nextPosition(constrainedPos, status)) {
size_t startIndex = (size_t)constrainedPos.getStart();
if (constrainedPos.getCategory() == UFIELD_CATEGORY_NUMBER) {
if (constrainedPos.getField() == UNUM_GROUPING_SEPARATOR_FIELD) {
indexMap.insert(std::make_pair(startIndex, (size_t)constrainedPos.getLimit()));
continue;
}
if (startIndex > prevIndex) {
InsertInfo(timeVector, unit, false, result.substr(prevIndex, startIndex - prevIndex));
}
if (constrainedPos.getField() == UNUM_INTEGER_FIELD) {
ProcessIntegerField(indexMap, timeVector, startIndex, unit, result);
}
InsertInfo(timeVector, unit, true, result.substr(startIndex,
(size_t)constrainedPos.getLimit() - startIndex));
prevIndex = (size_t)constrainedPos.getLimit();
}
}
if (prevIndex < length) {
InsertInfo(timeVector, unit, false, result.substr(prevIndex, length - prevIndex));
}
}
void RelativeTimeFormat::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 (!numeric.empty()) {
map.insert(std::make_pair("numeric", numeric));
}
if (!numberingSystem.empty()) {
map.insert(std::make_pair("numberingSystem", numberingSystem));
}
}
void RelativeTimeFormat::SetDefaultStyle()
{
auto plugin = Plugin::INTL::Create();
if (!plugin) {
return;
}
std::string deviceType = plugin->GetDeviceType();
if (defaultFormatStyle.find(deviceType) != defaultFormatStyle.end()) {
styleString = defaultFormatStyle[deviceType];
}
}
}
}
}