ICU-20342 Adding FormattedDateInterval in C and C++.

- Adds first "span" field category
- Re-implements DateIntervalFormat#fallbackFormat to use FieldPositionHandler
- New temporary wiring in SimpleFormatter
This commit is contained in:
Shane Carr 2019-02-11 15:53:31 -08:00 committed by Shane F. Carr
parent f4f899a63b
commit e2ad4f8113
17 changed files with 787 additions and 130 deletions

View file

@ -246,15 +246,24 @@ UnicodeString &SimpleFormatter::formatAndReplace(
}
UnicodeString SimpleFormatter::getTextWithNoArguments(
const UChar *compiledPattern, int32_t compiledPatternLength) {
const UChar *compiledPattern,
int32_t compiledPatternLength,
int32_t* offsets,
int32_t offsetsLength) {
for (int32_t i = 0; i < offsetsLength; i++) {
offsets[i] = -1;
}
int32_t capacity = compiledPatternLength - 1 -
getArgumentLimit(compiledPattern, compiledPatternLength);
UnicodeString sb(capacity, 0, 0); // Java: StringBuilder
for (int32_t i = 1; i < compiledPatternLength;) {
int32_t segmentLength = compiledPattern[i++] - ARG_NUM_LIMIT;
if (segmentLength > 0) {
sb.append(compiledPattern + i, segmentLength);
i += segmentLength;
int32_t n = compiledPattern[i++];
if (n > ARG_NUM_LIMIT) {
n -= ARG_NUM_LIMIT;
sb.append(compiledPattern + i, n);
i += n;
} else if (n < offsetsLength) {
offsets[n] = sb.length();
}
}
return sb;

View file

@ -265,9 +265,38 @@ public:
* @stable ICU 57
*/
UnicodeString getTextWithNoArguments() const {
return getTextWithNoArguments(compiledPattern.getBuffer(), compiledPattern.length());
return getTextWithNoArguments(
compiledPattern.getBuffer(),
compiledPattern.length(),
nullptr,
0);
}
#ifndef U_HIDE_INTERNAL_API
/**
* Returns the pattern text with none of the arguments.
* Like formatting with all-empty string values.
*
* TODO(ICU-20406): Replace this with an Iterator interface.
*
* @param offsets offsets[i] receives the offset of where {i} was located
* before it was replaced by an empty string.
* For example, "a{0}b{1}" produces offset 1 for i=0 and 2 for i=1.
* Can be nullptr if offsetsLength==0.
* If there is no {i} in the pattern, then offsets[i] is set to -1.
* @param offsetsLength The length of the offsets array.
*
* @internal
*/
UnicodeString getTextWithNoArguments(int32_t *offsets, int32_t offsetsLength) const {
return getTextWithNoArguments(
compiledPattern.getBuffer(),
compiledPattern.length(),
offsets,
offsetsLength);
}
#endif // U_HIDE_INTERNAL_API
private:
/**
* Binary representation of the compiled pattern.
@ -285,7 +314,11 @@ private:
return compiledPatternLength == 0 ? 0 : compiledPattern[0];
}
static UnicodeString getTextWithNoArguments(const char16_t *compiledPattern, int32_t compiledPatternLength);
static UnicodeString getTextWithNoArguments(
const char16_t *compiledPattern,
int32_t compiledPatternLength,
int32_t *offsets,
int32_t offsetsLength);
static UnicodeString &format(
const char16_t *compiledPattern, int32_t compiledPatternLength,

View file

@ -28,6 +28,7 @@
#include "dtitv_impl.h"
#include "mutex.h"
#include "uresimp.h"
#include "formattedval_impl.h"
#ifdef DTITVFMT_DEBUG
#include <iostream>
@ -65,6 +66,17 @@ static const UChar gLaterFirstPrefix[] = {LOW_L, LOW_A, LOW_T, LOW_E, LOW_S,LOW_
static const UChar gEarlierFirstPrefix[] = {LOW_E, LOW_A, LOW_R, LOW_L, LOW_I, LOW_E, LOW_S, LOW_T, CAP_F, LOW_I, LOW_R, LOW_S, LOW_T, COLON};
class FormattedDateIntervalData : public FormattedValueFieldPositionIteratorImpl {
public:
FormattedDateIntervalData(UErrorCode& status) : FormattedValueFieldPositionIteratorImpl(5, status) {}
virtual ~FormattedDateIntervalData();
};
FormattedDateIntervalData::~FormattedDateIntervalData() = default;
UPRV_FORMATTED_VALUE_SUBCLASS_AUTO_IMPL(FormattedDateInterval)
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DateIntervalFormat)
// Mutex, protects access to fDateFormat, fFromCalendar and fToCalendar.
@ -271,15 +283,51 @@ DateIntervalFormat::format(const DateInterval* dtInterval,
if ( U_FAILURE(status) ) {
return appendTo;
}
if (fFromCalendar == NULL || fToCalendar == NULL || fDateFormat == NULL || fInfo == NULL) {
if (fDateFormat == NULL || fInfo == NULL) {
status = U_INVALID_STATE_ERROR;
return appendTo;
}
FieldPositionOnlyHandler handler(fieldPosition);
handler.setAcceptFirstOnly(TRUE);
int8_t ignore;
Mutex lock(&gFormatterMutex);
fFromCalendar->setTime(dtInterval->getFromDate(), status);
fToCalendar->setTime(dtInterval->getToDate(), status);
return formatImpl(*fFromCalendar, *fToCalendar, appendTo,fieldPosition, status);
return formatIntervalImpl(*dtInterval, appendTo, ignore, handler, status);
}
FormattedDateInterval DateIntervalFormat::formatToValue(
const DateInterval& dtInterval,
UErrorCode& status) const {
LocalPointer<FormattedDateIntervalData> result(new FormattedDateIntervalData(status), status);
if (U_FAILURE(status)) {
return FormattedDateInterval(status);
}
UnicodeString string;
int8_t firstIndex;
auto handler = result->getHandler(status);
handler.setCategory(UFIELD_CATEGORY_DATE);
{
Mutex lock(&gFormatterMutex);
formatIntervalImpl(dtInterval, string, firstIndex, handler, status);
}
handler.getError(status);
result->appendString(string, status);
if (U_FAILURE(status)) {
return FormattedDateInterval(status);
}
// Compute the span fields and sort them into place:
if (firstIndex != -1) {
result->addOverlapSpans(UFIELD_CATEGORY_DATE_INTERVAL_SPAN, firstIndex, status);
if (U_FAILURE(status)) {
return FormattedDateInterval(status);
}
result->sort();
}
return FormattedDateInterval(result.orphan());
}
@ -289,8 +337,63 @@ DateIntervalFormat::format(Calendar& fromCalendar,
UnicodeString& appendTo,
FieldPosition& pos,
UErrorCode& status) const {
FieldPositionOnlyHandler handler(pos);
handler.setAcceptFirstOnly(TRUE);
int8_t ignore;
Mutex lock(&gFormatterMutex);
return formatImpl(fromCalendar, toCalendar, appendTo, pos, status);
return formatImpl(fromCalendar, toCalendar, appendTo, ignore, handler, status);
}
FormattedDateInterval DateIntervalFormat::formatToValue(
Calendar& fromCalendar,
Calendar& toCalendar,
UErrorCode& status) const {
LocalPointer<FormattedDateIntervalData> result(new FormattedDateIntervalData(status), status);
if (U_FAILURE(status)) {
return FormattedDateInterval(status);
}
UnicodeString string;
int8_t firstIndex;
auto handler = result->getHandler(status);
handler.setCategory(UFIELD_CATEGORY_DATE);
{
Mutex lock(&gFormatterMutex);
formatImpl(fromCalendar, toCalendar, string, firstIndex, handler, status);
}
handler.getError(status);
result->appendString(string, status);
if (U_FAILURE(status)) {
return FormattedDateInterval(status);
}
// Compute the span fields and sort them into place:
if (firstIndex != -1) {
result->addOverlapSpans(UFIELD_CATEGORY_DATE_INTERVAL_SPAN, firstIndex, status);
result->sort();
}
return FormattedDateInterval(result.orphan());
}
UnicodeString& DateIntervalFormat::formatIntervalImpl(
const DateInterval& dtInterval,
UnicodeString& appendTo,
int8_t& firstIndex,
FieldPositionHandler& fphandler,
UErrorCode& status) const {
if (U_FAILURE(status)) {
return appendTo;
}
if (fFromCalendar == nullptr || fToCalendar == nullptr) {
status = U_INVALID_STATE_ERROR;
return appendTo;
}
fFromCalendar->setTime(dtInterval.getFromDate(), status);
fToCalendar->setTime(dtInterval.getToDate(), status);
return formatImpl(*fFromCalendar, *fToCalendar, appendTo, firstIndex, fphandler, status);
}
@ -298,12 +401,16 @@ UnicodeString&
DateIntervalFormat::formatImpl(Calendar& fromCalendar,
Calendar& toCalendar,
UnicodeString& appendTo,
FieldPosition& pos,
int8_t& firstIndex,
FieldPositionHandler& fphandler,
UErrorCode& status) const {
if ( U_FAILURE(status) ) {
return appendTo;
}
// Initialize firstIndex to -1 (single date, no range)
firstIndex = -1;
// not support different calendar types and time zones
//if ( fromCalendar.getType() != toCalendar.getType() ) {
if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
@ -346,7 +453,7 @@ DateIntervalFormat::formatImpl(Calendar& fromCalendar,
/* ignore the millisecond etc. small fields' difference.
* use single date when all the above are the same.
*/
return fDateFormat->format(fromCalendar, appendTo, pos);
return fDateFormat->_format(fromCalendar, appendTo, fphandler, status);
}
UBool fromToOnSameDay = (field==UCAL_AM_PM || field==UCAL_HOUR || field==UCAL_MINUTE || field==UCAL_SECOND);
@ -363,9 +470,9 @@ DateIntervalFormat::formatImpl(Calendar& fromCalendar,
* the smallest calendar field in pattern,
* return single date format.
*/
return fDateFormat->format(fromCalendar, appendTo, pos);
return fDateFormat->_format(fromCalendar, appendTo, fphandler, status);
}
return fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos, status);
return fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, firstIndex, fphandler, status);
}
// If the first part in interval pattern is empty,
// the 2nd part of it saves the full-pattern used in fall-back.
@ -375,7 +482,7 @@ DateIntervalFormat::formatImpl(Calendar& fromCalendar,
UnicodeString originalPattern;
fDateFormat->toPattern(originalPattern);
fDateFormat->applyPattern(intervalPattern.secondPart);
appendTo = fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos, status);
appendTo = fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, firstIndex, fphandler, status);
fDateFormat->applyPattern(originalPattern);
return appendTo;
}
@ -384,24 +491,22 @@ DateIntervalFormat::formatImpl(Calendar& fromCalendar,
if ( intervalPattern.laterDateFirst ) {
firstCal = &toCalendar;
secondCal = &fromCalendar;
firstIndex = 1;
} else {
firstCal = &fromCalendar;
secondCal = &toCalendar;
firstIndex = 0;
}
// break the interval pattern into 2 parts,
// first part should not be empty,
UnicodeString originalPattern;
fDateFormat->toPattern(originalPattern);
fDateFormat->applyPattern(intervalPattern.firstPart);
fDateFormat->format(*firstCal, appendTo, pos);
fDateFormat->_format(*firstCal, appendTo, fphandler, status);
if ( !intervalPattern.secondPart.isEmpty() ) {
fDateFormat->applyPattern(intervalPattern.secondPart);
FieldPosition otherPos;
otherPos.setField(pos.getField());
fDateFormat->format(*secondCal, appendTo, otherPos);
if (pos.getEndIndex() == 0 && otherPos.getEndIndex() > 0) {
pos = otherPos;
}
fDateFormat->_format(*secondCal, appendTo, fphandler, status);
}
fDateFormat->applyPattern(originalPattern);
return appendTo;
@ -1294,40 +1399,37 @@ DateIntervalFormat::splitPatternInto2Part(const UnicodeString& intervalPattern)
return (i - count);
}
static const UChar bracketedZero[] = {0x7B,0x30,0x7D};
static const UChar bracketedOne[] = {0x7B,0x31,0x7D};
void
DateIntervalFormat::adjustPosition(UnicodeString& combiningPattern, // has {0} and {1} in it
UnicodeString& pat0, FieldPosition& pos0, // pattern and pos corresponding to {0}
UnicodeString& pat1, FieldPosition& pos1, // pattern and pos corresponding to {1}
FieldPosition& posResult) {
int32_t index0 = combiningPattern.indexOf(bracketedZero, 3, 0);
int32_t index1 = combiningPattern.indexOf(bracketedOne, 3, 0);
if (index0 < 0 || index1 < 0) {
void DateIntervalFormat::fallbackFormatRange(
Calendar& fromCalendar,
Calendar& toCalendar,
UnicodeString& appendTo,
int8_t& firstIndex,
FieldPositionHandler& fphandler,
UErrorCode& status) const {
UnicodeString fallbackPattern;
fInfo->getFallbackIntervalPattern(fallbackPattern);
SimpleFormatter sf(fallbackPattern, 2, 2, status);
if (U_FAILURE(status)) {
return;
}
int32_t placeholderLen = 3; // length of "{0}" or "{1}"
if (index0 < index1) {
if (pos0.getEndIndex() > 0) {
posResult.setBeginIndex(pos0.getBeginIndex() + index0);
posResult.setEndIndex(pos0.getEndIndex() + index0);
} else if (pos1.getEndIndex() > 0) {
// here index1 >= 3
index1 += pat0.length() - placeholderLen; // adjust for pat0 replacing {0}
posResult.setBeginIndex(pos1.getBeginIndex() + index1);
posResult.setEndIndex(pos1.getEndIndex() + index1);
}
int32_t offsets[2];
UnicodeString patternBody = sf.getTextWithNoArguments(offsets, 2);
// TODO(ICU-20406): Use SimpleFormatter Iterator interface when available.
if (offsets[0] < offsets[1]) {
firstIndex = 0;
appendTo.append(patternBody.tempSubStringBetween(0, offsets[0]));
fDateFormat->_format(fromCalendar, appendTo, fphandler, status);
appendTo.append(patternBody.tempSubStringBetween(offsets[0], offsets[1]));
fDateFormat->_format(toCalendar, appendTo, fphandler, status);
appendTo.append(patternBody.tempSubStringBetween(offsets[1]));
} else {
if (pos1.getEndIndex() > 0) {
posResult.setBeginIndex(pos1.getBeginIndex() + index1);
posResult.setEndIndex(pos1.getEndIndex() + index1);
} else if (pos0.getEndIndex() > 0) {
// here index0 >= 3
index0 += pat1.length() - placeholderLen; // adjust for pat1 replacing {1}
posResult.setBeginIndex(pos0.getBeginIndex() + index0);
posResult.setEndIndex(pos0.getEndIndex() + index0);
}
firstIndex = 1;
appendTo.append(patternBody.tempSubStringBetween(0, offsets[1]));
fDateFormat->_format(toCalendar, appendTo, fphandler, status);
appendTo.append(patternBody.tempSubStringBetween(offsets[1], offsets[0]));
fDateFormat->_format(fromCalendar, appendTo, fphandler, status);
appendTo.append(patternBody.tempSubStringBetween(offsets[0]));
}
}
@ -1336,51 +1438,50 @@ DateIntervalFormat::fallbackFormat(Calendar& fromCalendar,
Calendar& toCalendar,
UBool fromToOnSameDay, // new
UnicodeString& appendTo,
FieldPosition& pos,
int8_t& firstIndex,
FieldPositionHandler& fphandler,
UErrorCode& status) const {
if ( U_FAILURE(status) ) {
return appendTo;
}
UnicodeString fullPattern; // for saving the pattern in fDateFormat
UBool formatDatePlusTimeRange = (fromToOnSameDay && fDatePattern && fTimePattern);
// the fall back
if (formatDatePlusTimeRange) {
SimpleFormatter sf(*fDateTimeFormat, 2, 2, status);
if (U_FAILURE(status)) {
return appendTo;
}
int32_t offsets[2];
UnicodeString patternBody = sf.getTextWithNoArguments(offsets, 2);
UnicodeString fullPattern; // for saving the pattern in fDateFormat
fDateFormat->toPattern(fullPattern); // save current pattern, restore later
fDateFormat->applyPattern(*fTimePattern);
}
FieldPosition otherPos;
otherPos.setField(pos.getField());
UnicodeString earlierDate;
fDateFormat->format(fromCalendar, earlierDate, pos);
UnicodeString laterDate;
fDateFormat->format(toCalendar, laterDate, otherPos);
UnicodeString fallbackPattern;
fInfo->getFallbackIntervalPattern(fallbackPattern);
adjustPosition(fallbackPattern, earlierDate, pos, laterDate, otherPos, pos);
UnicodeString fallbackRange;
SimpleFormatter(fallbackPattern, 2, 2, status).
format(earlierDate, laterDate, fallbackRange, status);
if ( U_SUCCESS(status) && formatDatePlusTimeRange ) {
// fallbackRange has just the time range, need to format the date part and combine that
fDateFormat->applyPattern(*fDatePattern);
UnicodeString datePortion;
otherPos.setBeginIndex(0);
otherPos.setEndIndex(0);
fDateFormat->format(fromCalendar, datePortion, otherPos);
adjustPosition(*fDateTimeFormat, fallbackRange, pos, datePortion, otherPos, pos);
const UnicodeString *values[2] = {
&fallbackRange, // {0} is time range
&datePortion, // {1} is single date portion
};
SimpleFormatter(*fDateTimeFormat, 2, 2, status).
formatAndReplace(values, 2, fallbackRange, NULL, 0, status);
}
if ( U_SUCCESS(status) ) {
appendTo.append(fallbackRange);
}
if (formatDatePlusTimeRange) {
// {0} is time range
// {1} is single date portion
// TODO(ICU-20406): Use SimpleFormatter Iterator interface when available.
if (offsets[0] < offsets[1]) {
appendTo.append(patternBody.tempSubStringBetween(0, offsets[0]));
fDateFormat->applyPattern(*fTimePattern);
fallbackFormatRange(fromCalendar, toCalendar, appendTo, firstIndex, fphandler, status);
appendTo.append(patternBody.tempSubStringBetween(offsets[0], offsets[1]));
fDateFormat->applyPattern(*fDatePattern);
fDateFormat->_format(fromCalendar, appendTo, fphandler, status);
appendTo.append(patternBody.tempSubStringBetween(offsets[1]));
} else {
appendTo.append(patternBody.tempSubStringBetween(0, offsets[1]));
fDateFormat->applyPattern(*fDatePattern);
fDateFormat->_format(fromCalendar, appendTo, fphandler, status);
appendTo.append(patternBody.tempSubStringBetween(offsets[1], offsets[0]));
fDateFormat->applyPattern(*fTimePattern);
fallbackFormatRange(fromCalendar, toCalendar, appendTo, firstIndex, fphandler, status);
appendTo.append(patternBody.tempSubStringBetween(offsets[0]));
}
// restore full pattern
fDateFormat->applyPattern(fullPattern);
} else {
fallbackFormatRange(fromCalendar, toCalendar, appendTo, firstIndex, fphandler, status);
}
return appendTo;
}
@ -1552,6 +1653,7 @@ DateIntervalFormat::fgCalendarFieldToPatternLetter[] =
};
U_NAMESPACE_END
#endif

View file

@ -44,6 +44,24 @@ public:
FieldPositionIteratorHandler getHandler(UErrorCode& status);
void appendString(UnicodeString string, UErrorCode& status);
/**
* Computes the spans for duplicated values.
* For example, if the string has fields:
*
* ...aa..[b.cc]..d.[bb.e.c]..a..
*
* then the spans will be the bracketed regions.
*
* Assumes that the currently known fields are sorted
* and all in the same category.
*/
void addOverlapSpans(UFieldCategory spanCategory, int8_t firstIndex, UErrorCode& status);
/**
* Sorts the fields: start index first, length second.
*/
void sort();
private:
UnicodeString fString;
UVector32 fFields;

View file

@ -10,6 +10,7 @@
// better dependency modularization.
#include "formattedval_impl.h"
#include "putilimp.h"
U_NAMESPACE_BEGIN
@ -82,6 +83,94 @@ void FormattedValueFieldPositionIteratorImpl::appendString(
}
void FormattedValueFieldPositionIteratorImpl::addOverlapSpans(
UFieldCategory spanCategory,
int8_t firstIndex,
UErrorCode& status) {
// In order to avoid fancy data structures, this is an O(N^2) algorithm,
// which should be fine for all real-life applications of this function.
int32_t s1a = INT32_MAX;
int32_t s1b = 0;
int32_t s2a = INT32_MAX;
int32_t s2b = 0;
int32_t numFields = fFields.size() / 4;
for (int32_t i = 0; i<numFields; i++) {
int32_t field1 = fFields.elementAti(i * 4 + 1);
for (int32_t j = i + 1; j<numFields; j++) {
int32_t field2 = fFields.elementAti(j * 4 + 1);
if (field1 != field2) {
continue;
}
// Found a duplicate
s1a = uprv_min(s1a, fFields.elementAti(i * 4 + 2));
s1b = uprv_max(s1b, fFields.elementAti(i * 4 + 3));
s2a = uprv_min(s2a, fFields.elementAti(j * 4 + 2));
s2b = uprv_max(s2b, fFields.elementAti(j * 4 + 3));
break;
}
}
if (s1a != INT32_MAX) {
// Success: add the two span fields
fFields.addElement(spanCategory, status);
fFields.addElement(firstIndex, status);
fFields.addElement(s1a, status);
fFields.addElement(s1b, status);
fFields.addElement(spanCategory, status);
fFields.addElement(1 - firstIndex, status);
fFields.addElement(s2a, status);
fFields.addElement(s2b, status);
}
}
void FormattedValueFieldPositionIteratorImpl::sort() {
// Use bubble sort, O(N^2) but easy and no fancy data structures.
int32_t numFields = fFields.size() / 4;
while (true) {
bool isSorted = true;
for (int32_t i=0; i<numFields-1; i++) {
int32_t categ1 = fFields.elementAti(i*4 + 0);
int32_t field1 = fFields.elementAti(i*4 + 1);
int32_t start1 = fFields.elementAti(i*4 + 2);
int32_t limit1 = fFields.elementAti(i*4 + 3);
int32_t categ2 = fFields.elementAti(i*4 + 4);
int32_t field2 = fFields.elementAti(i*4 + 5);
int32_t start2 = fFields.elementAti(i*4 + 6);
int32_t limit2 = fFields.elementAti(i*4 + 7);
int64_t comparison = 0;
if (start1 != start2) {
// Higher start index -> higher rank
comparison = start2 - start1;
} else if (limit1 != limit2) {
// Higher length (end index) -> lower rank
comparison = limit1 - limit2;
} else if (categ1 != categ2) {
// Higher field category -> lower rank
comparison = categ1 - categ2;
} else if (field1 != field2) {
// Higher field -> higher rank
comparison = field2 - field1;
}
if (comparison < 0) {
// Perform a swap
isSorted = false;
fFields.setElementAt(categ2, i*4 + 0);
fFields.setElementAt(field2, i*4 + 1);
fFields.setElementAt(start2, i*4 + 2);
fFields.setElementAt(limit2, i*4 + 3);
fFields.setElementAt(categ1, i*4 + 4);
fFields.setElementAt(field1, i*4 + 5);
fFields.setElementAt(start1, i*4 + 6);
fFields.setElementAt(limit1, i*4 + 7);
}
}
if (isSorted) {
break;
}
}
}
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -38,7 +38,8 @@ FieldPositionOnlyHandler::~FieldPositionOnlyHandler() {
void
FieldPositionOnlyHandler::addAttribute(int32_t id, int32_t start, int32_t limit) {
if (pos.getField() == id) {
if (pos.getField() == id && (!acceptFirstOnly || !seenFirst)) {
seenFirst = TRUE;
pos.setBeginIndex(start + fShift);
pos.setEndIndex(limit + fShift);
}
@ -57,6 +58,10 @@ FieldPositionOnlyHandler::isRecording(void) const {
return pos.getField() != FieldPosition::DONT_CARE;
}
void FieldPositionOnlyHandler::setAcceptFirstOnly(UBool acceptFirstOnly) {
this->acceptFirstOnly = acceptFirstOnly;
}
// utility subclass FieldPositionIteratorHandler

View file

@ -41,6 +41,8 @@ class U_I18N_API FieldPositionHandler: public UMemory {
class FieldPositionOnlyHandler : public FieldPositionHandler {
FieldPosition& pos;
UBool acceptFirstOnly = FALSE;
UBool seenFirst = FALSE;
public:
FieldPositionOnlyHandler(FieldPosition& pos);
@ -49,6 +51,13 @@ class FieldPositionOnlyHandler : public FieldPositionHandler {
void addAttribute(int32_t id, int32_t start, int32_t limit) U_OVERRIDE;
void shiftLast(int32_t delta) U_OVERRIDE;
UBool isRecording(void) const U_OVERRIDE;
/**
* Enable this option to lock in the FieldPosition value after seeing the
* first occurrence of the field. The default behavior is to take the last
* occurrence.
*/
void setAcceptFirstOnly(UBool acceptFirstOnly);
};

View file

@ -18,10 +18,21 @@
#include "unicode/timezone.h"
#include "unicode/locid.h"
#include "unicode/unistr.h"
#include "formattedval_impl.h"
U_NAMESPACE_USE
// Magic number: FDIV in ASCII
UPRV_FORMATTED_VALUE_CAPI_AUTO_IMPL(
FormattedDateInterval,
UFormattedDateInterval,
UFormattedDateIntervalImpl,
UFormattedDateIntervalApiHelper,
udtitvfmt,
0x46444956)
U_CAPI UDateIntervalFormat* U_EXPORT2
udtitvfmt_open(const char* locale,
const UChar* skeleton,
@ -105,4 +116,21 @@ udtitvfmt_format(const UDateIntervalFormat* formatter,
}
U_DRAFT void U_EXPORT2
udtitvfmt_formatToResult(
const UDateIntervalFormat* formatter,
UFormattedDateInterval* result,
UDate fromDate,
UDate toDate,
UErrorCode* status) {
if (U_FAILURE(*status)) {
return;
}
auto* resultImpl = UFormattedDateIntervalApiHelper::validate(result, *status);
DateInterval interval = DateInterval(fromDate,toDate);
resultImpl->fImpl = reinterpret_cast<const DateIntervalFormat*>(formatter)
->formatToValue(interval, *status);
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -28,10 +28,85 @@
#include "unicode/dtintrv.h"
#include "unicode/dtitvinf.h"
#include "unicode/dtptngen.h"
#include "unicode/formattedvalue.h"
U_NAMESPACE_BEGIN
class FormattedDateIntervalData;
class DateIntervalFormat;
#ifndef U_HIDE_DRAFT_API
/**
* An immutable class containing the result of a date interval formatting operation.
*
* Not intended for public subclassing.
*
* When calling nextPosition():
* The fields are returned from left to right. The special field category
* UFIELD_CATEGORY_DATE_INTERVAL_SPAN is used to indicate which datetime
* primitives came from which arguments: 0 means fromCalendar, and 1 means
* toCalendar. The span category will always occur before the
* corresponding fields in UFIELD_CATEGORY_DATE
* in the nextPosition() iterator.
*
* @draft ICU 64
*/
class U_I18N_API FormattedDateInterval : public UMemory, public FormattedValue {
public:
/**
* Default constructor; makes an empty FormattedDateInterval.
* @draft ICU 64
*/
FormattedDateInterval() : fData(nullptr), fErrorCode(U_INVALID_STATE_ERROR) {};
/**
* Move constructor: Leaves the source FormattedDateInterval in an undefined state.
* @draft ICU 64
*/
FormattedDateInterval(FormattedDateInterval&& src) U_NOEXCEPT;
/**
* Destruct an instance of FormattedDateInterval.
* @draft ICU 64
*/
virtual ~FormattedDateInterval() U_OVERRIDE;
/** Copying not supported; use move constructor instead. */
FormattedDateInterval(const FormattedDateInterval&) = delete;
/** Copying not supported; use move assignment instead. */
FormattedDateInterval& operator=(const FormattedDateInterval&) = delete;
/**
* Move assignment: Leaves the source FormattedDateInterval in an undefined state.
* @draft ICU 64
*/
FormattedDateInterval& operator=(FormattedDateInterval&& 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:
FormattedDateIntervalData *fData;
UErrorCode fErrorCode;
explicit FormattedDateInterval(FormattedDateIntervalData *results)
: fData(results), fErrorCode(U_ZERO_ERROR) {};
explicit FormattedDateInterval(UErrorCode errorCode)
: fData(nullptr), fErrorCode(errorCode) {};
friend class DateIntervalFormat;
};
#endif /* U_HIDE_DRAFT_API */
/**
* DateIntervalFormat is a class for formatting and parsing date
@ -218,7 +293,6 @@ U_NAMESPACE_BEGIN
* \endcode
* </pre>
*/
class U_I18N_API DateIntervalFormat : public Format {
public:
@ -425,6 +499,21 @@ public:
FieldPosition& fieldPosition,
UErrorCode& status) const ;
#ifndef U_HIDE_DRAFT_API
/**
* Format a DateInterval to produce a FormattedDateInterval.
*
* The FormattedDateInterval exposes field information about the formatted string.
*
* @param dtInterval DateInterval to be formatted.
* @param status Set if an error occurs.
* @return A FormattedDateInterval containing the format result.
* @draft ICU 64
*/
FormattedDateInterval formatToValue(
const DateInterval& dtInterval,
UErrorCode& status) const;
#endif /* U_HIDE_DRAFT_API */
/**
* Format 2 Calendars to produce a string.
@ -455,6 +544,29 @@ public:
FieldPosition& fieldPosition,
UErrorCode& status) const ;
#ifndef U_HIDE_DRAFT_API
/**
* Format 2 Calendars to produce a FormattedDateInterval.
*
* The FormattedDateInterval exposes field information about the formatted string.
*
* Note: "fromCalendar" and "toCalendar" are not const,
* since calendar is not const in SimpleDateFormat::format(Calendar&),
*
* @param fromCalendar calendar set to the from date in date interval
* to be formatted into date interval string
* @param toCalendar calendar set to the to date in date interval
* to be formatted into date interval string
* @param status Set if an error occurs.
* @return A FormattedDateInterval containing the format result.
* @draft ICU 64
*/
FormattedDateInterval formatToValue(
Calendar& fromCalendar,
Calendar& toCalendar,
UErrorCode& status) const;
#endif /* U_HIDE_DRAFT_API */
/**
* Date interval parsing is not supported. Please do not use.
* <P>
@ -664,28 +776,14 @@ private:
* Below are for generating interval patterns local to the formatter
*/
/**
* Provide an updated FieldPosition posResult based on two formats,
* the FieldPosition values for each of them, and the pattern used
* to combine them. The idea is for posResult to indicate the first
* instance (if any) of the specified field in the combined result,
* with correct offsets.
*
* @param combiningPattern Pattern used to combine pat0 and pat1
* @param pat0 Formatted date/time value to replace {0}
* @param pos0 FieldPosition within pat0
* @param pat1 Formatted date/time value to replace {1}
* @param pos1 FieldPosition within pat1
* @param posResult FieldPosition to be set to the correct
* position of the first field instance when
* pat0 and pat1 are combined using combiningPattern
*/
static void
adjustPosition(UnicodeString& combiningPattern, // has {0} and {1} in it
UnicodeString& pat0, FieldPosition& pos0, // pattern and pos corresponding to {0}
UnicodeString& pat1, FieldPosition& pos1, // pattern and pos corresponding to {1}
FieldPosition& posResult);
/** Like fallbackFormat, but only formats the range part of the fallback. */
void fallbackFormatRange(
Calendar& fromCalendar,
Calendar& toCalendar,
UnicodeString& appendTo,
int8_t& firstIndex,
FieldPositionHandler& fphandler,
UErrorCode& status) const;
/**
* Format 2 Calendars using fall-back interval pattern
@ -703,8 +801,8 @@ private:
* (any difference is in ampm/hours or below)
* @param appendTo Output parameter to receive result.
* Result is appended to existing contents.
* @param pos On input: an alignment field, if desired.
* On output: the offsets of the alignment field.
* @param firstIndex See formatImpl for more information.
* @param fphandler See formatImpl for more information.
* @param status output param set to success/failure code on exit
* @return Reference to 'appendTo' parameter.
* @internal (private)
@ -713,7 +811,8 @@ private:
Calendar& toCalendar,
UBool fromToOnSameDay,
UnicodeString& appendTo,
FieldPosition& pos,
int8_t& firstIndex,
FieldPositionHandler& fphandler,
UErrorCode& status) const;
@ -977,11 +1076,11 @@ private:
* to be formatted into date interval string
* @param appendTo Output parameter to receive result.
* Result is appended to existing contents.
* @param fieldPosition On input: an alignment field, if desired.
* On output: the offsets of the alignment field.
* There may be multiple instances of a given field type
* in an interval format; in this case the fieldPosition
* offsets refer to the first instance.
* @param firstIndex 0 if the first output date is fromCalendar;
* 1 if it corresponds to toCalendar;
* -1 if there is only one date printed.
* @param fphandler Handler for field position information.
* The fields will be from the UDateFormatField enum.
* @param status Output param filled with success/failure status.
* Caller needs to make sure it is SUCCESS
* at the function entrance
@ -991,9 +1090,17 @@ private:
UnicodeString& formatImpl(Calendar& fromCalendar,
Calendar& toCalendar,
UnicodeString& appendTo,
FieldPosition& fieldPosition,
int8_t& firstIndex,
FieldPositionHandler& fphandler,
UErrorCode& status) const ;
/** Version of formatImpl for DateInterval. */
UnicodeString& formatIntervalImpl(const DateInterval& dtInterval,
UnicodeString& appendTo,
int8_t& firstIndex,
FieldPositionHandler& fphandler,
UErrorCode& status) const;
// from calendar field to pattern letter
static const char16_t fgCalendarFieldToPatternLetter[];

View file

@ -149,7 +149,6 @@ U_NAMESPACE_BEGIN
* calendar; non-Gregorian calendars are supported from ICU 4.4.1.
* @stable ICU 4.0
**/
class U_I18N_API DateIntervalInfo U_FINAL : public UObject {
public:
/**

View file

@ -49,6 +49,7 @@ class FieldPositionHandler;
class TimeZoneFormat;
class SharedNumberFormat;
class SimpleDateFormatMutableNFs;
class DateIntervalFormat;
namespace number {
class LocalizedNumberFormatter;
@ -1217,6 +1218,7 @@ public:
private:
friend class DateFormat;
friend class DateIntervalFormat;
void initializeDefaultCentury(void);

View file

@ -16,6 +16,7 @@
#include "unicode/umisc.h"
#include "unicode/localpointer.h"
#include "unicode/uformattedvalue.h"
/**
* \file
@ -81,6 +82,15 @@
struct UDateIntervalFormat;
typedef struct UDateIntervalFormat UDateIntervalFormat; /**< C typedef for struct UDateIntervalFormat. @stable ICU 4.8 */
#ifndef U_HIDE_DRAFT_API
struct UFormattedDateInterval;
/**
* Opaque struct to contain the results of a UDateIntervalFormat operation.
* @draft ICU 64
*/
typedef struct UFormattedDateInterval UFormattedDateInterval;
#endif /* U_HIDE_DRAFT_API */
/**
* Open a new UDateIntervalFormat object using the predefined rules for a
* given locale plus a specified skeleton.
@ -123,6 +133,55 @@ U_STABLE void U_EXPORT2
udtitvfmt_close(UDateIntervalFormat *formatter);
#ifndef U_HIDE_DRAFT_API
/**
* Creates an object to hold the result of a UDateIntervalFormat
* 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_CAPI UFormattedDateInterval* U_EXPORT2
udtitvfmt_openResult(UErrorCode* ec);
/**
* Returns a representation of a UFormattedDateInterval as a UFormattedValue,
* which can be subsequently passed to any API requiring that type.
*
* The returned object is owned by the UFormattedDateInterval and is valid
* only as long as the UFormattedDateInterval is present and unchanged in memory.
*
* You can think of this method as a cast between types.
*
* When calling ufmtval_nextPosition():
* The fields are returned from left to right. The special field category
* UFIELD_CATEGORY_DATE_INTERVAL_SPAN is used to indicate which datetime
* primitives came from which arguments: 0 means fromCalendar, and 1 means
* toCalendar. The span category will always occur before the
* corresponding fields in UFIELD_CATEGORY_DATE
* in the ufmtval_nextPosition() iterator.
*
* @param uresult 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_CAPI const UFormattedValue* U_EXPORT2
udtitvfmt_resultAsValue(const UFormattedDateInterval* uresult, UErrorCode* ec);
/**
* Releases the UFormattedDateInterval created by udtitvfmt_openResult().
*
* @param uresult The object to release.
* @draft ICU 64
*/
U_CAPI void U_EXPORT2
udtitvfmt_closeResult(UFormattedDateInterval* uresult);
#endif /* U_HIDE_DRAFT_API */
#if U_SHOW_CPLUSPLUS_API
U_NAMESPACE_BEGIN
@ -138,6 +197,19 @@ U_NAMESPACE_BEGIN
*/
U_DEFINE_LOCAL_OPEN_POINTER(LocalUDateIntervalFormatPointer, UDateIntervalFormat, udtitvfmt_close);
#ifndef U_HIDE_DRAFT_API
/**
* \class LocalUFormattedDateIntervalPointer
* "Smart pointer" class, closes a UFormattedDateInterval via udtitvfmt_close().
* For most methods see the LocalPointerBase base class.
*
* @see LocalPointerBase
* @see LocalPointer
* @draft ICU 64
*/
U_DEFINE_LOCAL_OPEN_POINTER(LocalUFormattedDateIntervalPointer, UFormattedDateInterval, udtitvfmt_closeResult);
#endif /* U_HIDE_DRAFT_API */
U_NAMESPACE_END
#endif
@ -181,6 +253,34 @@ udtitvfmt_format(const UDateIntervalFormat* formatter,
UFieldPosition* position,
UErrorCode* status);
#ifndef U_HIDE_DRAFT_API
/**
* Formats a date/time range using the conventions established for the
* UDateIntervalFormat object.
* @param formatter
* The UDateIntervalFormat object specifying the format conventions.
* @param result
* The UFormattedDateInterval to contain the result of the
* formatting operation.
* @param fromDate
* The starting point of the range.
* @param toDate
* The ending point of the range.
* @param status
* A pointer to a UErrorCode to receive any errors.
* @draft ICU 64
*/
U_DRAFT void U_EXPORT2
udtitvfmt_formatToResult(
const UDateIntervalFormat* formatter,
UFormattedDateInterval* result,
UDate fromDate,
UDate toDate,
UErrorCode* status);
#endif /* U_HIDE_DRAFT_API */
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif

View file

@ -67,11 +67,25 @@ typedef enum UFieldCategory {
*/
UFIELD_CATEGORY_RELATIVE_DATETIME,
/**
* Reserved for possible future fields in UDateIntervalFormatField.
*
* @internal
*/
UFIELD_CATEGORY_DATE_INTERVAL,
#ifndef U_HIDE_INTERNAL_API
/** @internal */
UFIELD_CATEGORY_COUNT
UFIELD_CATEGORY_COUNT,
#endif
/**
* Category for spans in a date interval.
*
* @draft ICU 64
*/
UFIELD_CATEGORY_DATE_INTERVAL_SPAN = 0x1000 + UFIELD_CATEGORY_DATE_INTERVAL,
} UFieldCategory;

View file

@ -16,9 +16,11 @@
#include "unicode/ustring.h"
#include "cintltst.h"
#include "cmemory.h"
#include "cformtst.h"
static void TestDateIntervalFormat(void);
static void TestFPos_SkelWithSeconds(void);
static void TestFormatToResult(void);
void addDateIntervalFormatTest(TestNode** root);
@ -28,6 +30,7 @@ void addDateIntervalFormatTest(TestNode** root)
{
TESTCASE(TestDateIntervalFormat);
TESTCASE(TestFPos_SkelWithSeconds);
TESTCASE(TestFormatToResult);
}
static const char tzUSPacific[] = "US/Pacific";
@ -316,4 +319,65 @@ static void TestFPos_SkelWithSeconds()
}
}
static void TestFormatToResult() {
UErrorCode ec = U_ZERO_ERROR;
UDateIntervalFormat* fmt = udtitvfmt_open("de", u"dMMMMyHHmm", -1, zoneGMT, -1, &ec);
UFormattedDateInterval* fdi = udtitvfmt_openResult(&ec);
assertSuccess("Opening", &ec);
{
const char* message = "Field position test 1";
const UChar* expectedString = u"27. September 2010, 15:00 2. März 2011, 18:30";
udtitvfmt_formatToResult(fmt, fdi, Date201009270800, Date201103021030, &ec);
assertSuccess("Formatting", &ec);
static const UFieldPositionWithCategory expectedFieldPositions[] = {
// category, field, begin index, end index
{UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 0, 25},
{UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 0, 2},
{UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 4, 13},
{UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 14, 18},
{UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 20, 22},
{UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 23, 25},
{UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 28, 47},
{UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 28, 29},
{UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 31, 35},
{UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 36, 40},
{UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 42, 44},
{UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 45, 47}};
checkMixedFormattedValue(
message,
udtitvfmt_resultAsValue(fdi, &ec),
expectedString,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
{
const char* message = "Field position test 1";
const UChar* expectedString = u"27. September 2010, 15:0022:00 Uhr";
udtitvfmt_formatToResult(fmt, fdi, Date201009270800, Date201009270800 + 7*_HOUR, &ec);
assertSuccess("Formatting", &ec);
static const UFieldPositionWithCategory expectedFieldPositions[] = {
// category, field, begin index, end index
{UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 0, 2},
{UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 4, 13},
{UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 14, 18},
{UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 20, 25},
{UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 20, 22},
{UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 23, 25},
{UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 26, 31},
{UFIELD_CATEGORY_DATE, UDAT_HOUR_OF_DAY0_FIELD, 26, 28},
{UFIELD_CATEGORY_DATE, UDAT_MINUTE_FIELD, 29, 31},
{UFIELD_CATEGORY_DATE, UDAT_AM_PM_FIELD, 32, 35}};
checkMixedFormattedValue(
message,
udtitvfmt_resultAsValue(fdi, &ec),
expectedString,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
udtitvfmt_close(fmt);
udtitvfmt_closeResult(fdi);
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -55,6 +55,7 @@ void DateIntervalFormatTest::runIndexedTest( int32_t index, UBool exec, const ch
TESTCASE(7, testTicket11985);
TESTCASE(8, testTicket11669);
TESTCASE(9, testTicket12065);
TESTCASE(10, testFormattedDateInterval);
default: name = ""; break;
}
}
@ -1620,4 +1621,67 @@ void DateIntervalFormatTest::testTicket12065() {
}
void DateIntervalFormatTest::testFormattedDateInterval() {
IcuTestErrorCode status(*this, "testFormattedDateInterval");
LocalPointer<DateIntervalFormat> fmt(DateIntervalFormat::createInstance(u"dMMMMy", "en-US", status), status);
{
const char16_t* message = u"FormattedDateInterval test 1";
const char16_t* expectedString = u"July 20 \u2013 25, 2018";
LocalPointer<Calendar> input1(Calendar::createInstance("en-GB", status));
LocalPointer<Calendar> input2(Calendar::createInstance("en-GB", status));
input1->set(2018, 6, 20);
input2->set(2018, 6, 25);
FormattedDateInterval result = fmt->formatToValue(*input1, *input2, status);
static const UFieldPositionWithCategory expectedFieldPositions[] = {
// field, begin index, end index
{UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 0, 4},
{UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 5, 7},
{UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 5, 7},
{UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 10, 12},
{UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 10, 12},
{UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 14, 18}};
checkMixedFormattedValue(
message,
result,
expectedString,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
// To test the fallback pattern behavior, make a custom DateIntervalInfo.
DateIntervalInfo dtitvinf(status);
dtitvinf.setFallbackIntervalPattern("<< {1} --- {0} >>", status);
fmt.adoptInsteadAndCheckErrorCode(
DateIntervalFormat::createInstance(u"dMMMMy", "en-US", dtitvinf, status),
status);
{
const char16_t* message = u"FormattedDateInterval with fallback format test 1";
const char16_t* expectedString = u"<< July 25, 2018 --- July 20, 2018 >>";
LocalPointer<Calendar> input1(Calendar::createInstance("en-GB", status));
LocalPointer<Calendar> input2(Calendar::createInstance("en-GB", status));
input1->set(2018, 6, 20);
input2->set(2018, 6, 25);
FormattedDateInterval result = fmt->formatToValue(*input1, *input2, status);
static const UFieldPositionWithCategory expectedFieldPositions[] = {
// field, begin index, end index
{UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 1, 3, 16},
{UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 3, 7},
{UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 8, 10},
{UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 12, 16},
{UFIELD_CATEGORY_DATE_INTERVAL_SPAN, 0, 21, 34},
{UFIELD_CATEGORY_DATE, UDAT_MONTH_FIELD, 21, 25},
{UFIELD_CATEGORY_DATE, UDAT_DATE_FIELD, 26, 28},
{UFIELD_CATEGORY_DATE, UDAT_YEAR_FIELD, 30, 34}};
checkMixedFormattedValue(
message,
result,
expectedString,
expectedFieldPositions,
UPRV_LENGTHOF(expectedFieldPositions));
}
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -15,11 +15,12 @@
#if !UCONFIG_NO_FORMATTING
#include "intltest.h"
#include "itformat.h"
/**
* Test basic functionality of various API functions
**/
class DateIntervalFormatTest: public IntlTest {
class DateIntervalFormatTest: public IntlTestWithFieldPosition {
void runIndexedTest( int32_t index, UBool exec, const char* &name, char* par = NULL );
public:
@ -63,6 +64,8 @@ public:
void testTicket12065();
void testFormattedDateInterval();
private:
/**
* Test formatting against expected result

View file

@ -329,10 +329,21 @@ void SimpleFormatterTest::TestBadArguments() {
}
void SimpleFormatterTest::TestTextWithNoArguments() {
UErrorCode status = U_ZERO_ERROR;
IcuTestErrorCode status(*this, "TestTextWithNoArguments");
SimpleFormatter fmt("{0} has no {1} arguments.", status);
assertEquals(
"", " has no arguments.", fmt.getTextWithNoArguments());
assertEquals("String output 1",
" has no arguments.", fmt.getTextWithNoArguments());
// Test offset positions
int32_t offsets[3];
assertEquals("String output 2",
u" has no arguments.", fmt.getTextWithNoArguments(offsets, 3));
assertEquals("Offset at 0",
0, offsets[0]);
assertEquals("Offset at 1",
8, offsets[1]);
assertEquals("Offset at 2",
-1, offsets[2]);
}
void SimpleFormatterTest::TestFormatReplaceNoOptimization() {