You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1417 lines
48 KiB

// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
******************************************************************************
* Copyright (C) 2014-2016, International Business Machines Corporation and
* others. All Rights Reserved.
******************************************************************************
*
* File reldatefmt.cpp
******************************************************************************
*/
#include "unicode/reldatefmt.h"
#if !UCONFIG_NO_FORMATTING && !UCONFIG_NO_BREAK_ITERATION
#include <cmath>
#include <functional>
#include "unicode/dtfmtsym.h"
#include "unicode/ucasemap.h"
#include "unicode/ureldatefmt.h"
#include "unicode/udisplaycontext.h"
#include "unicode/unum.h"
#include "unicode/localpointer.h"
#include "unicode/plurrule.h"
#include "unicode/simpleformatter.h"
#include "unicode/decimfmt.h"
#include "unicode/numfmt.h"
#include "unicode/brkiter.h"
#include "unicode/simpleformatter.h"
#include "uresimp.h"
#include "unicode/ures.h"
#include "cstring.h"
#include "ucln_in.h"
#include "mutex.h"
#include "charstr.h"
#include "uassert.h"
#include "quantityformatter.h"
#include "resource.h"
#include "sharedbreakiterator.h"
#include "sharedpluralrules.h"
#include "sharednumberformat.h"
#include "standardplural.h"
#include "unifiedcache.h"
#include "util.h"
#include "formatted_string_builder.h"
#include "number_utypes.h"
#include "number_modifiers.h"
#include "formattedval_impl.h"
#include "number_utils.h"
// Copied from uscript_props.cpp
U_NAMESPACE_BEGIN
// RelativeDateTimeFormatter specific data for a single locale
class RelativeDateTimeCacheData: public SharedObject {
public:
RelativeDateTimeCacheData() : combinedDateAndTime(nullptr) {
// Initialize the cache arrays
for (int32_t style = 0; style < UDAT_STYLE_COUNT; ++style) {
for (int32_t relUnit = 0; relUnit < UDAT_REL_UNIT_COUNT; ++relUnit) {
for (int32_t pl = 0; pl < StandardPlural::COUNT; ++pl) {
relativeUnitsFormatters[style][relUnit][0][pl] = nullptr;
relativeUnitsFormatters[style][relUnit][1][pl] = nullptr;
}
}
}
for (int32_t i = 0; i < UDAT_STYLE_COUNT; ++i) {
fallBackCache[i] = -1;
}
}
virtual ~RelativeDateTimeCacheData();
// no numbers: e.g Next Tuesday; Yesterday; etc.
UnicodeString absoluteUnits[UDAT_STYLE_COUNT][UDAT_ABSOLUTE_UNIT_COUNT][UDAT_DIRECTION_COUNT];
// SimpleFormatter pointers for relative unit format,
// e.g., Next Tuesday; Yesterday; etc. For third index, 0
// means past, e.g., 5 days ago; 1 means future, e.g., in 5 days.
SimpleFormatter *relativeUnitsFormatters[UDAT_STYLE_COUNT]
[UDAT_REL_UNIT_COUNT][2][StandardPlural::COUNT];
const UnicodeString& getAbsoluteUnitString(int32_t fStyle,
UDateAbsoluteUnit unit,
UDateDirection direction) const;
const SimpleFormatter* getRelativeUnitFormatter(int32_t fStyle,
UDateRelativeUnit unit,
int32_t pastFutureIndex,
int32_t pluralUnit) const;
const SimpleFormatter* getRelativeDateTimeUnitFormatter(int32_t fStyle,
URelativeDateTimeUnit unit,
int32_t pastFutureIndex,
int32_t pluralUnit) const;
const UnicodeString emptyString;
// Mappping from source to target styles for alias fallback.
int32_t fallBackCache[UDAT_STYLE_COUNT];
void adoptCombinedDateAndTime(SimpleFormatter *fmtToAdopt) {
delete combinedDateAndTime;
combinedDateAndTime = fmtToAdopt;
}
const SimpleFormatter *getCombinedDateAndTime() const {
return combinedDateAndTime;
}
private:
SimpleFormatter *combinedDateAndTime;
RelativeDateTimeCacheData(const RelativeDateTimeCacheData &other);
RelativeDateTimeCacheData& operator=(
const RelativeDateTimeCacheData &other);
};
RelativeDateTimeCacheData::~RelativeDateTimeCacheData() {
// clear out the cache arrays
for (int32_t style = 0; style < UDAT_STYLE_COUNT; ++style) {
for (int32_t relUnit = 0; relUnit < UDAT_REL_UNIT_COUNT; ++relUnit) {
for (int32_t pl = 0; pl < StandardPlural::COUNT; ++pl) {
delete relativeUnitsFormatters[style][relUnit][0][pl];
delete relativeUnitsFormatters[style][relUnit][1][pl];
}
}
}
delete combinedDateAndTime;
}
// Use fallback cache for absolute units.
const UnicodeString& RelativeDateTimeCacheData::getAbsoluteUnitString(
int32_t fStyle, UDateAbsoluteUnit unit, UDateDirection direction) const {
int32_t style = fStyle;
do {
if (!absoluteUnits[style][unit][direction].isEmpty()) {
return absoluteUnits[style][unit][direction];
}
style = fallBackCache[style];
} while (style != -1);
return emptyString;
}
const SimpleFormatter* RelativeDateTimeCacheData::getRelativeUnitFormatter(
int32_t fStyle,
UDateRelativeUnit unit,
int32_t pastFutureIndex,
int32_t pluralUnit) const {
URelativeDateTimeUnit rdtunit = UDAT_REL_UNIT_COUNT;
switch (unit) {
case UDAT_RELATIVE_YEARS: rdtunit = UDAT_REL_UNIT_YEAR; break;
case UDAT_RELATIVE_MONTHS: rdtunit = UDAT_REL_UNIT_MONTH; break;
case UDAT_RELATIVE_WEEKS: rdtunit = UDAT_REL_UNIT_WEEK; break;
case UDAT_RELATIVE_DAYS: rdtunit = UDAT_REL_UNIT_DAY; break;
case UDAT_RELATIVE_HOURS: rdtunit = UDAT_REL_UNIT_HOUR; break;
case UDAT_RELATIVE_MINUTES: rdtunit = UDAT_REL_UNIT_MINUTE; break;
case UDAT_RELATIVE_SECONDS: rdtunit = UDAT_REL_UNIT_SECOND; break;
default: // a unit that the above method does not handle
return nullptr;
}
return getRelativeDateTimeUnitFormatter(fStyle, rdtunit, pastFutureIndex, pluralUnit);
}
// Use fallback cache for SimpleFormatter relativeUnits.
const SimpleFormatter* RelativeDateTimeCacheData::getRelativeDateTimeUnitFormatter(
int32_t fStyle,
URelativeDateTimeUnit unit,
int32_t pastFutureIndex,
int32_t pluralUnit) const {
while (true) {
int32_t style = fStyle;
do {
if (relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit] != nullptr) {
return relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit];
}
style = fallBackCache[style];
} while (style != -1);
if (pluralUnit == StandardPlural::OTHER) {
break;
}
pluralUnit = StandardPlural::OTHER;
}
return nullptr; // No formatter found.
}
static UBool getStringWithFallback(
const UResourceBundle *resource,
const char *key,
UnicodeString &result,
UErrorCode &status) {
int32_t len = 0;
const UChar *resStr = ures_getStringByKeyWithFallback(
resource, key, &len, &status);
if (U_FAILURE(status)) {
return FALSE;
}
result.setTo(TRUE, resStr, len);
return TRUE;
}
static UBool getStringByIndex(
const UResourceBundle *resource,
int32_t idx,
UnicodeString &result,
UErrorCode &status) {
int32_t len = 0;
const UChar *resStr = ures_getStringByIndex(
resource, idx, &len, &status);
if (U_FAILURE(status)) {
return FALSE;
}
result.setTo(TRUE, resStr, len);
return TRUE;
}
namespace {
/**
* Sink for enumerating all of the measurement unit display names.
*
* More specific bundles (en_GB) are enumerated before their parents (en_001, en, root):
* Only store a value if it is still missing, that is, it has not been overridden.
*/
struct RelDateTimeFmtDataSink : public ResourceSink {
/**
* Sink for patterns for relative dates and times. For example,
* fields/relative/...
*/
// Generic unit enum for storing Unit info.
typedef enum RelAbsUnit {
INVALID_UNIT = -1,
SECOND,
MINUTE,
HOUR,
DAY,
WEEK,
MONTH,
QUARTER,
YEAR,
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
} RelAbsUnit;
static int32_t relUnitFromGeneric(RelAbsUnit genUnit) {
// Converts the generic units to UDAT_RELATIVE version.
switch (genUnit) {
case SECOND:
return UDAT_REL_UNIT_SECOND;
case MINUTE:
return UDAT_REL_UNIT_MINUTE;
case HOUR:
return UDAT_REL_UNIT_HOUR;
case DAY:
return UDAT_REL_UNIT_DAY;
case WEEK:
return UDAT_REL_UNIT_WEEK;
case MONTH:
return UDAT_REL_UNIT_MONTH;
case QUARTER:
return UDAT_REL_UNIT_QUARTER;
case YEAR:
return UDAT_REL_UNIT_YEAR;
case SUNDAY:
return UDAT_REL_UNIT_SUNDAY;
case MONDAY:
return UDAT_REL_UNIT_MONDAY;
case TUESDAY:
return UDAT_REL_UNIT_TUESDAY;
case WEDNESDAY:
return UDAT_REL_UNIT_WEDNESDAY;
case THURSDAY:
return UDAT_REL_UNIT_THURSDAY;
case FRIDAY:
return UDAT_REL_UNIT_FRIDAY;
case SATURDAY:
return UDAT_REL_UNIT_SATURDAY;
default:
return -1;
}
}
static int32_t absUnitFromGeneric(RelAbsUnit genUnit) {
// Converts the generic units to UDAT_RELATIVE version.
switch (genUnit) {
case DAY:
return UDAT_ABSOLUTE_DAY;
case WEEK:
return UDAT_ABSOLUTE_WEEK;
case MONTH:
return UDAT_ABSOLUTE_MONTH;
case QUARTER:
return UDAT_ABSOLUTE_QUARTER;
case YEAR:
return UDAT_ABSOLUTE_YEAR;
case SUNDAY:
return UDAT_ABSOLUTE_SUNDAY;
case MONDAY:
return UDAT_ABSOLUTE_MONDAY;
case TUESDAY:
return UDAT_ABSOLUTE_TUESDAY;
case WEDNESDAY:
return UDAT_ABSOLUTE_WEDNESDAY;
case THURSDAY:
return UDAT_ABSOLUTE_THURSDAY;
case FRIDAY:
return UDAT_ABSOLUTE_FRIDAY;
case SATURDAY:
return UDAT_ABSOLUTE_SATURDAY;
case HOUR:
return UDAT_ABSOLUTE_HOUR;
case MINUTE:
return UDAT_ABSOLUTE_MINUTE;
default:
return -1;
}
}
static int32_t keyToDirection(const char* key) {
if (uprv_strcmp(key, "-2") == 0) {
return UDAT_DIRECTION_LAST_2;
}
if (uprv_strcmp(key, "-1") == 0) {
return UDAT_DIRECTION_LAST;
}
if (uprv_strcmp(key, "0") == 0) {
return UDAT_DIRECTION_THIS;
}
if (uprv_strcmp(key, "1") == 0) {
return UDAT_DIRECTION_NEXT;
}
if (uprv_strcmp(key, "2") == 0) {
return UDAT_DIRECTION_NEXT_2;
}
return -1;
}
// Values kept between levels of parsing the CLDR data.
int32_t pastFutureIndex; // 0 == past or 1 == future
UDateRelativeDateTimeFormatterStyle style; // {LONG, SHORT, NARROW}
RelAbsUnit genericUnit;
RelativeDateTimeCacheData &outputData;
// Constructor
RelDateTimeFmtDataSink(RelativeDateTimeCacheData& cacheData)
: outputData(cacheData) {
// Clear cacheData.fallBackCache
cacheData.fallBackCache[UDAT_STYLE_LONG] = -1;
cacheData.fallBackCache[UDAT_STYLE_SHORT] = -1;
cacheData.fallBackCache[UDAT_STYLE_NARROW] = -1;
}
~RelDateTimeFmtDataSink();
// Utility functions
static UDateRelativeDateTimeFormatterStyle styleFromString(const char *s) {
int32_t len = static_cast<int32_t>(uprv_strlen(s));
if (len >= 7 && uprv_strcmp(s + len - 7, "-narrow") == 0) {
return UDAT_STYLE_NARROW;
}
if (len >= 6 && uprv_strcmp(s + len - 6, "-short") == 0) {
return UDAT_STYLE_SHORT;
}
return UDAT_STYLE_LONG;
}
static int32_t styleSuffixLength(UDateRelativeDateTimeFormatterStyle style) {
switch (style) {
case UDAT_STYLE_NARROW:
return 7;
case UDAT_STYLE_SHORT:
return 6;
default:
return 0;
}
}
// Utility functions
static UDateRelativeDateTimeFormatterStyle styleFromAliasUnicodeString(UnicodeString s) {
static const UChar narrow[7] = {0x002D, 0x006E, 0x0061, 0x0072, 0x0072, 0x006F, 0x0077};
static const UChar sshort[6] = {0x002D, 0x0073, 0x0068, 0x006F, 0x0072, 0x0074,};
if (s.endsWith(narrow, 7)) {
return UDAT_STYLE_NARROW;
}
if (s.endsWith(sshort, 6)) {
return UDAT_STYLE_SHORT;
}
return UDAT_STYLE_LONG;
}
static RelAbsUnit unitOrNegativeFromString(const char* keyword, int32_t length) {
// Quick check from string to enum.
switch (length) {
case 3:
if (uprv_strncmp(keyword, "day", length) == 0) {
return DAY;
} else if (uprv_strncmp(keyword, "sun", length) == 0) {
return SUNDAY;
} else if (uprv_strncmp(keyword, "mon", length) == 0) {
return MONDAY;
} else if (uprv_strncmp(keyword, "tue", length) == 0) {
return TUESDAY;
} else if (uprv_strncmp(keyword, "wed", length) == 0) {
return WEDNESDAY;
} else if (uprv_strncmp(keyword, "thu", length) == 0) {
return THURSDAY;
} else if (uprv_strncmp(keyword, "fri", length) == 0) {
return FRIDAY;
} else if (uprv_strncmp(keyword, "sat", length) == 0) {
return SATURDAY;
}
break;
case 4:
if (uprv_strncmp(keyword, "hour", length) == 0) {
return HOUR;
} else if (uprv_strncmp(keyword, "week", length) == 0) {
return WEEK;
} else if (uprv_strncmp(keyword, "year", length) == 0) {
return YEAR;
}
break;
case 5:
if (uprv_strncmp(keyword, "month", length) == 0) {
return MONTH;
}
break;
case 6:
if (uprv_strncmp(keyword, "minute", length) == 0) {
return MINUTE;
} else if (uprv_strncmp(keyword, "second", length) == 0) {
return SECOND;
}
break;
case 7:
if (uprv_strncmp(keyword, "quarter", length) == 0) {
return QUARTER; // TODO: Check @provisional
}
break;
default:
break;
}
return INVALID_UNIT;
}
void handlePlainDirection(ResourceValue &value, UErrorCode &errorCode) {
// Handle Display Name for PLAIN direction for some units.
if (U_FAILURE(errorCode)) { return; }
int32_t absUnit = absUnitFromGeneric(genericUnit);
if (absUnit < 0) {
return; // Not interesting.
}
// Store displayname if not set.
if (outputData.absoluteUnits[style]
[absUnit][UDAT_DIRECTION_PLAIN].isEmpty()) {
outputData.absoluteUnits[style]
[absUnit][UDAT_DIRECTION_PLAIN].fastCopyFrom(value.getUnicodeString(errorCode));
return;
}
}
void consumeTableRelative(const char *key, ResourceValue &value, UErrorCode &errorCode) {
ResourceTable unitTypesTable = value.getTable(errorCode);
if (U_FAILURE(errorCode)) { return; }
for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) {
if (value.getType() == URES_STRING) {
int32_t direction = keyToDirection(key);
if (direction < 0) {
continue;
}
int32_t relUnitIndex = relUnitFromGeneric(genericUnit);
if (relUnitIndex == UDAT_REL_UNIT_SECOND && uprv_strcmp(key, "0") == 0 &&
outputData.absoluteUnits[style][UDAT_ABSOLUTE_NOW][UDAT_DIRECTION_PLAIN].isEmpty()) {
// Handle "NOW"
outputData.absoluteUnits[style][UDAT_ABSOLUTE_NOW]
[UDAT_DIRECTION_PLAIN].fastCopyFrom(value.getUnicodeString(errorCode));
}
int32_t absUnitIndex = absUnitFromGeneric(genericUnit);
if (absUnitIndex < 0) {
continue;
}
// Only reset if slot is empty.
if (outputData.absoluteUnits[style][absUnitIndex][direction].isEmpty()) {
outputData.absoluteUnits[style][absUnitIndex]
[direction].fastCopyFrom(value.getUnicodeString(errorCode));
}
}
}
}
void consumeTimeDetail(int32_t relUnitIndex,
const char *key, ResourceValue &value, UErrorCode &errorCode) {
ResourceTable unitTypesTable = value.getTable(errorCode);
if (U_FAILURE(errorCode)) { return; }
for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) {
if (value.getType() == URES_STRING) {
int32_t pluralIndex = StandardPlural::indexOrNegativeFromString(key);
if (pluralIndex >= 0) {
SimpleFormatter **patterns =
outputData.relativeUnitsFormatters[style][relUnitIndex]
[pastFutureIndex];
// Only set if not already established.
if (patterns[pluralIndex] == nullptr) {
patterns[pluralIndex] = new SimpleFormatter(
value.getUnicodeString(errorCode), 0, 1, errorCode);
if (patterns[pluralIndex] == nullptr) {
errorCode = U_MEMORY_ALLOCATION_ERROR;
}
}
}
}
}
}
void consumeTableRelativeTime(const char *key, ResourceValue &value, UErrorCode &errorCode) {
ResourceTable relativeTimeTable = value.getTable(errorCode);
if (U_FAILURE(errorCode)) { return; }
int32_t relUnitIndex = relUnitFromGeneric(genericUnit);
if (relUnitIndex < 0) {
return;
}
for (int32_t i = 0; relativeTimeTable.getKeyAndValue(i, key, value); ++i) {
if (uprv_strcmp(key, "past") == 0) {
pastFutureIndex = 0;
} else if (uprv_strcmp(key, "future") == 0) {
pastFutureIndex = 1;
} else {
// Unknown key.
continue;
}
consumeTimeDetail(relUnitIndex, key, value, errorCode);
}
}
void consumeAlias(const char *key, const ResourceValue &value, UErrorCode &errorCode) {
UDateRelativeDateTimeFormatterStyle sourceStyle = styleFromString(key);
const UnicodeString valueStr = value.getAliasUnicodeString(errorCode);
if (U_FAILURE(errorCode)) { return; }
UDateRelativeDateTimeFormatterStyle targetStyle =
styleFromAliasUnicodeString(valueStr);
if (sourceStyle == targetStyle) {
errorCode = U_INVALID_FORMAT_ERROR;
return;
}
if (outputData.fallBackCache[sourceStyle] != -1 &&
outputData.fallBackCache[sourceStyle] != targetStyle) {
errorCode = U_INVALID_FORMAT_ERROR;
return;
}
outputData.fallBackCache[sourceStyle] = targetStyle;
}
void consumeTimeUnit(const char *key, ResourceValue &value, UErrorCode &errorCode) {
ResourceTable unitTypesTable = value.getTable(errorCode);
if (U_FAILURE(errorCode)) { return; }
for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) {
// Handle display name.
if (uprv_strcmp(key, "dn") == 0 && value.getType() == URES_STRING) {
handlePlainDirection(value, errorCode);
}
if (value.getType() == URES_TABLE) {
if (uprv_strcmp(key, "relative") == 0) {
consumeTableRelative(key, value, errorCode);
} else if (uprv_strcmp(key, "relativeTime") == 0) {
consumeTableRelativeTime(key, value, errorCode);
}
}
}
}
virtual void put(const char *key, ResourceValue &value,
UBool /*noFallback*/, UErrorCode &errorCode) {
// Main entry point to sink
ResourceTable table = value.getTable(errorCode);
if (U_FAILURE(errorCode)) { return; }
for (int32_t i = 0; table.getKeyAndValue(i, key, value); ++i) {
if (value.getType() == URES_ALIAS) {
consumeAlias(key, value, errorCode);
} else {
style = styleFromString(key);
int32_t unitSize = static_cast<int32_t>(uprv_strlen(key)) - styleSuffixLength(style);
genericUnit = unitOrNegativeFromString(key, unitSize);
if (style >= 0 && genericUnit != INVALID_UNIT) {
consumeTimeUnit(key, value, errorCode);
}
}
}
}
};
// Virtual destructors must be defined out of line.
RelDateTimeFmtDataSink::~RelDateTimeFmtDataSink() {}
} // namespace
static const DateFormatSymbols::DtWidthType styleToDateFormatSymbolWidth[UDAT_STYLE_COUNT] = {
DateFormatSymbols::WIDE, DateFormatSymbols::SHORT, DateFormatSymbols::NARROW
};
// Get days of weeks from the DateFormatSymbols class.
static void loadWeekdayNames(UnicodeString absoluteUnits[UDAT_STYLE_COUNT]
[UDAT_ABSOLUTE_UNIT_COUNT][UDAT_DIRECTION_COUNT],
const char* localeId,
UErrorCode& status) {
if (U_FAILURE(status)) {
return;
}
Locale locale(localeId);
DateFormatSymbols dfSym(locale, status);
if (U_FAILURE(status)) {
return;
}
for (int32_t style = 0; style < UDAT_STYLE_COUNT; ++style) {
DateFormatSymbols::DtWidthType dtfmtWidth = styleToDateFormatSymbolWidth[style];
int32_t count;
const UnicodeString* weekdayNames =
dfSym.getWeekdays(count, DateFormatSymbols::STANDALONE, dtfmtWidth);
for (int32_t dayIndex = UDAT_ABSOLUTE_SUNDAY;
dayIndex <= UDAT_ABSOLUTE_SATURDAY; ++ dayIndex) {
int32_t dateSymbolIndex = (dayIndex - UDAT_ABSOLUTE_SUNDAY) + UCAL_SUNDAY;
absoluteUnits[style][dayIndex][UDAT_DIRECTION_PLAIN].fastCopyFrom(
weekdayNames[dateSymbolIndex]);
}
}
}
static UBool loadUnitData(
const UResourceBundle *resource,
RelativeDateTimeCacheData &cacheData,
const char* localeId,
UErrorCode &status) {
RelDateTimeFmtDataSink sink(cacheData);
ures_getAllItemsWithFallback(resource, "fields", sink, status);
if (U_FAILURE(status)) {
return false;
}
// Get the weekday names from DateFormatSymbols.
loadWeekdayNames(cacheData.absoluteUnits, localeId, status);
return U_SUCCESS(status);
}
static UBool getDateTimePattern(
const UResourceBundle *resource,
UnicodeString &result,
UErrorCode &status) {
UnicodeString defaultCalendarName;
if (!getStringWithFallback(
resource,
"calendar/default",
defaultCalendarName,
status)) {
return FALSE;
}
CharString pathBuffer;
pathBuffer.append("calendar/", status)
.appendInvariantChars(defaultCalendarName, status)
.append("/DateTimePatterns", status);
LocalUResourceBundlePointer topLevel(
ures_getByKeyWithFallback(
resource, pathBuffer.data(), nullptr, &status));
if (U_FAILURE(status)) {
return FALSE;
}
int32_t size = ures_getSize(topLevel.getAlias());
if (size <= 8) {
// Oops, size is too small to access the index that we want, fallback
// to a hard-coded value.
result = UNICODE_STRING_SIMPLE("{1} {0}");
return TRUE;
}
return getStringByIndex(topLevel.getAlias(), 8, result, status);
}
template<> U_I18N_API
const RelativeDateTimeCacheData *LocaleCacheKey<RelativeDateTimeCacheData>::createObject(const void * /*unused*/, UErrorCode &status) const {
const char *localeId = fLoc.getName();
LocalUResourceBundlePointer topLevel(ures_open(nullptr, localeId, &status));
if (U_FAILURE(status)) {
return nullptr;
}
LocalPointer<RelativeDateTimeCacheData> result(
new RelativeDateTimeCacheData());
if (result.isNull()) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
if (!loadUnitData(
topLevel.getAlias(),
*result,
localeId,
status)) {
return nullptr;
}
UnicodeString dateTimePattern;
if (!getDateTimePattern(topLevel.getAlias(), dateTimePattern, status)) {
return nullptr;
}
result->adoptCombinedDateAndTime(
new SimpleFormatter(dateTimePattern, 2, 2, status));
if (U_FAILURE(status)) {
return nullptr;
}
result->addRef();
return result.orphan();
}
static constexpr FormattedStringBuilder::Field kRDTNumericField
= {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD};
static constexpr FormattedStringBuilder::Field kRDTLiteralField
= {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD};
class FormattedRelativeDateTimeData : public FormattedValueStringBuilderImpl {
public:
FormattedRelativeDateTimeData() : FormattedValueStringBuilderImpl(kRDTNumericField) {}
virtual ~FormattedRelativeDateTimeData();
};
FormattedRelativeDateTimeData::~FormattedRelativeDateTimeData() = default;
UPRV_FORMATTED_VALUE_SUBCLASS_AUTO_IMPL(FormattedRelativeDateTime)
RelativeDateTimeFormatter::RelativeDateTimeFormatter(UErrorCode& status) :
fCache(nullptr),
fNumberFormat(nullptr),
fPluralRules(nullptr),
fStyle(UDAT_STYLE_LONG),
fContext(UDISPCTX_CAPITALIZATION_NONE),
fOptBreakIterator(nullptr) {
init(nullptr, nullptr, status);
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(
const Locale& locale, UErrorCode& status) :
fCache(nullptr),
fNumberFormat(nullptr),
fPluralRules(nullptr),
fStyle(UDAT_STYLE_LONG),
fContext(UDISPCTX_CAPITALIZATION_NONE),
fOptBreakIterator(nullptr),
fLocale(locale) {
init(nullptr, nullptr, status);
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(
const Locale& locale, NumberFormat *nfToAdopt, UErrorCode& status) :
fCache(nullptr),
fNumberFormat(nullptr),
fPluralRules(nullptr),
fStyle(UDAT_STYLE_LONG),
fContext(UDISPCTX_CAPITALIZATION_NONE),
fOptBreakIterator(nullptr),
fLocale(locale) {
init(nfToAdopt, nullptr, status);
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(
const Locale& locale,
NumberFormat *nfToAdopt,
UDateRelativeDateTimeFormatterStyle styl,
UDisplayContext capitalizationContext,
UErrorCode& status) :
fCache(nullptr),
fNumberFormat(nullptr),
fPluralRules(nullptr),
fStyle(styl),
fContext(capitalizationContext),
fOptBreakIterator(nullptr),
fLocale(locale) {
if (U_FAILURE(status)) {
return;
}
if ((capitalizationContext >> 8) != UDISPCTX_TYPE_CAPITALIZATION) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
if (capitalizationContext == UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE) {
BreakIterator *bi = BreakIterator::createSentenceInstance(locale, status);
if (U_FAILURE(status)) {
return;
}
init(nfToAdopt, bi, status);
} else {
init(nfToAdopt, nullptr, status);
}
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(
const RelativeDateTimeFormatter& other)
: UObject(other),
fCache(other.fCache),
fNumberFormat(other.fNumberFormat),
fPluralRules(other.fPluralRules),
fStyle(other.fStyle),
fContext(other.fContext),
fOptBreakIterator(other.fOptBreakIterator),
fLocale(other.fLocale) {
fCache->addRef();
fNumberFormat->addRef();
fPluralRules->addRef();
if (fOptBreakIterator != nullptr) {
fOptBreakIterator->addRef();
}
}
RelativeDateTimeFormatter& RelativeDateTimeFormatter::operator=(
const RelativeDateTimeFormatter& other) {
if (this != &other) {
SharedObject::copyPtr(other.fCache, fCache);
SharedObject::copyPtr(other.fNumberFormat, fNumberFormat);
SharedObject::copyPtr(other.fPluralRules, fPluralRules);
SharedObject::copyPtr(other.fOptBreakIterator, fOptBreakIterator);
fStyle = other.fStyle;
fContext = other.fContext;
fLocale = other.fLocale;
}
return *this;
}
RelativeDateTimeFormatter::~RelativeDateTimeFormatter() {
if (fCache != nullptr) {
fCache->removeRef();
}
if (fNumberFormat != nullptr) {
fNumberFormat->removeRef();
}
if (fPluralRules != nullptr) {
fPluralRules->removeRef();
}
if (fOptBreakIterator != nullptr) {
fOptBreakIterator->removeRef();
}
}
const NumberFormat& RelativeDateTimeFormatter::getNumberFormat() const {
return **fNumberFormat;
}
UDisplayContext RelativeDateTimeFormatter::getCapitalizationContext() const {
return fContext;
}
UDateRelativeDateTimeFormatterStyle RelativeDateTimeFormatter::getFormatStyle() const {
return fStyle;
}
// To reduce boilerplate code, we use a helper function that forwards variadic
// arguments to the formatImpl function.
template<typename F, typename... Args>
UnicodeString& RelativeDateTimeFormatter::doFormat(
F callback,
UnicodeString& appendTo,
UErrorCode& status,
Args... args) const {
FormattedRelativeDateTimeData output;
(this->*callback)(std::forward<Args>(args)..., output, status);
if (U_FAILURE(status)) {
return appendTo;
}
UnicodeString result = output.getStringRef().toUnicodeString();
return appendTo.append(adjustForContext(result));
}
template<typename F, typename... Args>
FormattedRelativeDateTime RelativeDateTimeFormatter::doFormatToValue(
F callback,
UErrorCode& status,
Args... args) const {
if (!checkNoAdjustForContext(status)) {
return FormattedRelativeDateTime(status);
}
LocalPointer<FormattedRelativeDateTimeData> output(
new FormattedRelativeDateTimeData(), status);
if (U_FAILURE(status)) {
return FormattedRelativeDateTime(status);
}
(this->*callback)(std::forward<Args>(args)..., *output, status);
output->getStringRef().writeTerminator(status);
return FormattedRelativeDateTime(output.orphan());
}
UnicodeString& RelativeDateTimeFormatter::format(
double quantity,
UDateDirection direction,
UDateRelativeUnit unit,
UnicodeString& appendTo,
UErrorCode& status) const {
return doFormat(
&RelativeDateTimeFormatter::formatImpl,
appendTo,
status,
quantity,
direction,
unit);
}
FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue(
double quantity,
UDateDirection direction,
UDateRelativeUnit unit,
UErrorCode& status) const {
return doFormatToValue(
&RelativeDateTimeFormatter::formatImpl,
status,
quantity,
direction,
unit);
}
void RelativeDateTimeFormatter::formatImpl(
double quantity,
UDateDirection direction,
UDateRelativeUnit unit,
FormattedRelativeDateTimeData& output,
UErrorCode& status) const {
if (U_FAILURE(status)) {
return;
}
if (direction != UDAT_DIRECTION_LAST && direction != UDAT_DIRECTION_NEXT) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0;
StandardPlural::Form pluralForm;
QuantityFormatter::formatAndSelect(
quantity,
**fNumberFormat,
**fPluralRules,
output.getStringRef(),
pluralForm,
status);
if (U_FAILURE(status)) {
return;
}
const SimpleFormatter* formatter =
fCache->getRelativeUnitFormatter(fStyle, unit, bFuture, pluralForm);
if (formatter == nullptr) {
// TODO: WARN - look at quantity formatter's action with an error.
status = U_INVALID_FORMAT_ERROR;
return;
}
number::impl::SimpleModifier modifier(*formatter, kRDTLiteralField, false);
modifier.formatAsPrefixSuffix(
output.getStringRef(), 0, output.getStringRef().length(), status);
}
UnicodeString& RelativeDateTimeFormatter::formatNumeric(
double offset,
URelativeDateTimeUnit unit,
UnicodeString& appendTo,
UErrorCode& status) const {
return doFormat(
&RelativeDateTimeFormatter::formatNumericImpl,
appendTo,
status,
offset,
unit);
}
FormattedRelativeDateTime RelativeDateTimeFormatter::formatNumericToValue(
double offset,
URelativeDateTimeUnit unit,
UErrorCode& status) const {
return doFormatToValue(
&RelativeDateTimeFormatter::formatNumericImpl,
status,
offset,
unit);
}
void RelativeDateTimeFormatter::formatNumericImpl(
double offset,
URelativeDateTimeUnit unit,
FormattedRelativeDateTimeData& output,
UErrorCode& status) const {
if (U_FAILURE(status)) {
return;
}
UDateDirection direction = UDAT_DIRECTION_NEXT;
if (std::signbit(offset)) { // needed to handle -0.0
direction = UDAT_DIRECTION_LAST;
offset = -offset;
}
if (direction != UDAT_DIRECTION_LAST && direction != UDAT_DIRECTION_NEXT) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0;
StandardPlural::Form pluralForm;
QuantityFormatter::formatAndSelect(
offset,
**fNumberFormat,
**fPluralRules,
output.getStringRef(),
pluralForm,
status);
if (U_FAILURE(status)) {
return;
}
const SimpleFormatter* formatter =
fCache->getRelativeDateTimeUnitFormatter(fStyle, unit, bFuture, pluralForm);
if (formatter == nullptr) {
// TODO: WARN - look at quantity formatter's action with an error.
status = U_INVALID_FORMAT_ERROR;
return;
}
number::impl::SimpleModifier modifier(*formatter, kRDTLiteralField, false);
modifier.formatAsPrefixSuffix(
output.getStringRef(), 0, output.getStringRef().length(), status);
}
UnicodeString& RelativeDateTimeFormatter::format(
UDateDirection direction,
UDateAbsoluteUnit unit,
UnicodeString& appendTo,
UErrorCode& status) const {
return doFormat(
&RelativeDateTimeFormatter::formatAbsoluteImpl,
appendTo,
status,
direction,
unit);
}
FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue(
UDateDirection direction,
UDateAbsoluteUnit unit,
UErrorCode& status) const {
return doFormatToValue(
&RelativeDateTimeFormatter::formatAbsoluteImpl,
status,
direction,
unit);
}
void RelativeDateTimeFormatter::formatAbsoluteImpl(
UDateDirection direction,
UDateAbsoluteUnit unit,
FormattedRelativeDateTimeData& output,
UErrorCode& status) const {
if (U_FAILURE(status)) {
return;
}
if (unit == UDAT_ABSOLUTE_NOW && direction != UDAT_DIRECTION_PLAIN) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
// Get string using fallback.
output.getStringRef().append(
fCache->getAbsoluteUnitString(fStyle, unit, direction),
kRDTLiteralField,
status);
}
UnicodeString& RelativeDateTimeFormatter::format(
double offset,
URelativeDateTimeUnit unit,
UnicodeString& appendTo,
UErrorCode& status) const {
return doFormat(
&RelativeDateTimeFormatter::formatRelativeImpl,
appendTo,
status,
offset,
unit);
}
FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue(
double offset,
URelativeDateTimeUnit unit,
UErrorCode& status) const {
return doFormatToValue(
&RelativeDateTimeFormatter::formatRelativeImpl,
status,
offset,
unit);
}
void RelativeDateTimeFormatter::formatRelativeImpl(
double offset,
URelativeDateTimeUnit unit,
FormattedRelativeDateTimeData& output,
UErrorCode& status) const {
if (U_FAILURE(status)) {
return;
}
// TODO:
// The full implementation of this depends on CLDR data that is not yet available,
// see: http://unicode.org/cldr/trac/ticket/9165 Add more relative field data.
// In the meantime do a quick bring-up by calling the old format method; this
// leaves some holes (even for data that is currently available, such as quarter).
// When the new CLDR data is available, update the data storage accordingly,
// rewrite this to use it directly, and rewrite the old format method to call this
// new one; that is covered by http://bugs.icu-project.org/trac/ticket/12171.
UDateDirection direction = UDAT_DIRECTION_COUNT;
if (offset > -2.1 && offset < 2.1) {
// Allow a 1% epsilon, so offsets in -1.01..-0.99 map to LAST
double offsetx100 = offset * 100.0;
int32_t intoffset = (offsetx100 < 0)? (int32_t)(offsetx100-0.5) : (int32_t)(offsetx100+0.5);
switch (intoffset) {
case -200/*-2*/: direction = UDAT_DIRECTION_LAST_2; break;
case -100/*-1*/: direction = UDAT_DIRECTION_LAST; break;
case 0/* 0*/: direction = UDAT_DIRECTION_THIS; break;
case 100/* 1*/: direction = UDAT_DIRECTION_NEXT; break;
case 200/* 2*/: direction = UDAT_DIRECTION_NEXT_2; break;
default: break;
}
}
UDateAbsoluteUnit absunit = UDAT_ABSOLUTE_UNIT_COUNT;
switch (unit) {
case UDAT_REL_UNIT_YEAR: absunit = UDAT_ABSOLUTE_YEAR; break;
case UDAT_REL_UNIT_QUARTER: absunit = UDAT_ABSOLUTE_QUARTER; break;
case UDAT_REL_UNIT_MONTH: absunit = UDAT_ABSOLUTE_MONTH; break;
case UDAT_REL_UNIT_WEEK: absunit = UDAT_ABSOLUTE_WEEK; break;
case UDAT_REL_UNIT_DAY: absunit = UDAT_ABSOLUTE_DAY; break;
case UDAT_REL_UNIT_SECOND:
if (direction == UDAT_DIRECTION_THIS) {
absunit = UDAT_ABSOLUTE_NOW;
direction = UDAT_DIRECTION_PLAIN;
}
break;
case UDAT_REL_UNIT_SUNDAY: absunit = UDAT_ABSOLUTE_SUNDAY; break;
case UDAT_REL_UNIT_MONDAY: absunit = UDAT_ABSOLUTE_MONDAY; break;
case UDAT_REL_UNIT_TUESDAY: absunit = UDAT_ABSOLUTE_TUESDAY; break;
case UDAT_REL_UNIT_WEDNESDAY: absunit = UDAT_ABSOLUTE_WEDNESDAY; break;
case UDAT_REL_UNIT_THURSDAY: absunit = UDAT_ABSOLUTE_THURSDAY; break;
case UDAT_REL_UNIT_FRIDAY: absunit = UDAT_ABSOLUTE_FRIDAY; break;
case UDAT_REL_UNIT_SATURDAY: absunit = UDAT_ABSOLUTE_SATURDAY; break;
case UDAT_REL_UNIT_HOUR: absunit = UDAT_ABSOLUTE_HOUR; break;
case UDAT_REL_UNIT_MINUTE: absunit = UDAT_ABSOLUTE_MINUTE; break;
default: break;
}
if (direction != UDAT_DIRECTION_COUNT && absunit != UDAT_ABSOLUTE_UNIT_COUNT) {
formatAbsoluteImpl(direction, absunit, output, status);
if (output.getStringRef().length() != 0) {
return;
}
}
// otherwise fallback to formatNumeric
formatNumericImpl(offset, unit, output, status);
}
UnicodeString& RelativeDateTimeFormatter::combineDateAndTime(
const UnicodeString& relativeDateString, const UnicodeString& timeString,
UnicodeString& appendTo, UErrorCode& status) const {
return fCache->getCombinedDateAndTime()->format(
timeString, relativeDateString, appendTo, status);
}
UnicodeString& RelativeDateTimeFormatter::adjustForContext(UnicodeString &str) const {
if (fOptBreakIterator == nullptr
|| str.length() == 0 || !u_islower(str.char32At(0))) {
return str;
}
// Must guarantee that one thread at a time accesses the shared break
// iterator.
static UMutex gBrkIterMutex;
Mutex lock(&gBrkIterMutex);
str.toTitle(
fOptBreakIterator->get(),
fLocale,
U_TITLECASE_NO_LOWERCASE | U_TITLECASE_NO_BREAK_ADJUSTMENT);
return str;
}
UBool RelativeDateTimeFormatter::checkNoAdjustForContext(UErrorCode& status) const {
// This is unsupported because it's hard to keep fields in sync with title
// casing. The code could be written and tested if there is demand.
if (fOptBreakIterator != nullptr) {
status = U_UNSUPPORTED_ERROR;
return FALSE;
}
return TRUE;
}
void RelativeDateTimeFormatter::init(
NumberFormat *nfToAdopt,
BreakIterator *biToAdopt,
UErrorCode &status) {
LocalPointer<NumberFormat> nf(nfToAdopt);
LocalPointer<BreakIterator> bi(biToAdopt);
UnifiedCache::getByLocale(fLocale, fCache, status);
if (U_FAILURE(status)) {
return;
}
const SharedPluralRules *pr = PluralRules::createSharedInstance(
fLocale, UPLURAL_TYPE_CARDINAL, status);
if (U_FAILURE(status)) {
return;
}
SharedObject::copyPtr(pr, fPluralRules);
pr->removeRef();
if (nf.isNull()) {
const SharedNumberFormat *shared = NumberFormat::createSharedInstance(
fLocale, UNUM_DECIMAL, status);
if (U_FAILURE(status)) {
return;
}
SharedObject::copyPtr(shared, fNumberFormat);
shared->removeRef();
} else {
SharedNumberFormat *shared = new SharedNumberFormat(nf.getAlias());
if (shared == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
nf.orphan();
SharedObject::copyPtr(shared, fNumberFormat);
}
if (bi.isNull()) {
SharedObject::clearPtr(fOptBreakIterator);
} else {
SharedBreakIterator *shared = new SharedBreakIterator(bi.getAlias());
if (shared == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
bi.orphan();
SharedObject::copyPtr(shared, fOptBreakIterator);
}
}
U_NAMESPACE_END
// Plain C API
U_NAMESPACE_USE
// Magic number: "FRDT" (FormattedRelativeDateTime) in ASCII
UPRV_FORMATTED_VALUE_CAPI_AUTO_IMPL(
FormattedRelativeDateTime,
UFormattedRelativeDateTime,
UFormattedRelativeDateTimeImpl,
UFormattedRelativeDateTimeApiHelper,
ureldatefmt,
0x46524454)
U_CAPI URelativeDateTimeFormatter* U_EXPORT2
ureldatefmt_open( const char* locale,
UNumberFormat* nfToAdopt,
UDateRelativeDateTimeFormatterStyle width,
UDisplayContext capitalizationContext,
UErrorCode* status )
{
if (U_FAILURE(*status)) {
return nullptr;
}
LocalPointer<RelativeDateTimeFormatter> formatter(new RelativeDateTimeFormatter(Locale(locale),
(NumberFormat*)nfToAdopt, width,
capitalizationContext, *status), *status);
if (U_FAILURE(*status)) {
return nullptr;
}
return (URelativeDateTimeFormatter*)formatter.orphan();
}
U_CAPI void U_EXPORT2
ureldatefmt_close(URelativeDateTimeFormatter *reldatefmt)
{
delete (RelativeDateTimeFormatter*)reldatefmt;
}
U_CAPI int32_t U_EXPORT2
ureldatefmt_formatNumeric( const URelativeDateTimeFormatter* reldatefmt,
double offset,
URelativeDateTimeUnit unit,
UChar* result,
int32_t resultCapacity,
UErrorCode* status)
{
if (U_FAILURE(*status)) {
return 0;
}
if (result == nullptr ? resultCapacity != 0 : resultCapacity < 0) {
*status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
UnicodeString res;
if (result != nullptr) {
// nullptr destination for pure preflighting: empty dummy string
// otherwise, alias the destination buffer (copied from udat_format)
res.setTo(result, 0, resultCapacity);
}
((RelativeDateTimeFormatter*)reldatefmt)->formatNumeric(offset, unit, res, *status);
if (U_FAILURE(*status)) {
return 0;
}
return res.extract(result, resultCapacity, *status);
}
U_CAPI void U_EXPORT2
ureldatefmt_formatNumericToResult(
const URelativeDateTimeFormatter* reldatefmt,
double offset,
URelativeDateTimeUnit unit,
UFormattedRelativeDateTime* result,
UErrorCode* status) {
if (U_FAILURE(*status)) {
return;
}
auto* fmt = reinterpret_cast<const RelativeDateTimeFormatter*>(reldatefmt);
auto* resultImpl = UFormattedRelativeDateTimeApiHelper::validate(result, *status);
resultImpl->fImpl = fmt->formatNumericToValue(offset, unit, *status);
}
U_CAPI int32_t U_EXPORT2
ureldatefmt_format( const URelativeDateTimeFormatter* reldatefmt,
double offset,
URelativeDateTimeUnit unit,
UChar* result,
int32_t resultCapacity,
UErrorCode* status)
{
if (U_FAILURE(*status)) {
return 0;
}
if (result == nullptr ? resultCapacity != 0 : resultCapacity < 0) {
*status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
UnicodeString res;
if (result != nullptr) {
// nullptr destination for pure preflighting: empty dummy string
// otherwise, alias the destination buffer (copied from udat_format)
res.setTo(result, 0, resultCapacity);
}
((RelativeDateTimeFormatter*)reldatefmt)->format(offset, unit, res, *status);
if (U_FAILURE(*status)) {
return 0;
}
return res.extract(result, resultCapacity, *status);
}
U_CAPI void U_EXPORT2
ureldatefmt_formatToResult(
const URelativeDateTimeFormatter* reldatefmt,
double offset,
URelativeDateTimeUnit unit,
UFormattedRelativeDateTime* result,
UErrorCode* status) {
if (U_FAILURE(*status)) {
return;
}
auto* fmt = reinterpret_cast<const RelativeDateTimeFormatter*>(reldatefmt);
auto* resultImpl = UFormattedRelativeDateTimeApiHelper::validate(result, *status);
resultImpl->fImpl = fmt->formatToValue(offset, unit, *status);
}
U_CAPI int32_t U_EXPORT2
ureldatefmt_combineDateAndTime( const URelativeDateTimeFormatter* reldatefmt,
const UChar * relativeDateString,
int32_t relativeDateStringLen,
const UChar * timeString,
int32_t timeStringLen,
UChar* result,
int32_t resultCapacity,
UErrorCode* status )
{
if (U_FAILURE(*status)) {
return 0;
}
if (result == nullptr ? resultCapacity != 0 : resultCapacity < 0 ||
(relativeDateString == nullptr ? relativeDateStringLen != 0 : relativeDateStringLen < -1) ||
(timeString == nullptr ? timeStringLen != 0 : timeStringLen < -1)) {
*status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
UnicodeString relDateStr((UBool)(relativeDateStringLen == -1), relativeDateString, relativeDateStringLen);
UnicodeString timeStr((UBool)(timeStringLen == -1), timeString, timeStringLen);
UnicodeString res(result, 0, resultCapacity);
((RelativeDateTimeFormatter*)reldatefmt)->combineDateAndTime(relDateStr, timeStr, res, *status);
if (U_FAILURE(*status)) {
return 0;
}
return res.extract(result, resultCapacity, *status);
}
#endif /* !UCONFIG_NO_FORMATTING */