ICU-13256 Implementing FormattedRelativeDateTime in C, C++, and Java.

- Adds additional logic to NumberStringBuilder.
- Extends logic of number::impl::Field type.
- Adds tests for RBNF support.
- Adds tests from ftang's original PR.
This commit is contained in:
Shane Carr 2019-02-08 22:08:16 -08:00 committed by Shane F. Carr
parent c70a9db818
commit 249e03ccd6
47 changed files with 1936 additions and 208 deletions

View file

@ -112,7 +112,7 @@ numparse_stringsegment.o numparse_parsednumber.o numparse_impl.o \
numparse_symbols.o numparse_decimal.o numparse_scientific.o numparse_currency.o \
numparse_affixes.o numparse_compositions.o numparse_validators.o \
numrange_fluent.o numrange_impl.o \
erarules.o formattedvalue.o formattedval_iterimpl.o
erarules.o formattedvalue.o formattedval_iterimpl.o formattedval_sbimpl.o
## Header files to install
HEADERS = $(srcdir)/unicode/*.h

View file

@ -18,6 +18,7 @@
#include "fphdlimp.h"
#include "util.h"
#include "uvectr32.h"
#include "number_stringbuilder.h"
U_NAMESPACE_BEGIN
@ -44,12 +45,35 @@ public:
void appendString(UnicodeString string, UErrorCode& status);
private:
// Final data:
UnicodeString fString;
UVector32 fFields;
};
class FormattedValueNumberStringBuilderImpl : public UMemory, public FormattedValue {
public:
FormattedValueNumberStringBuilderImpl(number::impl::Field numericField);
virtual ~FormattedValueNumberStringBuilderImpl();
// Implementation of FormattedValue (const):
UnicodeString toString(UErrorCode& status) const U_OVERRIDE;
UnicodeString toTempString(UErrorCode& status) const U_OVERRIDE;
Appendable& appendTo(Appendable& appendable, UErrorCode& status) const U_OVERRIDE;
UBool nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const U_OVERRIDE;
inline number::impl::NumberStringBuilder& getStringRef() {
return fString;
}
private:
number::impl::NumberStringBuilder fString;
number::impl::Field fNumericField;
};
// C API Helpers for FormattedValue
// Magic number as ASCII == "UFV"
struct UFormattedValueImpl;

View file

@ -0,0 +1,46 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
// This file contains one implementation of FormattedValue.
// Other independent implementations should go into their own cpp file for
// better dependency modularization.
#include "formattedval_impl.h"
U_NAMESPACE_BEGIN
FormattedValueNumberStringBuilderImpl::FormattedValueNumberStringBuilderImpl(number::impl::Field numericField)
: fNumericField(numericField) {
}
FormattedValueNumberStringBuilderImpl::~FormattedValueNumberStringBuilderImpl() {
}
UnicodeString FormattedValueNumberStringBuilderImpl::toString(UErrorCode&) const {
return fString.toUnicodeString();
}
UnicodeString FormattedValueNumberStringBuilderImpl::toTempString(UErrorCode&) const {
return fString.toTempUnicodeString();
}
Appendable& FormattedValueNumberStringBuilderImpl::appendTo(Appendable& appendable, UErrorCode&) const {
appendable.appendString(fString.chars(), fString.length());
return appendable;
}
UBool FormattedValueNumberStringBuilderImpl::nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const {
// NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
return fString.nextPosition(cfpos, fNumericField, status) ? TRUE : FALSE;
}
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -51,7 +51,7 @@ void ConstrainedFieldPosition::setState(
fLimit = limit;
}
UBool ConstrainedFieldPosition::matchesField(UFieldCategory category, int32_t field) {
UBool ConstrainedFieldPosition::matchesField(int32_t category, int32_t field) {
switch (fConstraint) {
case UCFPOS_CONSTRAINT_NONE:
return TRUE;

View file

@ -239,6 +239,7 @@
<ClCompile Include="fmtable_cnv.cpp" />
<ClCompile Include="format.cpp" />
<ClCompile Include="formattedval_iterimpl.cpp" />
<ClCompile Include="formattedval_sbimpl.cpp" />
<ClCompile Include="formattedvalue.cpp" />
<ClCompile Include="fphdlimp.cpp" />
<ClCompile Include="fpositer.cpp" />

View file

@ -159,6 +159,9 @@
<ClCompile Include="formattedval_iterimpl.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="formattedval_sbimpl.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="formattedvalue.cpp">
<Filter>formatting</Filter>
</ClCompile>

View file

@ -346,6 +346,7 @@
<ClCompile Include="fmtable_cnv.cpp" />
<ClCompile Include="format.cpp" />
<ClCompile Include="formattedval_iterimpl.cpp" />
<ClCompile Include="formattedval_sbimpl.cpp" />
<ClCompile Include="formattedvalue.cpp" />
<ClCompile Include="fphdlimp.cpp" />
<ClCompile Include="fpositer.cpp" />

View file

@ -131,25 +131,25 @@ UnicodeString AffixUtils::escape(const UnicodeString &input) {
Field AffixUtils::getFieldForType(AffixPatternType type) {
switch (type) {
case TYPE_MINUS_SIGN:
return Field::UNUM_SIGN_FIELD;
return UNUM_SIGN_FIELD;
case TYPE_PLUS_SIGN:
return Field::UNUM_SIGN_FIELD;
return UNUM_SIGN_FIELD;
case TYPE_PERCENT:
return Field::UNUM_PERCENT_FIELD;
return UNUM_PERCENT_FIELD;
case TYPE_PERMILLE:
return Field::UNUM_PERMILL_FIELD;
return UNUM_PERMILL_FIELD;
case TYPE_CURRENCY_SINGLE:
return Field::UNUM_CURRENCY_FIELD;
return UNUM_CURRENCY_FIELD;
case TYPE_CURRENCY_DOUBLE:
return Field::UNUM_CURRENCY_FIELD;
return UNUM_CURRENCY_FIELD;
case TYPE_CURRENCY_TRIPLE:
return Field::UNUM_CURRENCY_FIELD;
return UNUM_CURRENCY_FIELD;
case TYPE_CURRENCY_QUAD:
return Field::UNUM_CURRENCY_FIELD;
return UNUM_CURRENCY_FIELD;
case TYPE_CURRENCY_QUINT:
return Field::UNUM_CURRENCY_FIELD;
return UNUM_CURRENCY_FIELD;
case TYPE_CURRENCY_OVERFLOW:
return Field::UNUM_CURRENCY_FIELD;
return UNUM_CURRENCY_FIELD;
default:
UPRV_UNREACHABLE;
}

View file

@ -156,7 +156,7 @@ SimpleModifier::SimpleModifier()
int32_t SimpleModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
UErrorCode &status) const {
return formatAsPrefixSuffix(output, leftIndex, rightIndex, fField, status);
return formatAsPrefixSuffix(output, leftIndex, rightIndex, status);
}
int32_t SimpleModifier::getPrefixLength() const {
@ -204,13 +204,13 @@ bool SimpleModifier::semanticallyEquivalent(const Modifier& other) const {
int32_t
SimpleModifier::formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startIndex, int32_t endIndex,
Field field, UErrorCode &status) const {
UErrorCode &status) const {
if (fSuffixOffset == -1 && fPrefixLength + fSuffixLength > 0) {
// There is no argument for the inner number; overwrite the entire segment with our string.
return result.splice(startIndex, endIndex, fCompiledPattern, 2, 2 + fPrefixLength, field, status);
return result.splice(startIndex, endIndex, fCompiledPattern, 2, 2 + fPrefixLength, fField, status);
} else {
if (fPrefixLength > 0) {
result.insert(startIndex, fCompiledPattern, 2, 2 + fPrefixLength, field, status);
result.insert(startIndex, fCompiledPattern, 2, 2 + fPrefixLength, fField, status);
}
if (fSuffixLength > 0) {
result.insert(
@ -218,7 +218,7 @@ SimpleModifier::formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startI
fCompiledPattern,
1 + fSuffixOffset,
1 + fSuffixOffset + fSuffixLength,
field,
fField,
status);
}
return fPrefixLength + fSuffixLength;

View file

@ -100,7 +100,7 @@ class U_I18N_API SimpleModifier : public Modifier, public UMemory {
* @return The number of characters (UTF-16 code points) that were added to the StringBuilder.
*/
int32_t
formatAsPrefixSuffix(NumberStringBuilder& result, int32_t startIndex, int32_t endIndex, Field field,
formatAsPrefixSuffix(NumberStringBuilder& result, int32_t startIndex, int32_t endIndex,
UErrorCode& status) const;
/**

View file

@ -74,7 +74,7 @@ UBool FormattedNumber::nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode&
return FALSE;
}
// NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
return fResults->string.nextPosition(cfpos, status) ? TRUE : FALSE;
return fResults->string.nextPosition(cfpos, 0, status) ? TRUE : FALSE;
}
UBool FormattedNumber::nextFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) const {

View file

@ -8,6 +8,7 @@
#include "number_stringbuilder.h"
#include "static_unicode_sets.h"
#include "unicode/utf16.h"
#include "number_utils.h"
using namespace icu;
using namespace icu::number;
@ -41,7 +42,7 @@ NumberStringBuilder::NumberStringBuilder() {
getCharPtr()[i] = 1;
}
#endif
};
}
NumberStringBuilder::~NumberStringBuilder() {
if (fUsingHeap) {
@ -449,7 +450,7 @@ bool NumberStringBuilder::nextFieldPosition(FieldPosition& fp, UErrorCode& statu
ConstrainedFieldPosition cfpos;
cfpos.constrainField(UFIELD_CATEGORY_NUMBER, rawField);
cfpos.setState(UFIELD_CATEGORY_NUMBER, rawField, fp.getBeginIndex(), fp.getEndIndex());
if (nextPosition(cfpos, status)) {
if (nextPosition(cfpos, 0, status)) {
fp.setBeginIndex(cfpos.getStart());
fp.setEndIndex(cfpos.getLimit());
return true;
@ -476,25 +477,21 @@ bool NumberStringBuilder::nextFieldPosition(FieldPosition& fp, UErrorCode& statu
void NumberStringBuilder::getAllFieldPositions(FieldPositionIteratorHandler& fpih,
UErrorCode& status) const {
ConstrainedFieldPosition cfpos;
while (nextPosition(cfpos, status)) {
while (nextPosition(cfpos, 0, status)) {
fpih.addAttribute(cfpos.getField(), cfpos.getStart(), cfpos.getLimit());
}
}
bool NumberStringBuilder::nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& /*status*/) const {
bool isSearchingForField = false;
if (cfpos.getConstraintType() == UCFPOS_CONSTRAINT_CATEGORY) {
if (cfpos.getCategory() != UFIELD_CATEGORY_NUMBER) {
return false;
}
} else if (cfpos.getConstraintType() == UCFPOS_CONSTRAINT_FIELD) {
isSearchingForField = true;
}
// Signal the end of the string using a field that doesn't exist and that is
// different from UNUM_FIELD_COUNT, which is used for "null number field".
static constexpr Field kEndField = 0xff;
bool NumberStringBuilder::nextPosition(ConstrainedFieldPosition& cfpos, Field numericField, UErrorCode& /*status*/) const {
auto numericCAF = NumFieldUtils::expand(numericField);
int32_t fieldStart = -1;
int32_t currField = UNUM_FIELD_COUNT;
Field currField = UNUM_FIELD_COUNT;
for (int32_t i = fZero + cfpos.getLimit(); i <= fZero + fLength; i++) {
Field _field = (i < fZero + fLength) ? getFieldPtr()[i] : UNUM_FIELD_COUNT;
Field _field = (i < fZero + fLength) ? getFieldPtr()[i] : kEndField;
// Case 1: currently scanning a field.
if (currField != UNUM_FIELD_COUNT) {
if (currField != _field) {
@ -514,15 +511,17 @@ bool NumberStringBuilder::nextPosition(ConstrainedFieldPosition& cfpos, UErrorCo
if (currField != UNUM_GROUPING_SEPARATOR_FIELD) {
start = trimFront(start);
}
cfpos.setState(UFIELD_CATEGORY_NUMBER, currField, start, end);
auto caf = NumFieldUtils::expand(currField);
cfpos.setState(caf.category, caf.field, start, end);
return true;
}
continue;
}
// Special case: coalesce the INTEGER if we are pointing at the end of the INTEGER.
if ((!isSearchingForField || cfpos.getField() == UNUM_INTEGER_FIELD)
if (cfpos.matchesField(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)
&& i > fZero
&& i - fZero > cfpos.getLimit() // don't return the same field twice in a row
// don't return the same field twice in a row:
&& i - fZero > cfpos.getLimit()
&& isIntOrGroup(getFieldPtr()[i - 1])
&& !isIntOrGroup(_field)) {
int j = i - 1;
@ -530,16 +529,32 @@ bool NumberStringBuilder::nextPosition(ConstrainedFieldPosition& cfpos, UErrorCo
cfpos.setState(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, j - fZero + 1, i - fZero);
return true;
}
// Special case: coalesce NUMERIC if we are pointing at the end of the NUMERIC.
if (numericField != 0
&& cfpos.matchesField(numericCAF.category, numericCAF.field)
&& i > fZero
// don't return the same field twice in a row:
&& (i - fZero > cfpos.getLimit()
|| cfpos.getCategory() != numericCAF.category
|| cfpos.getField() != numericCAF.field)
&& isNumericField(getFieldPtr()[i - 1])
&& !isNumericField(_field)) {
int j = i - 1;
for (; j >= fZero && isNumericField(getFieldPtr()[j]); j--) {}
cfpos.setState(numericCAF.category, numericCAF.field, j - fZero + 1, i - fZero);
return true;
}
// Special case: skip over INTEGER; will be coalesced later.
if (_field == UNUM_INTEGER_FIELD) {
_field = UNUM_FIELD_COUNT;
}
// Case 2: no field starting at this position.
if (_field == UNUM_FIELD_COUNT) {
if (_field == UNUM_FIELD_COUNT || _field == kEndField) {
continue;
}
// Case 3: check for field starting at this position
if (!isSearchingForField || cfpos.getField() == _field) {
auto caf = NumFieldUtils::expand(_field);
if (cfpos.matchesField(caf.category, caf.field)) {
fieldStart = i - fZero;
currField = _field;
}
@ -560,7 +575,11 @@ bool NumberStringBuilder::containsField(Field field) const {
bool NumberStringBuilder::isIntOrGroup(Field field) {
return field == UNUM_INTEGER_FIELD
|| field ==UNUM_GROUPING_SEPARATOR_FIELD;
|| field == UNUM_GROUPING_SEPARATOR_FIELD;
}
bool NumberStringBuilder::isNumericField(Field field) {
return NumFieldUtils::isNumericField(field);
}
int32_t NumberStringBuilder::trimBack(int32_t limit) const {

View file

@ -108,7 +108,7 @@ class U_I18N_API NumberStringBuilder : public UMemory {
void getAllFieldPositions(FieldPositionIteratorHandler& fpih, UErrorCode& status) const;
bool nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const;
bool nextPosition(ConstrainedFieldPosition& cfpos, Field numericField, UErrorCode& status) const;
bool containsField(Field field) const;
@ -147,6 +147,8 @@ class U_I18N_API NumberStringBuilder : public UMemory {
static bool isIntOrGroup(Field field);
static bool isNumericField(Field field);
int32_t trimBack(int32_t limit) const;
int32_t trimFront(int32_t start) const;

View file

@ -23,7 +23,11 @@ namespace impl {
// Typedef several enums for brevity and for easier comparison to Java.
typedef UNumberFormatFields Field;
// Convention: bottom 4 bits for field, top 4 bits for field category.
// Field category 0 implies the number category so that the number field
// literals can be directly passed as a Field type.
// See the helper functions in "NumFieldUtils" in number_utils.h
typedef uint8_t Field;
typedef UNumberFormatRoundingMode RoundingMode;
@ -346,6 +350,7 @@ class U_I18N_API NullableValue {
T fValue;
};
} // namespace impl
} // namespace number
U_NAMESPACE_END

View file

@ -32,6 +32,48 @@ enum CldrPatternStyle {
CLDR_PATTERN_STYLE_COUNT,
};
/**
* Helper functions for dealing with the Field typedef, which stores fields
* in a compressed format.
*/
class NumFieldUtils {
public:
struct CategoryFieldPair {
int32_t category;
int32_t field;
};
/** Compile-time function to construct a Field from a category and a field */
template <int32_t category, int32_t field>
static constexpr Field compress() {
static_assert(category != 0, "cannot use Undefined category in NumFieldUtils");
static_assert(category <= 0xf, "only 4 bits for category");
static_assert(field <= 0xf, "only 4 bits for field");
return static_cast<int8_t>((category << 4) | field);
}
/** Runtime inline function to unpack the category and field from the Field */
static inline CategoryFieldPair expand(Field field) {
if (field == UNUM_FIELD_COUNT) {
return {UFIELD_CATEGORY_UNDEFINED, 0};
}
CategoryFieldPair ret = {
(field >> 4),
(field & 0xf)
};
if (ret.category == 0) {
ret.category = UFIELD_CATEGORY_NUMBER;
}
return ret;
}
static inline bool isNumericField(Field field) {
int8_t category = field >> 4;
return category == 0 || category == UFIELD_CATEGORY_NUMBER;
}
};
// Namespace for naked functions
namespace utils {

View file

@ -435,7 +435,7 @@ UBool FormattedNumberRange::nextPosition(ConstrainedFieldPosition& cfpos, UError
return FALSE;
}
// NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
return fResults->string.nextPosition(cfpos, status) ? TRUE : FALSE;
return fResults->string.nextPosition(cfpos, 0, status) ? TRUE : FALSE;
}
UBool FormattedNumberRange::nextFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) const {

View file

@ -25,6 +25,8 @@
#include "standardplural.h"
#include "uassert.h"
#include "number_decimalquantity.h"
#include "number_utypes.h"
#include "number_stringbuilder.h"
U_NAMESPACE_BEGIN
@ -174,6 +176,39 @@ StandardPlural::Form QuantityFormatter::selectPlural(
return StandardPlural::orOtherFromString(pluralKeyword);
}
void QuantityFormatter::formatAndSelect(
double quantity,
const NumberFormat& fmt,
const PluralRules& rules,
number::impl::NumberStringBuilder& output,
StandardPlural::Form& pluralForm,
UErrorCode& status) {
UnicodeString pluralKeyword;
const DecimalFormat* df = dynamic_cast<const DecimalFormat*>(&fmt);
if (df != nullptr) {
number::impl::UFormattedNumberData fn;
fn.quantity.setToDouble(quantity);
df->toNumberFormatter().formatImpl(&fn, status);
if (U_FAILURE(status)) {
return;
}
output = std::move(fn.string);
pluralKeyword = rules.select(fn.quantity);
} else {
UnicodeString result;
fmt.format(quantity, result, status);
if (U_FAILURE(status)) {
return;
}
output.append(result, UNUM_FIELD_COUNT, status);
if (U_FAILURE(status)) {
return;
}
pluralKeyword = rules.select(quantity);
}
pluralForm = StandardPlural::orOtherFromString(pluralKeyword);
}
UnicodeString &QuantityFormatter::format(
const SimpleFormatter &pattern,
const UnicodeString &value,

View file

@ -27,6 +27,12 @@ class NumberFormat;
class Formattable;
class FieldPosition;
namespace number {
namespace impl {
class NumberStringBuilder;
}
}
/**
* A plural aware formatter that is good for expressing a single quantity and
* a unit.
@ -111,6 +117,7 @@ public:
/**
* Selects the standard plural form for the number/formatter/rules.
* TODO(13591): Remove this method.
*/
static StandardPlural::Form selectPlural(
const Formattable &number,
@ -120,6 +127,27 @@ public:
FieldPosition &pos,
UErrorCode &status);
/**
* Formats a quantity and selects its plural form. The output is appended
* to a NumberStringBuilder in order to retain field information.
*
* @param quantity The number to format.
* @param fmt The formatter to use to format the number.
* @param rules The rules to use to select the plural form of the
* formatted number.
* @param output Where to append the result of the format operation.
* @param pluralForm Output variable populated with the plural form of the
* formatted number.
* @param status Set if an error occurs.
*/
static void formatAndSelect(
double quantity,
const NumberFormat& fmt,
const PluralRules& rules,
number::impl::NumberStringBuilder& output,
StandardPlural::Form& pluralForm,
UErrorCode& status);
/**
* Formats the pattern with the value and adjusts the FieldPosition.
*/

View file

@ -15,6 +15,7 @@
#if !UCONFIG_NO_FORMATTING && !UCONFIG_NO_BREAK_ITERATION
#include <cmath>
#include <functional>
#include "unicode/dtfmtsym.h"
#include "unicode/ucasemap.h"
#include "unicode/ureldatefmt.h"
@ -41,6 +42,12 @@
#include "sharednumberformat.h"
#include "standardplural.h"
#include "unifiedcache.h"
#include "util.h"
#include "number_stringbuilder.h"
#include "number_utypes.h"
#include "number_modifiers.h"
#include "formattedval_impl.h"
#include "number_utils.h"
// Copied from uscript_props.cpp
@ -717,6 +724,26 @@ const RelativeDateTimeCacheData *LocaleCacheKey<RelativeDateTimeCacheData>::crea
return result.orphan();
}
static constexpr number::impl::Field kRDTNumericField
= number::impl::NumFieldUtils::compress<UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD>();
static constexpr number::impl::Field kRDTLiteralField
= number::impl::NumFieldUtils::compress<UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD>();
class FormattedRelativeDateTimeData : public FormattedValueNumberStringBuilderImpl {
public:
FormattedRelativeDateTimeData() : FormattedValueNumberStringBuilderImpl(kRDTNumericField) {}
virtual ~FormattedRelativeDateTimeData();
};
FormattedRelativeDateTimeData::~FormattedRelativeDateTimeData() = default;
UPRV_FORMATTED_VALUE_SUBCLASS_AUTO_IMPL(FormattedRelativeDateTime)
RelativeDateTimeFormatter::RelativeDateTimeFormatter(UErrorCode& status) :
fCache(nullptr),
fNumberFormat(nullptr),
@ -841,43 +868,142 @@ UDateRelativeDateTimeFormatterStyle RelativeDateTimeFormatter::getFormatStyle()
return fStyle;
}
UnicodeString& RelativeDateTimeFormatter::format(
double quantity, UDateDirection direction, UDateRelativeUnit unit,
UnicodeString& appendTo, UErrorCode& status) const {
// 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 appendTo;
return;
}
int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0;
FieldPosition pos(FieldPosition::DONT_CARE);
UnicodeString result;
UnicodeString formattedNumber;
StandardPlural::Form pluralIndex = QuantityFormatter::selectPlural(
quantity, **fNumberFormat, **fPluralRules, formattedNumber, pos,
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, pluralIndex);
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 appendTo;
return;
}
formatter->format(formattedNumber, result, status);
adjustForContext(result);
return appendTo.append(result);
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 {
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 appendTo;
return;
}
UDateDirection direction = UDAT_DIRECTION_NEXT;
if (std::signbit(offset)) { // needed to handle -0.0
@ -886,55 +1012,110 @@ UnicodeString& RelativeDateTimeFormatter::formatNumeric(
}
if (direction != UDAT_DIRECTION_LAST && direction != UDAT_DIRECTION_NEXT) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return appendTo;
return;
}
int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0;
FieldPosition pos(FieldPosition::DONT_CARE);
UnicodeString result;
UnicodeString formattedNumber;
StandardPlural::Form pluralIndex = QuantityFormatter::selectPlural(
offset, **fNumberFormat, **fPluralRules, formattedNumber, pos,
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, pluralIndex);
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 appendTo;
return;
}
formatter->format(formattedNumber, result, status);
adjustForContext(result);
return appendTo.append(result);
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 {
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 appendTo;
return;
}
if (unit == UDAT_ABSOLUTE_NOW && direction != UDAT_DIRECTION_PLAIN) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return appendTo;
return;
}
// Get string using fallback.
UnicodeString result;
result.fastCopyFrom(fCache->getAbsoluteUnitString(fStyle, unit, direction));
if (fOptBreakIterator != nullptr) {
adjustForContext(result);
}
return appendTo.append(result);
output.getStringRef().append(
fCache->getAbsoluteUnitString(fStyle, unit, direction),
kRDTLiteralField,
status);
}
UnicodeString& RelativeDateTimeFormatter::format(
double offset, URelativeDateTimeUnit unit,
UnicodeString& appendTo, UErrorCode& status) const {
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 appendTo;
return;
}
// TODO:
// The full implementation of this depends on CLDR data that is not yet available,
@ -981,20 +1162,13 @@ UnicodeString& RelativeDateTimeFormatter::format(
default: break;
}
if (direction != UDAT_DIRECTION_COUNT && absunit != UDAT_ABSOLUTE_UNIT_COUNT) {
const UnicodeString &unitFormatString =
fCache->getAbsoluteUnitString(fStyle, absunit, direction);
if (!unitFormatString.isEmpty()) {
if (fOptBreakIterator != nullptr) {
UnicodeString result(unitFormatString);
adjustForContext(result);
return appendTo.append(result);
} else {
return appendTo.append(unitFormatString);
}
formatAbsoluteImpl(direction, absunit, output, status);
if (output.getStringRef().length() != 0) {
return;
}
}
// otherwise fallback to formatNumeric
return formatNumeric(offset, unit, appendTo, status);
formatNumericImpl(offset, unit, output, status);
}
UnicodeString& RelativeDateTimeFormatter::combineDateAndTime(
@ -1004,10 +1178,10 @@ UnicodeString& RelativeDateTimeFormatter::combineDateAndTime(
timeString, relativeDateString, appendTo, status);
}
void RelativeDateTimeFormatter::adjustForContext(UnicodeString &str) const {
UnicodeString& RelativeDateTimeFormatter::adjustForContext(UnicodeString &str) const {
if (fOptBreakIterator == nullptr
|| str.length() == 0 || !u_islower(str.char32At(0))) {
return;
return str;
}
// Must guarantee that one thread at a time accesses the shared break
@ -1017,6 +1191,17 @@ void RelativeDateTimeFormatter::adjustForContext(UnicodeString &str) const {
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(
@ -1072,6 +1257,17 @@ U_NAMESPACE_END
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,
@ -1125,6 +1321,21 @@ ureldatefmt_formatNumeric( const URelativeDateTimeFormatter* reldatefmt,
return res.extract(result, resultCapacity, *status);
}
U_STABLE 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,
@ -1153,6 +1364,21 @@ ureldatefmt_format( const URelativeDateTimeFormatter* reldatefmt,
return res.extract(result, resultCapacity, *status);
}
U_DRAFT 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,

View file

@ -222,7 +222,7 @@ class U_I18N_API ConstrainedFieldPosition : public UMemory {
int32_t limit);
/** @internal */
UBool matchesField(UFieldCategory category, int32_t field);
UBool matchesField(int32_t category, int32_t field);
private:
int64_t fContext = 0LL;

View file

@ -19,6 +19,7 @@
#include "unicode/udisplaycontext.h"
#include "unicode/ureldatefmt.h"
#include "unicode/locid.h"
#include "unicode/formattedvalue.h"
/**
* \file
@ -245,6 +246,70 @@ class SharedPluralRules;
class SharedBreakIterator;
class NumberFormat;
class UnicodeString;
class FormattedRelativeDateTimeData;
/**
* An immutable class containing the result of a relative datetime formatting operation.
*
* Not intended for public subclassing.
*
* @draft ICU 64
*/
class U_I18N_API FormattedRelativeDateTime : public UMemory, public FormattedValue {
public:
/**
* Default constructor; makes an empty FormattedRelativeDateTime.
* @draft ICU 64
*/
FormattedRelativeDateTime() : fData(nullptr), fErrorCode(U_INVALID_STATE_ERROR) {};
/**
* Move constructor: Leaves the source FormattedRelativeDateTime in an undefined state.
* @draft ICU 64
*/
FormattedRelativeDateTime(FormattedRelativeDateTime&& src) U_NOEXCEPT;
/**
* Destruct an instance of FormattedRelativeDateTime.
* @draft ICU 64
*/
virtual ~FormattedRelativeDateTime() U_OVERRIDE;
/** Copying not supported; use move constructor instead. */
FormattedRelativeDateTime(const FormattedRelativeDateTime&) = delete;
/** Copying not supported; use move assignment instead. */
FormattedRelativeDateTime& operator=(const FormattedRelativeDateTime&) = delete;
/**
* Move assignment: Leaves the source FormattedRelativeDateTime in an undefined state.
* @draft ICU 64
*/
FormattedRelativeDateTime& operator=(FormattedRelativeDateTime&& src) U_NOEXCEPT;
/** @copydoc FormattedValue::toString() */
UnicodeString toString(UErrorCode& status) const U_OVERRIDE;
/** @copydoc FormattedValue::toTempString() */
UnicodeString toTempString(UErrorCode& status) const U_OVERRIDE;
/** @copydoc FormattedValue::appendTo() */
Appendable &appendTo(Appendable& appendable, UErrorCode& status) const U_OVERRIDE;
/** @copydoc FormattedValue::nextPosition() */
UBool nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const U_OVERRIDE;
private:
FormattedRelativeDateTimeData *fData;
UErrorCode fErrorCode;
explicit FormattedRelativeDateTime(FormattedRelativeDateTimeData *results)
: fData(results), fErrorCode(U_ZERO_ERROR) {};
explicit FormattedRelativeDateTime(UErrorCode errorCode)
: fData(nullptr), fErrorCode(errorCode) {};
friend class RelativeDateTimeFormatter;
};
/**
* Formats simple relative dates. There are two types of relative dates that
@ -386,6 +451,10 @@ public:
/**
* Formats a relative date with a quantity such as "in 5 days" or
* "3 months ago"
*
* This method returns a String. To get more information about the
* formatting result, use formatToValue().
*
* @param quantity The numerical amount e.g 5. This value is formatted
* according to this object's NumberFormat object.
* @param direction NEXT means a future relative date; LAST means a past
@ -405,8 +474,35 @@ public:
UnicodeString& appendTo,
UErrorCode& status) const;
/**
* Formats a relative date with a quantity such as "in 5 days" or
* "3 months ago"
*
* This method returns a FormattedRelativeDateTime, which exposes more
* information than the String returned by format().
*
* @param quantity The numerical amount e.g 5. This value is formatted
* according to this object's NumberFormat object.
* @param direction NEXT means a future relative date; LAST means a past
* relative date. If direction is anything else, this method sets
* status to U_ILLEGAL_ARGUMENT_ERROR.
* @param unit the unit e.g day? month? year?
* @param status ICU error code returned here.
* @return The formatted relative datetime
* @draft ICU 64
*/
FormattedRelativeDateTime formatToValue(
double quantity,
UDateDirection direction,
UDateRelativeUnit unit,
UErrorCode& status) const;
/**
* Formats a relative date without a quantity.
*
* This method returns a String. To get more information about the
* formatting result, use formatToValue().
*
* @param direction NEXT, LAST, THIS, etc.
* @param unit e.g SATURDAY, DAY, MONTH
* @param appendTo The string to which the formatted result will be
@ -423,10 +519,33 @@ public:
UnicodeString& appendTo,
UErrorCode& status) const;
/**
* Formats a relative date without a quantity.
*
* This method returns a FormattedRelativeDateTime, which exposes more
* information than the String returned by format().
*
* If the string is not available in the requested locale, the return
* value will be empty (calling toString will give an empty string).
*
* @param direction NEXT, LAST, THIS, etc.
* @param unit e.g SATURDAY, DAY, MONTH
* @param status ICU error code returned here.
* @return The formatted relative datetime
* @draft ICU 64
*/
FormattedRelativeDateTime formatToValue(
UDateDirection direction,
UDateAbsoluteUnit unit,
UErrorCode& status) const;
/**
* Format a combination of URelativeDateTimeUnit and numeric offset
* using a numeric style, e.g. "1 week ago", "in 1 week",
* "5 weeks ago", "in 5 weeks".
*
* This method returns a String. To get more information about the
* formatting result, use formatNumericToValue().
*
* @param offset The signed offset for the specified unit. This
* will be formatted according to this object's
@ -446,6 +565,29 @@ public:
UnicodeString& appendTo,
UErrorCode& status) const;
/**
* Format a combination of URelativeDateTimeUnit and numeric offset
* using a numeric style, e.g. "1 week ago", "in 1 week",
* "5 weeks ago", "in 5 weeks".
*
* This method returns a FormattedRelativeDateTime, which exposes more
* information than the String returned by formatNumeric().
*
* @param offset The signed offset for the specified unit. This
* will be formatted according to this object's
* NumberFormat object.
* @param unit The unit to use when formatting the relative
* date, e.g. UDAT_REL_UNIT_WEEK,
* UDAT_REL_UNIT_FRIDAY.
* @param status ICU error code returned here.
* @return The formatted relative datetime
* @draft ICU 64
*/
FormattedRelativeDateTime formatNumericToValue(
double offset,
URelativeDateTimeUnit unit,
UErrorCode& status) const;
/**
* Format a combination of URelativeDateTimeUnit and numeric offset
* using a text style if possible, e.g. "last week", "this week",
@ -453,6 +595,9 @@ public:
* style if no appropriate text term is available for the specified
* offset in the object's locale.
*
* This method returns a String. To get more information about the
* formatting result, use formatToValue().
*
* @param offset The signed offset for the specified unit.
* @param unit The unit to use when formatting the relative
* date, e.g. UDAT_REL_UNIT_WEEK,
@ -469,6 +614,29 @@ public:
UnicodeString& appendTo,
UErrorCode& status) const;
/**
* Format a combination of URelativeDateTimeUnit and numeric offset
* using a text style if possible, e.g. "last week", "this week",
* "next week", "yesterday", "tomorrow". Falls back to numeric
* style if no appropriate text term is available for the specified
* offset in the object's locale.
*
* This method returns a FormattedRelativeDateTime, which exposes more
* information than the String returned by format().
*
* @param offset The signed offset for the specified unit.
* @param unit The unit to use when formatting the relative
* date, e.g. UDAT_REL_UNIT_WEEK,
* UDAT_REL_UNIT_FRIDAY.
* @param status ICU error code returned here.
* @return The formatted relative datetime
* @draft ICU 64
*/
FormattedRelativeDateTime formatToValue(
double offset,
URelativeDateTimeUnit unit,
UErrorCode& status) const;
/**
* Combines a relative date string and a time string in this object's
* locale. This is done with the same date-time separator used for the
@ -520,7 +688,43 @@ private:
NumberFormat *nfToAdopt,
BreakIterator *brkIter,
UErrorCode &status);
void adjustForContext(UnicodeString &) const;
UnicodeString& adjustForContext(UnicodeString &) const;
UBool checkNoAdjustForContext(UErrorCode& status) const;
template<typename F, typename... Args>
UnicodeString& doFormat(
F callback,
UnicodeString& appendTo,
UErrorCode& status,
Args... args) const;
template<typename F, typename... Args>
FormattedRelativeDateTime doFormatToValue(
F callback,
UErrorCode& status,
Args... args) const;
void formatImpl(
double quantity,
UDateDirection direction,
UDateRelativeUnit unit,
FormattedRelativeDateTimeData& output,
UErrorCode& status) const;
void formatAbsoluteImpl(
UDateDirection direction,
UDateAbsoluteUnit unit,
FormattedRelativeDateTimeData& output,
UErrorCode& status) const;
void formatNumericImpl(
double offset,
URelativeDateTimeUnit unit,
FormattedRelativeDateTimeData& output,
UErrorCode& status) const;
void formatRelativeImpl(
double offset,
URelativeDateTimeUnit unit,
FormattedRelativeDateTimeData& output,
UErrorCode& status) const;
};
U_NAMESPACE_END

View file

@ -60,6 +60,13 @@ typedef enum UFieldCategory {
*/
UFIELD_CATEGORY_LIST,
/**
* For fields in URelativeDateTimeFormatterField (ureldatefmt.h), from ICU 64.
*
* @draft ICU 64
*/
UFIELD_CATEGORY_RELATIVE_DATETIME,
#ifndef U_HIDE_INTERNAL_API
/** @internal */
UFIELD_CATEGORY_COUNT

View file

@ -551,15 +551,17 @@ unumf_formatDecimal(const UNumberFormatter* uformatter, const char* value, int32
/**
* Returns a representation of a UFormattedNumber as a UFormattedValue, which can be
* subsequently passed to any API requiring that type.
* Returns a representation of a UFormattedNumber as a UFormattedValue,
* which can be subsequently passed to any API requiring that type.
*
* The returned object is owned by the UFormattedNumber and is valid only as long as the
* UFormattedNumber is present and unchanged in memory.
* The returned object is owned by the UFormattedNumber and is valid
* only as long as the UFormattedNumber is present and unchanged in memory.
*
* @param uresult The object containing the formatted number.
* You can think of this method as a cast between types.
*
* @param uresult The object containing the formatted string.
* @param ec Set if an error occurs.
* @return A representation of the given UFormattedNumber as a UFormattedValue.
* @return A UFormattedValue owned by the input object.
* @draft ICU 64
*/
U_DRAFT const UFormattedValue* U_EXPORT2

View file

@ -17,6 +17,7 @@
#include "unicode/unum.h"
#include "unicode/udisplaycontext.h"
#include "unicode/localpointer.h"
#include "unicode/uformattedvalue.h"
/**
* \file
@ -174,6 +175,27 @@ typedef enum URelativeDateTimeUnit {
#endif /* U_HIDE_DEPRECATED_API */
} URelativeDateTimeUnit;
#ifndef U_HIDE_DRAFT_API
/**
* FieldPosition and UFieldPosition selectors for format fields
* defined by RelativeDateTimeFormatter.
* @draft ICU 64
*/
typedef enum URelativeDateTimeFormatterField {
/**
* Represents a literal text string, like "tomorrow" or "days ago".
* @draft ICU 64
*/
UDAT_REL_LITERAL_FIELD,
/**
* Represents a number quantity, like "3" in "3 days ago".
* @draft ICU 64
*/
UDAT_REL_NUMERIC_FIELD,
} URelativeDateTimeFormatterField;
#endif // U_HIDE_DRAFT_API
/**
* Opaque URelativeDateTimeFormatter object for use in C programs.
* @stable ICU 57
@ -230,6 +252,53 @@ ureldatefmt_open( const char* locale,
U_STABLE void U_EXPORT2
ureldatefmt_close(URelativeDateTimeFormatter *reldatefmt);
struct UFormattedRelativeDateTime;
/**
* Opaque struct to contain the results of a URelativeDateTimeFormatter operation.
* @draft ICU 64
*/
typedef struct UFormattedRelativeDateTime UFormattedRelativeDateTime;
/**
* Creates an object to hold the result of a URelativeDateTimeFormatter
* operation. The object can be used repeatedly; it is cleared whenever
* passed to a format function.
*
* @param ec Set if an error occurs.
* @return A pointer needing ownership.
* @draft ICU 64
*/
U_DRAFT UFormattedRelativeDateTime* U_EXPORT2
ureldatefmt_openResult(UErrorCode* ec);
/**
* Returns a representation of a UFormattedRelativeDateTime as a UFormattedValue,
* which can be subsequently passed to any API requiring that type.
*
* The returned object is owned by the UFormattedRelativeDateTime and is valid
* only as long as the UFormattedRelativeDateTime is present and unchanged in memory.
*
* You can think of this method as a cast between types.
*
* @param ufrdt The object containing the formatted string.
* @param ec Set if an error occurs.
* @return A UFormattedValue owned by the input object.
* @draft ICU 64
*/
U_DRAFT const UFormattedValue* U_EXPORT2
ureldatefmt_resultAsValue(const UFormattedRelativeDateTime* ufrdt, UErrorCode* ec);
/**
* Releases the UFormattedRelativeDateTime created by ureldatefmt_openResult.
*
* @param ufrdt The object to release.
* @draft ICU 64
*/
U_DRAFT void U_EXPORT2
ureldatefmt_closeResult(UFormattedRelativeDateTime* ufrdt);
#if U_SHOW_CPLUSPLUS_API
U_NAMESPACE_BEGIN
@ -245,6 +314,17 @@ U_NAMESPACE_BEGIN
*/
U_DEFINE_LOCAL_OPEN_POINTER(LocalURelativeDateTimeFormatterPointer, URelativeDateTimeFormatter, ureldatefmt_close);
/**
* \class LocalUFormattedRelativeDateTimePointer
* "Smart pointer" class, closes a UFormattedRelativeDateTime via ureldatefmt_closeResult().
* For most methods see the LocalPointerBase base class.
*
* @see LocalPointerBase
* @see LocalPointer
* @draft ICU 64
*/
U_DEFINE_LOCAL_OPEN_POINTER(LocalUFormattedRelativeDateTimePointer, UFormattedRelativeDateTime, ureldatefmt_closeResult);
U_NAMESPACE_END
#endif
@ -285,6 +365,37 @@ ureldatefmt_formatNumeric( const URelativeDateTimeFormatter* reldatefmt,
int32_t resultCapacity,
UErrorCode* status);
/**
* Format a combination of URelativeDateTimeUnit and numeric
* offset using a numeric style, e.g. "1 week ago", "in 1 week",
* "5 weeks ago", "in 5 weeks".
*
* @param reldatefmt
* The URelativeDateTimeFormatter object specifying the
* format conventions.
* @param offset
* The signed offset for the specified unit. This will
* be formatted according to this object's UNumberFormat
* object.
* @param unit
* The unit to use when formatting the relative
* date, e.g. UDAT_REL_UNIT_WEEK, UDAT_REL_UNIT_FRIDAY.
* @param result
* A pointer to a UFormattedRelativeDateTime to populate.
* @param status
* A pointer to a UErrorCode to receive any errors. In
* case of error status, the contents of result are
* undefined.
* @draft ICU 64
*/
U_DRAFT void U_EXPORT2
ureldatefmt_formatNumericToResult(
const URelativeDateTimeFormatter* reldatefmt,
double offset,
URelativeDateTimeUnit unit,
UFormattedRelativeDateTime* result,
UErrorCode* status);
/**
* Format a combination of URelativeDateTimeUnit and numeric offset
* using a text style if possible, e.g. "last week", "this week",
@ -321,6 +432,40 @@ ureldatefmt_format( const URelativeDateTimeFormatter* reldatefmt,
int32_t resultCapacity,
UErrorCode* status);
/**
* Format a combination of URelativeDateTimeUnit and numeric offset
* using a text style if possible, e.g. "last week", "this week",
* "next week", "yesterday", "tomorrow". Falls back to numeric
* style if no appropriate text term is available for the specified
* offset in the object's locale.
*
* This method populates a UFormattedRelativeDateTime, which exposes more
* information than the string populated by format().
*
* @param reldatefmt
* The URelativeDateTimeFormatter object specifying the
* format conventions.
* @param offset
* The signed offset for the specified unit.
* @param unit
* The unit to use when formatting the relative
* date, e.g. UDAT_REL_UNIT_WEEK, UDAT_REL_UNIT_FRIDAY.
* @param result
* A pointer to a UFormattedRelativeDateTime to populate.
* @param status
* A pointer to a UErrorCode to receive any errors. In
* case of error status, the contents of result are
* undefined.
* @draft ICU 64
*/
U_DRAFT void U_EXPORT2
ureldatefmt_formatToResult(
const URelativeDateTimeFormatter* reldatefmt,
double offset,
URelativeDateTimeUnit unit,
UFormattedRelativeDateTime* result,
UErrorCode* status);
/**
* Combines a relative date string and a time string in this object's
* locale. This is done with the same date-time separator used for the

View file

@ -16,9 +16,12 @@
#include "unicode/ustring.h"
#include "cintltst.h"
#include "cmemory.h"
#include "cformtst.h"
static void TestRelDateFmt(void);
static void TestNumericField(void);
static void TestCombineDateTime(void);
static void TestFields(void);
void addRelativeDateFormatTest(TestNode** root);
@ -27,12 +30,20 @@ void addRelativeDateFormatTest(TestNode** root);
void addRelativeDateFormatTest(TestNode** root)
{
TESTCASE(TestRelDateFmt);
TESTCASE(TestNumericField);
TESTCASE(TestCombineDateTime);
TESTCASE(TestFields);
}
static const double offsets[] = { -5.0, -2.2, -2.0, -1.0, -0.7, -0.0, 0.0, 0.7, 1.0, 2.0, 5.0 };
enum { kNumOffsets = UPRV_LENGTHOF(offsets) };
typedef struct {
int32_t field;
int32_t beginPos;
int32_t endPos;
} FieldsDat;
static const char* en_decDef_long_midSent_sec[kNumOffsets*2] = {
/* text numeric */
"5 seconds ago", "5 seconds ago", /* -5 */
@ -48,6 +59,21 @@ static const char* en_decDef_long_midSent_sec[kNumOffsets*2] = {
"in 5 seconds", "in 5 seconds" /* 5 */
};
static const FieldsDat en_attrDef_long_midSent_sec[kNumOffsets*2] = {
/* text numeric text numeric */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "5 seconds ago", "5 seconds ago", -5 */
{UDAT_REL_NUMERIC_FIELD, 0, 3}, {UDAT_REL_NUMERIC_FIELD, 0, 3}, /* "2.2 seconds ago", "2.2 seconds ago", -2.2 */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "2 seconds ago", "2 seconds ago", -2 */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "1 second ago", "1 second ago", -1 */
{UDAT_REL_NUMERIC_FIELD, 0, 3}, {UDAT_REL_NUMERIC_FIELD, 0, 3}, /* "0.7 seconds ago", "0.7 seconds ago", -0.7 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "now", "0 seconds ago", -0 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "now", "in 0 seconds", 0 */
{UDAT_REL_NUMERIC_FIELD, 3, 6}, {UDAT_REL_NUMERIC_FIELD, 3, 6}, /* "in 0.7 seconds", "in 0.7 seconds", 0.7 */
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 1 second", "in 1 second", 1 */
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 2 seconds", "in 2 seconds", 2 */
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 5 seconds", "in 5 seconds" 5 */
};
static const char* en_decDef_long_midSent_week[kNumOffsets*2] = {
/* text numeric */
"5 weeks ago", "5 weeks ago", /* -5 */
@ -63,6 +89,21 @@ static const char* en_decDef_long_midSent_week[kNumOffsets*2] = {
"in 5 weeks", "in 5 weeks" /* 5 */
};
static const FieldsDat en_attrDef_long_midSent_week[kNumOffsets*2] = {
/* text numeric text numeric */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "5 weeks ago", "5 weeks ago", -5 */
{UDAT_REL_NUMERIC_FIELD, 0, 3}, {UDAT_REL_NUMERIC_FIELD, 0, 3}, /* "2.2 weeks ago", "2.2 weeks ago", -2.2 */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "2 weeks ago", "2 weeks ago", -2 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "last week", "1 week ago", -1 */
{UDAT_REL_NUMERIC_FIELD, 0, 3}, {UDAT_REL_NUMERIC_FIELD, 0, 3}, /* "0.7 weeks ago", "0.7 weeks ago", -0.7 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "this week", "0 weeks ago", -0 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "this week", "in 0 weeks", 0 */
{UDAT_REL_NUMERIC_FIELD, 3, 6}, {UDAT_REL_NUMERIC_FIELD, 3, 6}, /* "in 0.7 weeks", "in 0.7 weeks", 0.7 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "next week", "in 1 week", 1 */
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 2 weeks", "in 2 weeks", 2 */
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 5 weeks", "in 5 weeks" 5 */
};
static const char* en_dec0_long_midSent_week[kNumOffsets*2] = {
/* text numeric */
"5 weeks ago", "5 weeks ago", /* -5 */
@ -78,6 +119,21 @@ static const char* en_dec0_long_midSent_week[kNumOffsets*2] = {
"in 5 weeks", "in 5 weeks" /* 5 */
};
static const FieldsDat en_attr0_long_midSent_week[kNumOffsets*2] = {
/* text numeric text numeric */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "5 weeks ago", "5 weeks ago", -5 */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "2 weeks ago", "2 weeks ago", -2.2 */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "2 weeks ago", "2 weeks ago", -2 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "last week", "1 week ago", -1 */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "0 weeks ago", "0 weeks ago", -0.7 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "this week", "0 weeks ago", -0 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "this week", "in 0 weeks", 0 */
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 0 weeks", "in 0 weeks", 0.7 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "next week", "in 1 week", 1 */
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 2 weeks", "in 2 weeks", 2 */
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 5 weeks", "in 5 weeks" 5 */
};
static const char* en_decDef_short_midSent_week[kNumOffsets*2] = {
/* text numeric */
"5 wk. ago", "5 wk. ago", /* -5 */
@ -93,6 +149,21 @@ static const char* en_decDef_short_midSent_week[kNumOffsets*2] = {
"in 5 wk.", "in 5 wk." /* 5 */
};
static const FieldsDat en_attrDef_short_midSent_week[kNumOffsets*2] = {
/* text numeric text numeric */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "5 wk. ago", "5 wk. ago", -5 */
{UDAT_REL_NUMERIC_FIELD, 0, 3}, {UDAT_REL_NUMERIC_FIELD, 0, 3}, /* "2.2 wk. ago", "2.2 wk. ago", -2.2 */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "2 wk. ago", "2 wk. ago", -2 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "last wk.", "1 wk. ago", -1 */
{UDAT_REL_NUMERIC_FIELD, 0, 3}, {UDAT_REL_NUMERIC_FIELD, 0, 3}, /* "0.7 wk. ago", "0.7 wk. ago", -0.7 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "this wk.", "0 wk. ago", -0 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "this wk.", "in 0 wk.", 0 */
{UDAT_REL_NUMERIC_FIELD, 3, 6}, {UDAT_REL_NUMERIC_FIELD, 3, 6}, /* "in 0.7 wk.", "in 0.7 wk.", 0.7 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "next wk.", "in 1 wk.", 1 */
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 2 wk.", "in 2 wk.", 2 */
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 5 wk.", "in 5 wk." 5 */
};
static const char* en_decDef_long_midSent_min[kNumOffsets*2] = {
/* text numeric */
"5 minutes ago", "5 minutes ago", /* -5 */
@ -108,6 +179,21 @@ static const char* en_decDef_long_midSent_min[kNumOffsets*2] = {
"in 5 minutes", "in 5 minutes" /* 5 */
};
static const FieldsDat en_attrDef_long_midSent_min[kNumOffsets*2] = {
/* text numeric text numeric */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "5 minutes ago", "5 minutes ago", -5 */
{UDAT_REL_NUMERIC_FIELD, 0, 3}, {UDAT_REL_NUMERIC_FIELD, 0, 3}, /* "2.2 minutes ago", "2.2 minutes ago", -2.2 */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "2 minutes ago", "2 minutes ago", -2 */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "1 minute ago", "1 minute ago", -1 */
{UDAT_REL_NUMERIC_FIELD, 0, 3}, {UDAT_REL_NUMERIC_FIELD, 0, 3}, /* "0.7 minutes ago", "0.7 minutes ago", -0.7 */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "0 minutes ago", "0 minutes ago", -0 */
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 0 minutes", "in 0 minutes", 0 */
{UDAT_REL_NUMERIC_FIELD, 3, 6}, {UDAT_REL_NUMERIC_FIELD, 3, 6}, /* "in 0.7 minutes", "in 0.7 minutes", 0.7 */
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 1 minute", "in 1 minute", 1 */
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 2 minutes", "in 2 minutes", 2 */
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 5 minutes", "in 5 minutes" 5 */
};
static const char* en_dec0_long_midSent_tues[kNumOffsets*2] = {
/* text numeric */
"5 Tuesdays ago", "5 Tuesdays ago", /* -5 */
@ -123,6 +209,21 @@ static const char* en_dec0_long_midSent_tues[kNumOffsets*2] = {
"in 5 Tuesdays", "in 5 Tuesdays", /* 5 */
};
static const FieldsDat en_attr0_long_midSent_tues[kNumOffsets*2] = {
/* text numeric text numeric */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "5 Tuesdays ago", "5 Tuesdays ago", -5 */
{ -1, -1, -1}, { -1, -1, -1}, /* "" , "" , -2.2 */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "2 Tuesdays ago", "2 Tuesdays ago", -2 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "last Tuesday", "1 Tuesday ago", -1 */
{ -1, -1, -1}, { -1, -1, -1}, /* "" , "" , -0.7 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 0, 1}, /* "this Tuesday", "0 Tuesdays ago", -0 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "this Tuesday", "in 0 Tuesdays", 0 */
{ -1, -1, -1}, { -1, -1, -1}, /* "" , "" , 0.7 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "next Tuesday", "in 1 Tuesday", 1 */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 2 Tuesdays", "in 2 Tuesdays", 2 */
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 3, 4}, /* "in 5 Tuesdays", "in 5 Tuesdays", 5 */
};
static const char* fr_decDef_long_midSent_day[kNumOffsets*2] = {
/* text numeric */
"il y a 5 jours", "il y a 5 jours", /* -5 */
@ -138,6 +239,21 @@ static const char* fr_decDef_long_midSent_day[kNumOffsets*2] = {
"dans 5 jours", "dans 5 jours" /* 5 */
};
static const FieldsDat fr_attrDef_long_midSent_day[kNumOffsets*2] = {
/* text numeric text numeric */
{UDAT_REL_NUMERIC_FIELD, 7, 8}, {UDAT_REL_NUMERIC_FIELD, 7, 8}, /* "il y a 5 jours", "il y a 5 jours", -5 */
{UDAT_REL_NUMERIC_FIELD, 7, 10}, {UDAT_REL_NUMERIC_FIELD, 7, 10}, /* "il y a 2,2 jours", "il y a 2,2 jours", -2.2 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 7, 8}, /* "avant-hier", "il y a 2 jours", -2 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 7, 8}, /* "hier", "il y a 1 jour", -1 */
{UDAT_REL_NUMERIC_FIELD, 7, 10}, {UDAT_REL_NUMERIC_FIELD, 7, 10}, /* "il y a 0,7 jour", "il y a 0,7 jour", -0.7 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 7, 8}, /* "aujourd\\u2019hui", "il y a 0 jour", -0 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 5, 6}, /* "aujourd\\u2019hui", "dans 0 jour", 0 */
{UDAT_REL_NUMERIC_FIELD, 5, 8}, {UDAT_REL_NUMERIC_FIELD, 5, 8}, /* "dans 0,7 jour", "dans 0,7 jour", 0.7 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 5, 6}, /* "demain", "dans 1 jour", 1 */
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 5, 6}, /* "apr\\u00E8s-demain", "dans 2 jours", 2 */
{UDAT_REL_NUMERIC_FIELD, 5, 6}, {UDAT_REL_NUMERIC_FIELD, 5, 6}, /* "dans 5 jours", "dans 5 jours" 5 */
};
static const char* ak_decDef_long_stdAlon_sec[kNumOffsets*2] = { // falls back to root
/* text numeric */
"-5 s", "-5 s", /* -5 */
@ -153,6 +269,20 @@ static const char* ak_decDef_long_stdAlon_sec[kNumOffsets*2] = { // falls back t
"+5 s", "+5 s", /* 5 */
};
static const FieldsDat ak_attrDef_long_stdAlon_sec[kNumOffsets*2] = {
{UDAT_REL_NUMERIC_FIELD, 1, 2}, {UDAT_REL_NUMERIC_FIELD, 1, 2},
{UDAT_REL_NUMERIC_FIELD, 1, 4}, {UDAT_REL_NUMERIC_FIELD, 1, 4},
{UDAT_REL_NUMERIC_FIELD, 1, 2}, {UDAT_REL_NUMERIC_FIELD, 1, 2},
{UDAT_REL_NUMERIC_FIELD, 1, 2}, {UDAT_REL_NUMERIC_FIELD, 1, 2},
{UDAT_REL_NUMERIC_FIELD, 1, 4}, {UDAT_REL_NUMERIC_FIELD, 1, 4},
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 1, 2},
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 1, 2},
{UDAT_REL_NUMERIC_FIELD, 1, 4}, {UDAT_REL_NUMERIC_FIELD, 1, 4},
{UDAT_REL_NUMERIC_FIELD, 1, 2}, {UDAT_REL_NUMERIC_FIELD, 1, 2},
{UDAT_REL_NUMERIC_FIELD, 1, 2}, {UDAT_REL_NUMERIC_FIELD, 1, 2},
{UDAT_REL_NUMERIC_FIELD, 1, 2}, {UDAT_REL_NUMERIC_FIELD, 1, 2},
};
static const char* enIN_decDef_short_midSent_weds[kNumOffsets*2] = {
/* text numeric */
"5 Wed. ago", "5 Wed. ago", /* -5 */
@ -168,6 +298,20 @@ static const char* enIN_decDef_short_midSent_weds[kNumOffsets*2] = {
"in 5 Wed.", "in 5 Wed." /* 5 */
};
static const FieldsDat enIN_attrDef_short_midSent_weds[kNumOffsets*2] = {
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1},
{UDAT_REL_NUMERIC_FIELD, 0, 3}, {UDAT_REL_NUMERIC_FIELD, 0, 3},
{UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1},
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 0, 1},
{UDAT_REL_NUMERIC_FIELD, 0, 3}, {UDAT_REL_NUMERIC_FIELD, 0, 3},
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 0, 1},
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 3, 4},
{UDAT_REL_NUMERIC_FIELD, 3, 6}, {UDAT_REL_NUMERIC_FIELD, 3, 6},
{ -1, -1, -1}, {UDAT_REL_NUMERIC_FIELD, 3, 4},
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4},
{UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4},
};
typedef struct {
const char* locale;
int32_t decPlaces; /* fixed decimal places; -1 to use default num formatter */
@ -175,19 +319,29 @@ typedef struct {
UDisplayContext capContext;
URelativeDateTimeUnit unit;
const char ** expectedResults; /* for the various offsets */
const FieldsDat* expectedAttributes;
} RelDateTimeFormatTestItem;
static const RelDateTimeFormatTestItem fmtTestItems[] = {
{ "en", -1, UDAT_STYLE_LONG, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_SECOND, en_decDef_long_midSent_sec },
{ "en", -1, UDAT_STYLE_LONG, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEEK, en_decDef_long_midSent_week },
{ "en", 0, UDAT_STYLE_LONG, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEEK, en_dec0_long_midSent_week },
{ "en", -1, UDAT_STYLE_SHORT, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEEK, en_decDef_short_midSent_week },
{ "en", -1, UDAT_STYLE_LONG, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_MINUTE, en_decDef_long_midSent_min },
{ "en", -1, UDAT_STYLE_LONG, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_TUESDAY, en_dec0_long_midSent_tues },
{ "fr", -1, UDAT_STYLE_LONG, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_DAY, fr_decDef_long_midSent_day },
{ "ak", -1, UDAT_STYLE_LONG, UDISPCTX_CAPITALIZATION_FOR_STANDALONE, UDAT_REL_UNIT_SECOND, ak_decDef_long_stdAlon_sec },
{ "en_IN", -1, UDAT_STYLE_SHORT, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEDNESDAY, enIN_decDef_short_midSent_weds },
{ NULL, 0, (UDateRelativeDateTimeFormatterStyle)0, (UDisplayContext)0, (URelativeDateTimeUnit)0, NULL } /* terminator */
{ "en", -1, UDAT_STYLE_LONG, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_SECOND,
en_decDef_long_midSent_sec, en_attrDef_long_midSent_sec },
{ "en", -1, UDAT_STYLE_LONG, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEEK,
en_decDef_long_midSent_week, en_attrDef_long_midSent_week},
{ "en", 0, UDAT_STYLE_LONG, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEEK,
en_dec0_long_midSent_week, en_attr0_long_midSent_week},
{ "en", -1, UDAT_STYLE_SHORT, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEEK,
en_decDef_short_midSent_week, en_attrDef_short_midSent_week},
{ "en", -1, UDAT_STYLE_LONG, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_MINUTE,
en_decDef_long_midSent_min, en_attrDef_long_midSent_min},
{ "en", -1, UDAT_STYLE_LONG, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_TUESDAY,
en_dec0_long_midSent_tues, en_attr0_long_midSent_tues},
{ "fr", -1, UDAT_STYLE_LONG, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_DAY,
fr_decDef_long_midSent_day, fr_attrDef_long_midSent_day},
{ "ak", -1, UDAT_STYLE_LONG, UDISPCTX_CAPITALIZATION_FOR_STANDALONE, UDAT_REL_UNIT_SECOND,
ak_decDef_long_stdAlon_sec, ak_attrDef_long_stdAlon_sec},
{ "en_IN", -1, UDAT_STYLE_SHORT, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEDNESDAY,
enIN_decDef_short_midSent_weds, enIN_attrDef_short_midSent_weds},
{ NULL, 0, (UDateRelativeDateTimeFormatterStyle)0, (UDisplayContext)0, (URelativeDateTimeUnit)0, NULL, NULL } /* terminator */
};
enum { kUBufMax = 64, kBBufMax = 256 };
@ -273,6 +427,154 @@ static void TestRelDateFmt()
}
}
static void TestNumericField()
{
const RelDateTimeFormatTestItem *itemPtr;
log_verbose("\nTesting ureldatefmt_open(), ureldatefmt_formatForFields(), ureldatefmt_formatNumericForFields() with various parameters\n");
for (itemPtr = fmtTestItems; itemPtr->locale != NULL; itemPtr++) {
URelativeDateTimeFormatter *reldatefmt = NULL;
UNumberFormat* nfToAdopt = NULL;
UErrorCode status = U_ZERO_ERROR;
int32_t iOffset;
if (itemPtr->decPlaces >= 0) {
nfToAdopt = unum_open(UNUM_DECIMAL, NULL, 0, itemPtr->locale, NULL, &status);
if ( U_FAILURE(status) ) {
log_data_err("FAIL: unum_open(UNUM_DECIMAL, ...) for locale %s: %s\n", itemPtr->locale, myErrorName(status));
continue;
}
unum_setAttribute(nfToAdopt, UNUM_MIN_FRACTION_DIGITS, itemPtr->decPlaces);
unum_setAttribute(nfToAdopt, UNUM_MAX_FRACTION_DIGITS, itemPtr->decPlaces);
unum_setAttribute(nfToAdopt, UNUM_ROUNDING_MODE, UNUM_ROUND_DOWN);
}
reldatefmt = ureldatefmt_open(itemPtr->locale, nfToAdopt, itemPtr->width, itemPtr->capContext, &status);
if ( U_FAILURE(status) ) {
log_data_err("FAIL: ureldatefmt_open() for locale %s, decPlaces %d, width %d, capContext %d: %s\n",
itemPtr->locale, itemPtr->decPlaces, (int)itemPtr->width, (int)itemPtr->capContext,
myErrorName(status) );
continue;
}
for (iOffset = 0; iOffset < kNumOffsets; iOffset++) {
if (itemPtr->unit >= UDAT_REL_UNIT_SUNDAY && offsets[iOffset] != -1.0 && offsets[iOffset] != 0.0 && offsets[iOffset] != 1.0) {
continue; /* we do not currently have data for this */
}
/* Depend on the next one to verify the data */
status = U_ZERO_ERROR;
UFormattedRelativeDateTime* fv = ureldatefmt_openResult(&status);
if ( U_FAILURE(status) ) {
log_err("ureldatefmt_openResult fails, status %s\n", u_errorName(status));
continue;
}
ureldatefmt_formatToResult(reldatefmt, offsets[iOffset], itemPtr->unit, fv, &status);
if ( U_FAILURE(status) ) {
log_err("FAIL: ureldatefmt_formatForFields() for locale %s, decPlaces %d, width %d, capContext %d, offset %.2f, unit %d: %s\n",
itemPtr->locale, itemPtr->decPlaces, (int)itemPtr->width, (int)itemPtr->capContext,
offsets[iOffset], (int)itemPtr->unit, myErrorName(status) );
} else {
UChar ubufexp[kUBufMax];
int32_t ulenexp = u_unescape(itemPtr->expectedResults[iOffset*2], ubufexp, kUBufMax);
int32_t ulenget;
const UChar* ubufget = ufmtval_getString(ureldatefmt_resultAsValue(fv, &status), &ulenget, &status);
assertUEquals("String content", ubufexp, ubufget);
assertIntEquals("String length", ulenexp, ulenget);
FieldsDat expectedAttr = itemPtr->expectedAttributes[iOffset*2];
UConstrainedFieldPosition* cfpos = ucfpos_open(&status);
UBool foundNumeric = FALSE;
while (TRUE) {
foundNumeric = ufmtval_nextPosition(ureldatefmt_resultAsValue(fv, &status), cfpos, &status);
if (!foundNumeric) {
break;
}
if (ucfpos_getCategory(cfpos, &status) == UFIELD_CATEGORY_RELATIVE_DATETIME
&& ucfpos_getField(cfpos, &status) == UDAT_REL_NUMERIC_FIELD) {
break;
}
}
assertSuccess("Looking for numeric", &status);
int32_t beginPos, endPos;
ucfpos_getIndexes(cfpos, &beginPos, &endPos, &status);
if (expectedAttr.field == -1) {
if (foundNumeric) {
log_err("ureldatefmt_formatForFields as \"%s\"; expect no field, but got %d\n",
itemPtr->expectedResults[iOffset*2],
ucfpos_getField(cfpos, &status));
}
} else {
if (!foundNumeric ||
beginPos != expectedAttr.beginPos ||
endPos != expectedAttr.endPos) {
log_err("ureldatefmt_formatForFields as \"%s\"; expect field %d range %d-%d, get range %d-%d\n",
itemPtr->expectedResults[iOffset*2],
expectedAttr.field, expectedAttr.beginPos, expectedAttr.endPos,
beginPos, endPos);
}
}
ucfpos_close(cfpos);
}
if (itemPtr->unit >= UDAT_REL_UNIT_SUNDAY) {
ureldatefmt_closeResult(fv);
continue; /* we do not currently have numeric-style data for this */
}
/* Depend on the next one to verify the data */
status = U_ZERO_ERROR;
ureldatefmt_formatNumericToResult(reldatefmt, offsets[iOffset], itemPtr->unit, fv, &status);
if ( U_FAILURE(status) ) {
log_err("FAIL: ureldatefmt_formatNumericForFields() for locale %s, decPlaces %d, width %d, capContext %d, offset %.2f, unit %d: %s\n",
itemPtr->locale, itemPtr->decPlaces, (int)itemPtr->width, (int)itemPtr->capContext,
offsets[iOffset], (int)itemPtr->unit, myErrorName(status) );
} else {
UChar ubufexp[kUBufMax];
int32_t ulenexp = u_unescape(itemPtr->expectedResults[iOffset*2 + 1], ubufexp, kUBufMax);
int32_t ulenget;
const UChar* ubufget = ufmtval_getString(ureldatefmt_resultAsValue(fv, &status), &ulenget, &status);
assertUEquals("String content", ubufexp, ubufget);
assertIntEquals("String length", ulenexp, ulenget);
FieldsDat expectedAttr = itemPtr->expectedAttributes[iOffset*2 + 1];
UConstrainedFieldPosition* cfpos = ucfpos_open(&status);
UBool foundNumeric = FALSE;
while (TRUE) {
foundNumeric = ufmtval_nextPosition(ureldatefmt_resultAsValue(fv, &status), cfpos, &status);
if (!foundNumeric) {
break;
}
if (ucfpos_getCategory(cfpos, &status) == UFIELD_CATEGORY_RELATIVE_DATETIME
&& ucfpos_getField(cfpos, &status) == UDAT_REL_NUMERIC_FIELD) {
break;
}
}
assertSuccess("Looking for numeric", &status);
int32_t beginPos, endPos;
ucfpos_getIndexes(cfpos, &beginPos, &endPos, &status);
if (expectedAttr.field == -1) {
if (foundNumeric) {
log_err("ureldatefmt_formatForFields as \"%s\"; expect no field, but got %d rang %d-%d\n",
itemPtr->expectedResults[iOffset*2],
ucfpos_getField(cfpos, &status), beginPos, endPos);
}
} else {
if (!foundNumeric ||
(beginPos != expectedAttr.beginPos || endPos != expectedAttr.endPos)) {
log_err("ureldatefmt_formatForFields as \"%s\"; expect field %d range %d-%d, get field %d range %d-%d\n",
itemPtr->expectedResults[iOffset*2 + 1],
expectedAttr.field, expectedAttr.beginPos, expectedAttr.endPos,
ucfpos_getField(cfpos, &status), beginPos, endPos);
}
}
ucfpos_close(cfpos);
}
ureldatefmt_closeResult(fv);
}
ureldatefmt_close(reldatefmt);
}
}
typedef struct {
const char* locale;
UDateRelativeDateTimeFormatterStyle width;
@ -341,4 +643,54 @@ static void TestCombineDateTime()
}
}
static void TestFields() {
UErrorCode ec = U_ZERO_ERROR;
URelativeDateTimeFormatter* fmt = ureldatefmt_open(
"en-us",
NULL,
UDAT_STYLE_SHORT,
UDISPCTX_CAPITALIZATION_NONE,
&ec);
assertSuccess("Creating RelDTFmt", &ec);
UFormattedRelativeDateTime* frdt = ureldatefmt_openResult(&ec);
assertSuccess("Creating FmtVal", &ec);
ureldatefmt_formatNumericToResult(fmt, -50, UDAT_REL_UNIT_SATURDAY, frdt, &ec);
assertSuccess("formatNumeric", &ec);
{
const UFormattedValue* fv = ureldatefmt_resultAsValue(frdt, &ec);
assertSuccess("Should convert without error", &ec);
static const UFieldPositionWithCategory expectedFieldPositions[] = {
// category, field, begin index, end index
{UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 0, 2},
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD, 0, 2},
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 3, 11}};
checkMixedFormattedValue(
"FormattedRelativeDateTime as FormattedValue (numeric)",
fv,
u"50 Sat. ago",
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
ureldatefmt_formatToResult(fmt, -1, UDAT_REL_UNIT_WEEK, frdt, &ec);
assertSuccess("format", &ec);
{
const UFormattedValue* fv = ureldatefmt_resultAsValue(frdt, &ec);
assertSuccess("Should convert without error", &ec);
static const UFieldPositionWithCategory expectedFieldPositions[] = {
// category, field, begin index, end index
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 0, 8}};
checkMixedFormattedValue(
"FormattedRelativeDateTime as FormattedValue (relative)",
fv,
u"last wk.",
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
ureldatefmt_closeResult(frdt);
ureldatefmt_close(fmt);
}
#endif /* #if !UCONFIG_NO_FORMATTING && !UCONFIG_NO_BREAK_ITERATION */

View file

@ -1005,7 +1005,7 @@ group: formatting
# messageformat
choicfmt.o msgfmt.o plurfmt.o selfmt.o umsg.o
deps
decnumber formattable format units numberformatter numberparser
decnumber formattable format units numberformatter numberparser formatted_value_sbimpl
listformatter
dayperiodrules
collation collation_builder # for rbnf
@ -1051,6 +1051,11 @@ group: formatted_value_iterimpl
deps
formatted_value format uvector32
group: formatted_value_sbimpl
formattedval_sbimpl.o
deps
number_representation
group: format
format.o fphdlimp.o fpositer.o ufieldpositer.o
deps

View file

@ -184,8 +184,8 @@ void NumberStringBuilderTest::testFields() {
assertSuccess("Appending to sb", status);
assertEquals("Reference string copied twice", str.length() * 2, sb.length());
for (int32_t i = 0; i < str.length(); i++) {
assertEquals("Null field first", UNUM_FIELD_COUNT, sb.fieldAt(i));
assertEquals("Currency field second", UNUM_CURRENCY_FIELD, sb.fieldAt(i + str.length()));
assertEquals("Null field first", (Field) UNUM_FIELD_COUNT, sb.fieldAt(i));
assertEquals("Currency field second", (Field) UNUM_CURRENCY_FIELD, sb.fieldAt(i + str.length()));
}
// Very basic FieldPosition test. More robust tests happen in NumberFormatTest.
@ -200,7 +200,7 @@ void NumberStringBuilderTest::testFields() {
sb.insertCodePoint(2, 100, UNUM_INTEGER_FIELD, status);
assertSuccess("Inserting code point into sb", status);
assertEquals("New length", str.length() * 2 + 1, sb.length());
assertEquals("Integer field", UNUM_INTEGER_FIELD, sb.fieldAt(2));
assertEquals("Integer field", (Field) UNUM_INTEGER_FIELD, sb.fieldAt(2));
}
NumberStringBuilder old(sb);
@ -210,7 +210,7 @@ void NumberStringBuilderTest::testFields() {
int32_t numCurr = 0;
int32_t numInt = 0;
for (int32_t i = 0; i < sb.length(); i++) {
UNumberFormatFields field = sb.fieldAt(i);
Field field = sb.fieldAt(i);
assertEquals("Field should equal location in old", old.fieldAt(i % old.length()), field);
if (field == UNUM_FIELD_COUNT) {
numNull++;

View file

@ -22,7 +22,9 @@
#include "unicode/localpointer.h"
#include "unicode/numfmt.h"
#include "unicode/reldatefmt.h"
#include "unicode/rbnf.h"
#include "cmemory.h"
#include "itformat.h"
static const char *DirectionStr(UDateDirection direction);
static const char *RelativeUnitStr(UDateRelativeUnit unit);
@ -741,7 +743,7 @@ static WithQuantityExpectedRelativeDateTimeUnit kEnglishFormat[] = {
};
class RelativeDateTimeFormatterTest : public IntlTest {
class RelativeDateTimeFormatterTest : public IntlTestWithFieldPosition {
public:
RelativeDateTimeFormatterTest() {
}
@ -768,6 +770,9 @@ private:
void TestFormat();
void TestFormatNumeric();
void TestLocales();
void TestFields();
void TestRBNF();
void RunTest(
const Locale& locale,
const WithQuantityExpected* expectedResults,
@ -858,6 +863,8 @@ void RelativeDateTimeFormatterTest::runIndexedTest(
TESTCASE_AUTO(TestFormat);
TESTCASE_AUTO(TestFormatNumeric);
TESTCASE_AUTO(TestLocales);
TESTCASE_AUTO(TestFields);
TESTCASE_AUTO(TestRBNF);
TESTCASE_AUTO_END;
}
@ -1313,6 +1320,137 @@ void RelativeDateTimeFormatterTest::TestLocales() {
}
}
void RelativeDateTimeFormatterTest::TestFields() {
IcuTestErrorCode status(*this, "TestFields");
RelativeDateTimeFormatter fmt("en-US", status);
{
const char16_t* message = u"automatic absolute unit";
FormattedRelativeDateTime fv = fmt.formatToValue(1, UDAT_REL_UNIT_DAY, status);
const char16_t* expectedString = u"tomorrow";
static const UFieldPositionWithCategory expectedFieldPositions[] = {
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 0, 8}};
checkMixedFormattedValue(
message,
fv,
expectedString,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
{
const char16_t* message = u"automatic numeric unit";
FormattedRelativeDateTime fv = fmt.formatToValue(3, UDAT_REL_UNIT_DAY, status);
const char16_t* expectedString = u"in 3 days";
static const UFieldPositionWithCategory expectedFieldPositions[] = {
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 0, 2},
{UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 3, 4},
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD, 3, 4},
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 5, 9}};
checkMixedFormattedValue(
message,
fv,
expectedString,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
{
const char16_t* message = u"manual absolute unit";
FormattedRelativeDateTime fv = fmt.formatToValue(UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_MONDAY, status);
const char16_t* expectedString = u"next Monday";
static const UFieldPositionWithCategory expectedFieldPositions[] = {
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 0, 11}};
checkMixedFormattedValue(
message,
fv,
expectedString,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
{
const char16_t* message = u"manual numeric unit";
FormattedRelativeDateTime fv = fmt.formatNumericToValue(1.5, UDAT_REL_UNIT_WEEK, status);
const char16_t* expectedString = u"in 1.5 weeks";
static const UFieldPositionWithCategory expectedFieldPositions[] = {
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 0, 2},
{UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 3, 4},
{UFIELD_CATEGORY_NUMBER, UNUM_DECIMAL_SEPARATOR_FIELD, 4, 5},
{UFIELD_CATEGORY_NUMBER, UNUM_FRACTION_FIELD, 5, 6},
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD, 3, 6},
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 7, 12}};
checkMixedFormattedValue(
message,
fv,
expectedString,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
{
const char16_t* message = u"manual numeric resolved unit";
FormattedRelativeDateTime fv = fmt.formatToValue(12, UDAT_DIRECTION_LAST, UDAT_RELATIVE_HOURS, status);
const char16_t* expectedString = u"12 hours ago";
static const UFieldPositionWithCategory expectedFieldPositions[] = {
{UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 0, 2},
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD, 0, 2},
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 3, 12}};
checkMixedFormattedValue(
message,
fv,
expectedString,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
// Test when the number field is at the end
fmt = RelativeDateTimeFormatter("sw", status);
{
const char16_t* message = u"numeric field at end";
FormattedRelativeDateTime fv = fmt.formatToValue(12, UDAT_REL_UNIT_HOUR, status);
const char16_t* expectedString = u"baada ya saa 12";
static const UFieldPositionWithCategory expectedFieldPositions[] = {
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 0, 12},
{UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 13, 15},
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD, 13, 15}};
checkMixedFormattedValue(
message,
fv,
expectedString,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
}
void RelativeDateTimeFormatterTest::TestRBNF() {
IcuTestErrorCode status(*this, "TestRBNF");
LocalPointer<RuleBasedNumberFormat> rbnf(new RuleBasedNumberFormat(URBNF_SPELLOUT, "en-us", status));
RelativeDateTimeFormatter fmt("en-us", rbnf.orphan(), status);
UnicodeString result;
assertEquals("format (direction)", "in five seconds",
fmt.format(5, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_SECONDS, result, status));
assertEquals("formatNumeric", "one week ago",
fmt.formatNumeric(-1, UDAT_REL_UNIT_WEEK, result.remove(), status));
assertEquals("format (absolute)", "yesterday",
fmt.format(UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_DAY, result.remove(), status));
assertEquals("format (relative)", "in forty-two months",
fmt.format(42, UDAT_REL_UNIT_MONTH, result.remove(), status));
{
const char16_t* message = u"formatToValue (relative)";
FormattedRelativeDateTime fv = fmt.formatToValue(-100, UDAT_REL_UNIT_YEAR, status);
const char16_t* expectedString = u"one hundred years ago";
static const UFieldPositionWithCategory expectedFieldPositions[] = {
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD, 0, 11},
{UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 12, 21}};
checkMixedFormattedValue(
message,
fv,
expectedString,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
}
static const char *kLast2 = "Last_2";
static const char *kLast = "Last";
static const char *kThis = "This";

View file

@ -2,7 +2,7 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import com.ibm.icu.text.NumberFormat.Field;
import java.text.Format.Field;
/**
* The canonical implementation of {@link Modifier}, containing a prefix and suffix string.

View file

@ -2,10 +2,9 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.text.Format.Field;
import java.util.Arrays;
import com.ibm.icu.text.NumberFormat.Field;
/**
* An implementation of {@link Modifier} that allows for multiple types of fields in the same modifier.
* Constructed based on the contents of two {@link NumberStringBuilder} instances (one for the prefix,

View file

@ -2,6 +2,8 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.text.Format.Field;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.UnicodeSet;
@ -122,7 +124,7 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
// NOTE: For prefix, output.fieldAt(index-1) gets the last field type in the prefix.
// This works even if the last code point in the prefix is 2 code units because the
// field value gets populated to both indices in the field array.
NumberFormat.Field affixField = (affix == PREFIX) ? output.fieldAt(index - 1)
Field affixField = (affix == PREFIX) ? output.fieldAt(index - 1)
: output.fieldAt(index);
if (affixField != NumberFormat.Field.CURRENCY) {
return 0;

View file

@ -2,8 +2,9 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.text.Format.Field;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.text.NumberFormat.Field;
/**
* A Modifier is an object that can be passed through the formatting pipeline until it is finally applied

View file

@ -326,7 +326,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
}
@Override
public boolean containsField(Field field) {
public boolean containsField(java.text.Format.Field field) {
// This method is not currently used. (unsafe path not used in range formatting)
assert false;
return false;

View file

@ -5,15 +5,14 @@ package com.ibm.icu.impl.number;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.FieldPosition;
import java.text.Format.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.ibm.icu.impl.StaticUnicodeSets;
import com.ibm.icu.text.ConstrainedFieldPosition;
import com.ibm.icu.text.ConstrainedFieldPosition.ConstraintType;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.NumberFormat.Field;
import com.ibm.icu.text.UnicodeSet;
/**
@ -521,7 +520,7 @@ public class NumberStringBuilder implements CharSequence {
ConstrainedFieldPosition cfpos = new ConstrainedFieldPosition();
cfpos.constrainField(rawField);
cfpos.setState(rawField, null, fp.getBeginIndex(), fp.getEndIndex());
if (nextPosition(cfpos)) {
if (nextPosition(cfpos, null)) {
fp.setBeginIndex(cfpos.getStart());
fp.setEndIndex(cfpos.getLimit());
return true;
@ -545,28 +544,38 @@ public class NumberStringBuilder implements CharSequence {
return false;
}
public AttributedCharacterIterator toCharacterIterator() {
public AttributedCharacterIterator toCharacterIterator(Field numericField) {
ConstrainedFieldPosition cfpos = new ConstrainedFieldPosition();
AttributedString as = new AttributedString(toString());
while (this.nextPosition(cfpos)) {
while (this.nextPosition(cfpos, numericField)) {
// Backwards compatibility: field value = field
as.addAttribute(cfpos.getField(), cfpos.getField(), cfpos.getStart(), cfpos.getLimit());
}
return as.getIterator();
}
public boolean nextPosition(ConstrainedFieldPosition cfpos) {
if (cfpos.getConstraintType() == ConstraintType.CLASS
&& !cfpos.getClassConstraint().isAssignableFrom(NumberFormat.Field.class)) {
return false;
static class NullField extends Field {
private static final long serialVersionUID = 1L;
static final NullField END = new NullField("end");
private NullField(String name) {
super(name);
}
}
boolean isSearchingForField = (cfpos.getConstraintType() == ConstraintType.FIELD);
/**
* Implementation of nextPosition consistent with the contract of FormattedValue.
*
* @param cfpos
* The argument passed to the public API.
* @param numericField
* Optional. If non-null, apply this field to the entire numeric portion of the string.
* @return See FormattedValue#nextPosition.
*/
public boolean nextPosition(ConstrainedFieldPosition cfpos, Field numericField) {
int fieldStart = -1;
Field currField = null;
for (int i = zero + cfpos.getLimit(); i <= zero + length; i++) {
Field _field = (i < zero + length) ? fields[i] : null;
Field _field = (i < zero + length) ? fields[i] : NullField.END;
// Case 1: currently scanning a field.
if (currField != null) {
if (currField != _field) {
@ -592,9 +601,10 @@ public class NumberStringBuilder implements CharSequence {
continue;
}
// Special case: coalesce the INTEGER if we are pointing at the end of the INTEGER.
if ((!isSearchingForField || cfpos.getField() == NumberFormat.Field.INTEGER)
if (cfpos.matchesField(NumberFormat.Field.INTEGER)
&& i > zero
&& i - zero > cfpos.getLimit() // don't return the same field twice in a row
// don't return the same field twice in a row:
&& i - zero > cfpos.getLimit()
&& isIntOrGroup(fields[i - 1])
&& !isIntOrGroup(_field)) {
int j = i - 1;
@ -602,16 +612,29 @@ public class NumberStringBuilder implements CharSequence {
cfpos.setState(NumberFormat.Field.INTEGER, null, j - zero + 1, i - zero);
return true;
}
// Special case: coalesce NUMERIC if we are pointing at the end of the NUMERIC.
if (numericField != null
&& cfpos.matchesField(numericField)
&& i > zero
// don't return the same field twice in a row:
&& (i - zero > cfpos.getLimit() || cfpos.getField() != numericField)
&& isNumericField(fields[i - 1])
&& !isNumericField(_field)) {
int j = i - 1;
for (; j >= zero && isNumericField(fields[j]); j--) {}
cfpos.setState(numericField, null, j - zero + 1, i - zero);
return true;
}
// Special case: skip over INTEGER; will be coalesced later.
if (_field == NumberFormat.Field.INTEGER) {
_field = null;
}
// Case 2: no field starting at this position.
if (_field == null) {
if (_field == null || _field == NullField.END) {
continue;
}
// Case 3: check for field starting at this position
if (!isSearchingForField || cfpos.getField() == _field) {
if (cfpos.matchesField(_field)) {
fieldStart = i - zero;
currField = _field;
}
@ -621,10 +644,14 @@ public class NumberStringBuilder implements CharSequence {
return false;
}
private static boolean isIntOrGroup(NumberFormat.Field field) {
private static boolean isIntOrGroup(Field field) {
return field == NumberFormat.Field.INTEGER || field == NumberFormat.Field.GROUPING_SEPARATOR;
}
private static boolean isNumericField(Field field) {
return field == null || NumberFormat.Field.class.isAssignableFrom(field.getClass());
}
private int trimBack(int limit) {
return StaticUnicodeSets.get(StaticUnicodeSets.Key.DEFAULT_IGNORABLES)
.spanBack(this, limit, UnicodeSet.SpanCondition.CONTAINED);

View file

@ -2,9 +2,10 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.text.Format.Field;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.number.range.PrefixInfixSuffixLengthHelper;
import com.ibm.icu.text.NumberFormat.Field;
import com.ibm.icu.util.ICUException;
/**
@ -65,7 +66,7 @@ public class SimpleModifier implements Modifier {
@Override
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
return formatAsPrefixSuffix(output, leftIndex, rightIndex, field);
return formatAsPrefixSuffix(output, leftIndex, rightIndex);
}
@Override
@ -139,8 +140,7 @@ public class SimpleModifier implements Modifier {
public int formatAsPrefixSuffix(
NumberStringBuilder result,
int startIndex,
int endIndex,
Field field) {
int endIndex) {
if (suffixOffset == -1) {
// There is no argument for the inner number; overwrite the entire segment with our string.
return result.splice(startIndex, endIndex, compiledPattern, 2, 2 + prefixLength, field);

View file

@ -101,7 +101,7 @@ public class FormattedNumber implements FormattedValue {
*/
@Override
public boolean nextPosition(ConstrainedFieldPosition cfpos) {
return nsb.nextPosition(cfpos);
return nsb.nextPosition(cfpos, null);
}
/**
@ -150,7 +150,7 @@ public class FormattedNumber implements FormattedValue {
*/
@Override
public AttributedCharacterIterator toCharacterIterator() {
return nsb.toCharacterIterator();
return nsb.toCharacterIterator(null);
}
/**

View file

@ -107,7 +107,7 @@ public class FormattedNumberRange implements FormattedValue {
*/
@Override
public boolean nextPosition(ConstrainedFieldPosition cfpos) {
return string.nextPosition(cfpos);
return string.nextPosition(cfpos, null);
}
/**
@ -151,7 +151,7 @@ public class FormattedNumberRange implements FormattedValue {
*/
@Override
public AttributedCharacterIterator toCharacterIterator() {
return string.toCharacterIterator();
return string.toCharacterIterator(null);
}
/**

View file

@ -133,6 +133,13 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
return new LocalizedNumberFormatterAsFormat(this, resolve().loc);
}
/** Helper method that creates a NumberStringBuilder and formats. */
private FormattedNumber format(DecimalQuantity fq) {
NumberStringBuilder string = new NumberStringBuilder();
formatImpl(fq, string);
return new FormattedNumber(string, fq);
}
/**
* This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a
* static code path for the first few calls, and compiling a more efficient data structure if called
@ -143,20 +150,19 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
*
* @param fq
* The quantity to be formatted.
* @return The formatted number result.
* @param string
* The string builder into which to insert the result.
*
* @internal
* @deprecated ICU 60 This API is ICU internal only.
*/
@Deprecated
public FormattedNumber format(DecimalQuantity fq) {
NumberStringBuilder string = new NumberStringBuilder();
public void formatImpl(DecimalQuantity fq, NumberStringBuilder string) {
if (computeCompiled()) {
compiled.format(fq, string);
} else {
NumberFormatterImpl.formatStatic(resolve(), fq, string);
}
return new FormattedNumber(string, fq);
}
/**

View file

@ -2,6 +2,8 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.number;
import java.text.Format.Field;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.MicroProps;
import com.ibm.icu.impl.number.MicroPropsGenerator;
@ -13,7 +15,6 @@ import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.number.Precision.SignificantRounderImpl;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.NumberFormat.Field;
/**
* A class that defines the scientific notation style to be used when formatting numbers in

View file

@ -2009,6 +2009,10 @@ public abstract class NumberFormat extends UFormat {
return PERMILLE;
if (this.getName().equals(SIGN.getName()))
return SIGN;
if (this.getName().equals(MEASURE_UNIT.getName()))
return MEASURE_UNIT;
if (this.getName().equals(COMPACT.getName()))
return COMPACT;
throw new InvalidObjectException("An invalid object.");
}

View file

@ -12,12 +12,11 @@ import java.text.FieldPosition;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.text.PluralRules.FixedDecimal;
/**
* QuantityFormatter represents an unknown quantity of something and formats a known quantity
* in terms of that something. For example, a QuantityFormatter that represents X apples may
* format 1 as "1 apple" and 3 as "3 apples"
* format 1 as "1 apple" and 3 as "3 apples"
* <p>
* QuanitityFormatter appears here instead of in com.ibm.icu.impl because it depends on
* PluralRules and DecimalFormat. It is package-protected as it is not meant for public use.
@ -101,24 +100,6 @@ class QuantityFormatter {
return StandardPlural.orOtherFromString(pluralKeyword);
}
/**
* Selects the standard plural form for the number/formatter/rules.
*/
public static StandardPlural selectPlural(
Number number, NumberFormat fmt, PluralRules rules,
StringBuffer formattedNumber, FieldPosition pos) {
UFieldPosition fpos = new UFieldPosition(pos.getFieldAttribute(), pos.getField());
fmt.format(number, formattedNumber, fpos);
// TODO: Long, BigDecimal & BigInteger may not fit into doubleValue().
FixedDecimal fd = new FixedDecimal(
number.doubleValue(),
fpos.getCountVisibleFractionDigits(), fpos.getFractionDigits());
String pluralKeyword = rules.select(fd);
pos.setBeginIndex(fpos.getBeginIndex());
pos.setEndIndex(fpos.getEndIndex());
return StandardPlural.orOtherFromString(pluralKeyword);
}
/**
* Formats the pattern with the value and adjusts the FieldPosition.
*/

View file

@ -8,20 +8,28 @@
*/
package com.ibm.icu.text;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.text.AttributedCharacterIterator;
import java.text.Format;
import java.util.EnumMap;
import java.util.Locale;
import com.ibm.icu.impl.CacheBase;
import com.ibm.icu.impl.DontCareFieldPosition;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.SoftCache;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
import com.ibm.icu.impl.number.NumberStringBuilder;
import com.ibm.icu.impl.number.SimpleModifier;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.ICUException;
import com.ibm.icu.util.ICUUncheckedIOException;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
@ -386,6 +394,156 @@ public final class RelativeDateTimeFormatter {
SATURDAY,
}
/**
* Field constants used when accessing field information for relative
* datetime strings in FormattedValue.
* <p>
* There is no public constructor to this class; the only instances are the
* constants defined here.
* <p>
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
public static class Field extends Format.Field {
private static final long serialVersionUID = -5327685528663492325L;
/**
* Represents a literal text string, like "tomorrow" or "days ago".
*
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
public static final Field LITERAL = new Field("literal");
/**
* Represents a number quantity, like "3" in "3 days ago".
*
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
public static final Field NUMERIC = new Field("numeric");
private Field(String fieldName) {
super(fieldName);
}
/**
* Serizalization method resolve instances to the constant Field values
*
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
@Override
protected Object readResolve() throws InvalidObjectException {
if (this.getName().equals(LITERAL.getName()))
return LITERAL;
if (this.getName().equals(NUMERIC.getName()))
return NUMERIC;
throw new InvalidObjectException("An invalid object.");
}
}
/**
* Represents the result of a formatting operation of a relative datetime.
* Access the string value or field information.
*
* @author sffc
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
public static class FormattedRelativeDateTime implements FormattedValue {
private final NumberStringBuilder string;
private FormattedRelativeDateTime(NumberStringBuilder string) {
this.string = string;
}
/**
* {@inheritDoc}
*
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
@Override
public String toString() {
return string.toString();
}
/**
* {@inheritDoc}
*
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
@Override
public int length() {
return string.length();
}
/**
* {@inheritDoc}
*
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
@Override
public char charAt(int index) {
return string.charAt(index);
}
/**
* {@inheritDoc}
*
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
@Override
public CharSequence subSequence(int start, int end) {
return string.subString(start, end);
}
/**
* {@inheritDoc}
*
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
@Override
public <A extends Appendable> A appendTo(A appendable) {
try {
appendable.append(string);
} catch (IOException e) {
// Throw as an unchecked exception to avoid users needing try/catch
throw new ICUUncheckedIOException(e);
}
return appendable;
}
/**
* {@inheritDoc}
*
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
@Override
public boolean nextPosition(ConstrainedFieldPosition cfpos) {
return string.nextPosition(cfpos, Field.NUMERIC);
}
/**
* {@inheritDoc}
*
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
@Override
public AttributedCharacterIterator toCharacterIterator() {
return string.toCharacterIterator(Field.NUMERIC);
}
}
/**
* Returns a RelativeDateTimeFormatter for the default locale.
* @stable ICU 53
@ -482,7 +640,11 @@ public final class RelativeDateTimeFormatter {
/**
* Formats a relative date with a quantity such as "in 5 days" or
* "3 months ago"
* "3 months ago".
*
* This method returns a String. To get more information about the
* formatting result, use formatToValue().
*
* @param quantity The numerical amount e.g 5. This value is formatted
* according to this object's {@link NumberFormat} object.
* @param direction NEXT means a future relative date; LAST means a past
@ -494,25 +656,57 @@ public final class RelativeDateTimeFormatter {
* @stable ICU 53
*/
public String format(double quantity, Direction direction, RelativeUnit unit) {
NumberStringBuilder output = formatImpl(quantity, direction, unit);
return adjustForContext(output.toString());
}
/**
* Formats a relative date with a quantity such as "in 5 days" or
* "3 months ago".
*
* This method returns a FormattedRelativeDateTime, which exposes more
* information than the String returned by format().
*
* @param quantity The numerical amount e.g 5. This value is formatted
* according to this object's {@link NumberFormat} object.
* @param direction NEXT means a future relative date; LAST means a past
* relative date.
* @param unit the unit e.g day? month? year?
* @return the formatted relative datetime
* @throws IllegalArgumentException if direction is something other than
* NEXT or LAST.
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
public FormattedRelativeDateTime formatToValue(double quantity, Direction direction, RelativeUnit unit) {
checkNoAdjustForContext();
return new FormattedRelativeDateTime(formatImpl(quantity, direction, unit));
}
/** Implementation method for format and formatToValue with RelativeUnit */
private NumberStringBuilder formatImpl(double quantity, Direction direction, RelativeUnit unit) {
if (direction != Direction.LAST && direction != Direction.NEXT) {
throw new IllegalArgumentException("direction must be NEXT or LAST");
}
String result;
int pastFutureIndex = (direction == Direction.NEXT ? 1 : 0);
// This class is thread-safe, yet numberFormat is not. To ensure thread-safety of this
// class we must guarantee that only one thread at a time uses our numberFormat.
synchronized (numberFormat) {
StringBuffer formatStr = new StringBuffer();
DontCareFieldPosition fieldPosition = DontCareFieldPosition.INSTANCE;
StandardPlural pluralForm = QuantityFormatter.selectPlural(quantity,
numberFormat, pluralRules, formatStr, fieldPosition);
String formatter = getRelativeUnitPluralPattern(style, unit, pastFutureIndex, pluralForm);
result = SimpleFormatterImpl.formatCompiledPattern(formatter, formatStr);
NumberStringBuilder output = new NumberStringBuilder();
String pluralKeyword;
if (numberFormat instanceof DecimalFormat) {
DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(quantity);
((DecimalFormat) numberFormat).toNumberFormatter().formatImpl(dq, output);
pluralKeyword = pluralRules.select(dq);
} else {
String result = numberFormat.format(quantity);
output.append(result, null);
pluralKeyword = pluralRules.select(quantity);
}
return adjustForContext(result);
StandardPlural pluralForm = StandardPlural.orOtherFromString(pluralKeyword);
String compiledPattern = getRelativeUnitPluralPattern(style, unit, pastFutureIndex, pluralForm);
SimpleModifier modifier = new SimpleModifier(compiledPattern, Field.LITERAL, false);
modifier.formatAsPrefixSuffix(output, 0, output.length());
return output;
}
/**
@ -520,6 +714,9 @@ public final class RelativeDateTimeFormatter {
* using a numeric style, e.g. "1 week ago", "in 1 week",
* "5 weeks ago", "in 5 weeks".
*
* This method returns a String. To get more information about the
* formatting result, use formatNumericToValue().
*
* @param offset The signed offset for the specified unit. This
* will be formatted according to this object's
* NumberFormat object.
@ -530,6 +727,35 @@ public final class RelativeDateTimeFormatter {
* @stable ICU 57
*/
public String formatNumeric(double offset, RelativeDateTimeUnit unit) {
NumberStringBuilder output = formatNumericImpl(offset, unit);
return adjustForContext(output.toString());
}
/**
* Format a combination of RelativeDateTimeUnit and numeric offset
* using a numeric style, e.g. "1 week ago", "in 1 week",
* "5 weeks ago", "in 5 weeks".
*
* This method returns a FormattedRelativeDateTime, which exposes more
* information than the String returned by formatNumeric().
*
* @param offset The signed offset for the specified unit. This
* will be formatted according to this object's
* NumberFormat object.
* @param unit The unit to use when formatting the relative
* date, e.g. RelativeDateTimeUnit.WEEK,
* RelativeDateTimeUnit.FRIDAY.
* @return The formatted string (may be empty in case of error)
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
public FormattedRelativeDateTime formatNumericToValue(double offset, RelativeDateTimeUnit unit) {
checkNoAdjustForContext();
return new FormattedRelativeDateTime(formatNumericImpl(offset, unit));
}
/** Implementation method for formatNumeric and formatNumericToValue */
private NumberStringBuilder formatNumericImpl(double offset, RelativeDateTimeUnit unit) {
// 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.
@ -555,8 +781,7 @@ public final class RelativeDateTimeFormatter {
direction = Direction.LAST;
offset = -offset;
}
String result = format(offset, direction, relunit);
return (result != null)? result: "";
return formatImpl(offset, direction, relunit);
}
private int[] styleToDateFormatSymbolsWidth = {
@ -565,6 +790,10 @@ public final class RelativeDateTimeFormatter {
/**
* Formats a relative date without a quantity.
*
* This method returns a String. To get more information about the
* formatting result, use formatToValue().
*
* @param direction NEXT, LAST, THIS, etc.
* @param unit e.g SATURDAY, DAY, MONTH
* @return the formatted string. If direction has a value that is documented as not being
@ -575,6 +804,39 @@ public final class RelativeDateTimeFormatter {
* @stable ICU 53
*/
public String format(Direction direction, AbsoluteUnit unit) {
String result = formatAbsoluteImpl(direction, unit);
return result != null ? adjustForContext(result) : null;
}
/**
* Formats a relative date without a quantity.
*
* This method returns a FormattedRelativeDateTime, which exposes more
* information than the String returned by format().
*
* @param direction NEXT, LAST, THIS, etc.
* @param unit e.g SATURDAY, DAY, MONTH
* @return the formatted string. If direction has a value that is documented as not being
* fully supported in every locale (for example NEXT_2 or LAST_2) then this function may
* return null to signal that no formatted string is available.
* @throws IllegalArgumentException if the direction is incompatible with
* unit this can occur with NOW which can only take PLAIN.
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
public FormattedRelativeDateTime formatToValue(Direction direction, AbsoluteUnit unit) {
checkNoAdjustForContext();
String string = formatAbsoluteImpl(direction, unit);
if (string == null) {
return null;
}
NumberStringBuilder nsb = new NumberStringBuilder();
nsb.append(string, Field.LITERAL);
return new FormattedRelativeDateTime(nsb);
}
/** Implementation method for format and formatToValue with AbsoluteUnit */
private String formatAbsoluteImpl(Direction direction, AbsoluteUnit unit) {
if (unit == AbsoluteUnit.NOW && direction != Direction.PLAIN) {
throw new IllegalArgumentException("NOW can only accept direction PLAIN.");
}
@ -592,7 +854,7 @@ public final class RelativeDateTimeFormatter {
// Not PLAIN, or not a weekday.
result = getAbsoluteUnitString(style, unit, direction);
}
return result != null ? adjustForContext(result) : null;
return result;
}
/**
@ -602,6 +864,9 @@ public final class RelativeDateTimeFormatter {
* style if no appropriate text term is available for the specified
* offset in the objects locale.
*
* This method returns a String. To get more information about the
* formatting result, use formatToValue().
*
* @param offset The signed offset for the specified field.
* @param unit The unit to use when formatting the relative
* date, e.g. RelativeDateTimeUnit.WEEK,
@ -610,6 +875,43 @@ public final class RelativeDateTimeFormatter {
* @stable ICU 57
*/
public String format(double offset, RelativeDateTimeUnit unit) {
return adjustForContext(formatRelativeImpl(offset, unit).toString());
}
/**
* Format a combination of RelativeDateTimeUnit and numeric offset
* using a text style if possible, e.g. "last week", "this week",
* "next week", "yesterday", "tomorrow". Falls back to numeric
* style if no appropriate text term is available for the specified
* offset in the objects locale.
*
* This method returns a FormattedRelativeDateTime, which exposes more
* information than the String returned by format().
*
* @param offset The signed offset for the specified field.
* @param unit The unit to use when formatting the relative
* date, e.g. RelativeDateTimeUnit.WEEK,
* RelativeDateTimeUnit.FRIDAY.
* @return The formatted string (may be empty in case of error)
* @draft ICU 64
* @provisional This API might change or be removed in a future release.
*/
public FormattedRelativeDateTime formatToValue(double offset, RelativeDateTimeUnit unit) {
checkNoAdjustForContext();
CharSequence cs = formatRelativeImpl(offset, unit);
NumberStringBuilder nsb;
if (cs instanceof NumberStringBuilder) {
nsb = (NumberStringBuilder) cs;
} else {
nsb = new NumberStringBuilder();
nsb.append(cs, Field.LITERAL);
}
return new FormattedRelativeDateTime(nsb);
}
/** Implementation method for format and formatToValue with RelativeDateTimeUnit. */
private CharSequence formatRelativeImpl(double offset, RelativeDateTimeUnit unit) {
// 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.
@ -661,13 +963,13 @@ public final class RelativeDateTimeFormatter {
break;
}
if (!useNumeric) {
String result = format(direction, absunit);
String result = formatAbsoluteImpl(direction, absunit);
if (result != null && result.length() > 0) {
return result;
}
}
// otherwise fallback to formatNumeric
return formatNumeric(offset, unit);
return formatNumericImpl(offset, unit);
}
/**
@ -756,6 +1058,12 @@ public final class RelativeDateTimeFormatter {
}
}
private void checkNoAdjustForContext() {
if (breakIterator != null) {
throw new UnsupportedOperationException("Capitalization context is not supported in formatV");
}
}
private RelativeDateTimeFormatter(
EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap,
EnumMap<Style, EnumMap<RelativeUnit, String[][]>> patternMap,

View file

@ -128,7 +128,7 @@ public class FormattedValueTest {
String baseMessage = message + ": " + fv.toString() + ": ";
// Check the String and CharSequence
assertEquals(baseMessage + "string", expectedString, fv.toString());
assertEquals(baseMessage + " string", expectedString, fv.toString());
assertCharSequenceEquals(expectedString, fv);
// Check the AttributedCharacterIterator
@ -159,7 +159,8 @@ public class FormattedValueTest {
assertEquals(baseMessage + expectedField + " end @" + i, expectedEndIndex, actualEndIndex);
attributesRemaining--;
}
assertEquals(baseMessage + "Should have looked at every field", 0, attributesRemaining);
assertEquals(baseMessage + "Should have looked at every field: " + i + ": " + currentAttributes,
0, attributesRemaining);
}
assertEquals(baseMessage + "Should have looked at every character", stringLength, i);

View file

@ -21,9 +21,11 @@ import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.RelativeDateTimeFormatter;
import com.ibm.icu.text.RelativeDateTimeFormatter.AbsoluteUnit;
import com.ibm.icu.text.RelativeDateTimeFormatter.Direction;
import com.ibm.icu.text.RelativeDateTimeFormatter.FormattedRelativeDateTime;
import com.ibm.icu.text.RelativeDateTimeFormatter.RelativeDateTimeUnit;
import com.ibm.icu.text.RelativeDateTimeFormatter.RelativeUnit;
import com.ibm.icu.text.RelativeDateTimeFormatter.Style;
import com.ibm.icu.text.RuleBasedNumberFormat;
import com.ibm.icu.util.ULocale;
@RunWith(JUnit4.class)
@ -1025,12 +1027,101 @@ public class RelativeDateTimeFormatterTest extends TestFmwk {
assertEquals("narrow: in 6 qtr", "in 6 qtr", w);
}
@Test
public void TestLocales() {
ULocale[] availableLocales = ULocale.getAvailableLocales();
for (ULocale loc: availableLocales) {
RelativeDateTimeFormatter.getInstance(loc);
@Test
public void TestLocales() {
ULocale[] availableLocales = ULocale.getAvailableLocales();
for (ULocale loc: availableLocales) {
RelativeDateTimeFormatter.getInstance(loc);
}
}
@Test
public void TestFields() {
RelativeDateTimeFormatter fmt = RelativeDateTimeFormatter.getInstance(ULocale.US);
{
String message = "automatic absolute unit";
FormattedRelativeDateTime fv = fmt.formatToValue(1, RelativeDateTimeUnit.DAY);
String expectedString = "tomorrow";
Object[][] expectedFieldPositions = new Object[][]{
{RelativeDateTimeFormatter.Field.LITERAL, 0, 8}};
FormattedValueTest.checkFormattedValue(message, fv, expectedString, expectedFieldPositions);
}
{
String message = "automatic numeric unit";
FormattedRelativeDateTime fv = fmt.formatToValue(3, RelativeDateTimeUnit.DAY);
String expectedString = "in 3 days";
Object[][] expectedFieldPositions = new Object[][]{
{RelativeDateTimeFormatter.Field.LITERAL, 0, 2},
{NumberFormat.Field.INTEGER, 3, 4},
{RelativeDateTimeFormatter.Field.NUMERIC, 3, 4},
{RelativeDateTimeFormatter.Field.LITERAL, 5, 9}};
FormattedValueTest.checkFormattedValue(message, fv, expectedString, expectedFieldPositions);
}
{
String message = "manual absolute unit";
FormattedRelativeDateTime fv = fmt.formatToValue(Direction.NEXT, AbsoluteUnit.MONDAY);
String expectedString = "next Monday";
Object[][] expectedFieldPositions = new Object[][]{
{RelativeDateTimeFormatter.Field.LITERAL, 0, 11}};
FormattedValueTest.checkFormattedValue(message, fv, expectedString, expectedFieldPositions);
}
{
String message = "manual numeric unit";
FormattedRelativeDateTime fv = fmt.formatNumericToValue(1.5, RelativeDateTimeUnit.WEEK);
String expectedString = "in 1.5 weeks";
Object[][] expectedFieldPositions = new Object[][]{
{RelativeDateTimeFormatter.Field.LITERAL, 0, 2},
{NumberFormat.Field.INTEGER, 3, 4},
{NumberFormat.Field.DECIMAL_SEPARATOR, 4, 5},
{NumberFormat.Field.FRACTION, 5, 6},
{RelativeDateTimeFormatter.Field.NUMERIC, 3, 6},
{RelativeDateTimeFormatter.Field.LITERAL, 7, 12}};
FormattedValueTest.checkFormattedValue(message, fv, expectedString, expectedFieldPositions);
}
{
String message = "manual numeric resolved unit";
FormattedRelativeDateTime fv = fmt.formatToValue(12, Direction.LAST, RelativeUnit.HOURS);
String expectedString = "12 hours ago";
Object[][] expectedFieldPositions = new Object[][]{
{NumberFormat.Field.INTEGER, 0, 2},
{RelativeDateTimeFormatter.Field.NUMERIC, 0, 2},
{RelativeDateTimeFormatter.Field.LITERAL, 3, 12}};
FormattedValueTest.checkFormattedValue(message, fv, expectedString, expectedFieldPositions);
}
// Test when the number field is at the end
fmt = RelativeDateTimeFormatter.getInstance(new ULocale("sw"));
{
String message = "numeric field at end";
FormattedRelativeDateTime fv = fmt.formatToValue(12, RelativeDateTimeUnit.HOUR);
String expectedString = "baada ya saa 12";
Object[][] expectedFieldPositions = new Object[][]{
{RelativeDateTimeFormatter.Field.LITERAL, 0, 12},
{NumberFormat.Field.INTEGER, 13, 15},
{RelativeDateTimeFormatter.Field.NUMERIC, 13, 15}};
FormattedValueTest.checkFormattedValue(message, fv, expectedString, expectedFieldPositions);
}
}
@Test
public void TestRBNF() {
RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ULocale.US, RuleBasedNumberFormat.SPELLOUT);
RelativeDateTimeFormatter fmt = RelativeDateTimeFormatter.getInstance(ULocale.US, rbnf);
assertEquals("format (direction)", "in five seconds", fmt.format(5, Direction.NEXT, RelativeUnit.SECONDS));
assertEquals("formatNumeric", "one week ago", fmt.formatNumeric(-1, RelativeDateTimeUnit.WEEK));
assertEquals("format (absolute)", "yesterday", fmt.format(Direction.LAST, AbsoluteUnit.DAY));
assertEquals("format (relative)", "in forty-two months", fmt.format(42, RelativeDateTimeUnit.MONTH));
{
String message = "formatToValue (relative)";
FormattedRelativeDateTime fv = fmt.formatToValue(-100, RelativeDateTimeUnit.YEAR);
String expectedString = "one hundred years ago";
Object[][] expectedFieldPositions = new Object[][]{
{RelativeDateTimeFormatter.Field.NUMERIC, 0, 11},
{RelativeDateTimeFormatter.Field.LITERAL, 12, 21}};
FormattedValueTest.checkFormattedValue(message, fv, expectedString, expectedFieldPositions);
}
}
}
}

View file

@ -22,6 +22,7 @@ import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.impl.number.DecimalFormatProperties;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
import com.ibm.icu.impl.number.NumberStringBuilder;
import com.ibm.icu.impl.number.RoundingUtils;
import com.ibm.icu.number.LocalizedNumberFormatter;
import com.ibm.icu.number.NumberFormatter;
@ -235,8 +236,12 @@ public class DecimalQuantityTest extends TestFmwk {
for (LocalizedNumberFormatter format : formats) {
DecimalQuantity q0 = rq0.createCopy();
DecimalQuantity q1 = rq1.createCopy();
String s1 = format.format(q0).toString();
String s2 = format.format(q1).toString();
NumberStringBuilder nsb1 = new NumberStringBuilder();
NumberStringBuilder nsb2 = new NumberStringBuilder();
format.formatImpl(q0, nsb1);
format.formatImpl(q1, nsb2);
String s1 = nsb1.toString();
String s2 = nsb2.toString();
assertEquals("Different output from formatter (" + q0 + ", " + q1 + ")", s1, s2);
}
}

View file

@ -34,6 +34,7 @@ import com.ibm.icu.text.MessageFormat;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.PluralFormat;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.RelativeDateTimeFormatter;
import com.ibm.icu.text.RuleBasedNumberFormat;
import com.ibm.icu.text.SelectFormat;
import com.ibm.icu.text.SimpleDateFormat;
@ -1800,6 +1801,21 @@ public class FormatHandler
}
}
public static class RelativeDateTimeFormatterFieldHandler implements SerializableTestUtility.Handler
{
@Override
public Object[] getTestObjects()
{
return new Object[] {RelativeDateTimeFormatter.Field.LITERAL};
}
@Override
public boolean hasSameBehavior(Object a, Object b)
{
return (a == b);
}
}
public static class DateFormatHandler implements SerializableTestUtility.Handler
{
static HashMap cannedPatterns = new HashMap();

View file

@ -818,6 +818,7 @@ public class SerializableTestUtility {
map.put("com.ibm.icu.text.DateFormat$Field", new FormatHandler.DateFormatFieldHandler());
map.put("com.ibm.icu.text.ChineseDateFormat$Field", new FormatHandler.ChineseDateFormatFieldHandler());
map.put("com.ibm.icu.text.MessageFormat$Field", new FormatHandler.MessageFormatFieldHandler());
map.put("com.ibm.icu.text.RelativeDateTimeFormatter$Field", new FormatHandler.RelativeDateTimeFormatterFieldHandler());
map.put("com.ibm.icu.impl.duration.BasicDurationFormat", new FormatHandler.BasicDurationFormatHandler());
map.put("com.ibm.icu.impl.RelativeDateFormat", new FormatHandler.RelativeDateFormatHandler());