mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-13 17:01:16 +00:00
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:
parent
c70a9db818
commit
249e03ccd6
47 changed files with 1936 additions and 208 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
46
icu4c/source/i18n/formattedval_sbimpl.cpp
Normal file
46
icu4c/source/i18n/formattedval_sbimpl.cpp
Normal 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 */
|
|
@ -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;
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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++;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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 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 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 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 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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Add table
Reference in a new issue