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
diff --git a/icu4c/source/common/simpleformatter.cpp b/icu4c/source/common/simpleformatter.cpp
index f866e0a..76d8f54 100644
--- a/icu4c/source/common/simpleformatter.cpp
+++ b/icu4c/source/common/simpleformatter.cpp
@@ -246,15 +246,24 @@
}
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;
diff --git a/icu4c/source/common/unicode/simpleformatter.h b/icu4c/source/common/unicode/simpleformatter.h
index 850949c..3f7d93d 100644
--- a/icu4c/source/common/unicode/simpleformatter.h
+++ b/icu4c/source/common/unicode/simpleformatter.h
@@ -265,9 +265,38 @@
* @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 @@
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,
diff --git a/icu4c/source/i18n/dtitvfmt.cpp b/icu4c/source/i18n/dtitvfmt.cpp
index d952cbf..1de7652 100644
--- a/icu4c/source/i18n/dtitvfmt.cpp
+++ b/icu4c/source/i18n/dtitvfmt.cpp
@@ -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 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 @@
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 @@
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 @@
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 @@
/* 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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
};
+
U_NAMESPACE_END
#endif
diff --git a/icu4c/source/i18n/formattedval_impl.h b/icu4c/source/i18n/formattedval_impl.h
index b08a3e6..613ca27 100644
--- a/icu4c/source/i18n/formattedval_impl.h
+++ b/icu4c/source/i18n/formattedval_impl.h
@@ -44,6 +44,24 @@
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;
diff --git a/icu4c/source/i18n/formattedval_iterimpl.cpp b/icu4c/source/i18n/formattedval_iterimpl.cpp
index 35ce55c..0b148bd 100644
--- a/icu4c/source/i18n/formattedval_iterimpl.cpp
+++ b/icu4c/source/i18n/formattedval_iterimpl.cpp
@@ -10,6 +10,7 @@
// better dependency modularization.
#include "formattedval_impl.h"
+#include "putilimp.h"
U_NAMESPACE_BEGIN
@@ -82,6 +83,94 @@
}
+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 */
diff --git a/icu4c/source/i18n/fphdlimp.cpp b/icu4c/source/i18n/fphdlimp.cpp
index 68bc105..f51bf4b 100644
--- a/icu4c/source/i18n/fphdlimp.cpp
+++ b/icu4c/source/i18n/fphdlimp.cpp
@@ -38,7 +38,8 @@
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 @@
return pos.getField() != FieldPosition::DONT_CARE;
}
+void FieldPositionOnlyHandler::setAcceptFirstOnly(UBool acceptFirstOnly) {
+ this->acceptFirstOnly = acceptFirstOnly;
+}
+
// utility subclass FieldPositionIteratorHandler
diff --git a/icu4c/source/i18n/fphdlimp.h b/icu4c/source/i18n/fphdlimp.h
index 3cec1e4..d4a7685 100644
--- a/icu4c/source/i18n/fphdlimp.h
+++ b/icu4c/source/i18n/fphdlimp.h
@@ -41,6 +41,8 @@
class FieldPositionOnlyHandler : public FieldPositionHandler {
FieldPosition& pos;
+ UBool acceptFirstOnly = FALSE;
+ UBool seenFirst = FALSE;
public:
FieldPositionOnlyHandler(FieldPosition& pos);
@@ -49,6 +51,13 @@
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);
};
diff --git a/icu4c/source/i18n/udateintervalformat.cpp b/icu4c/source/i18n/udateintervalformat.cpp
index 44ba6b9..d9eaae4 100644
--- a/icu4c/source/i18n/udateintervalformat.cpp
+++ b/icu4c/source/i18n/udateintervalformat.cpp
@@ -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 @@
}
+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 */
diff --git a/icu4c/source/i18n/unicode/dtitvfmt.h b/icu4c/source/i18n/unicode/dtitvfmt.h
index 5eaa559..e2f4685 100644
--- a/icu4c/source/i18n/unicode/dtitvfmt.h
+++ b/icu4c/source/i18n/unicode/dtitvfmt.h
@@ -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 @@
* \endcode
* </pre>
*/
-
class U_I18N_API DateIntervalFormat : public Format {
public:
@@ -425,6 +499,21 @@
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 @@
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 @@
* 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 @@
* (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 @@
Calendar& toCalendar,
UBool fromToOnSameDay,
UnicodeString& appendTo,
- FieldPosition& pos,
+ int8_t& firstIndex,
+ FieldPositionHandler& fphandler,
UErrorCode& status) const;
@@ -977,11 +1076,11 @@
* 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 @@
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[];
diff --git a/icu4c/source/i18n/unicode/dtitvinf.h b/icu4c/source/i18n/unicode/dtitvinf.h
index 726e34a..a5b7f8f 100644
--- a/icu4c/source/i18n/unicode/dtitvinf.h
+++ b/icu4c/source/i18n/unicode/dtitvinf.h
@@ -149,7 +149,6 @@
* 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:
/**
diff --git a/icu4c/source/i18n/unicode/smpdtfmt.h b/icu4c/source/i18n/unicode/smpdtfmt.h
index 929c1b4..f1e7c96 100644
--- a/icu4c/source/i18n/unicode/smpdtfmt.h
+++ b/icu4c/source/i18n/unicode/smpdtfmt.h
@@ -49,6 +49,7 @@
class TimeZoneFormat;
class SharedNumberFormat;
class SimpleDateFormatMutableNFs;
+class DateIntervalFormat;
namespace number {
class LocalizedNumberFormatter;
@@ -1217,6 +1218,7 @@
private:
friend class DateFormat;
+ friend class DateIntervalFormat;
void initializeDefaultCentury(void);
diff --git a/icu4c/source/i18n/unicode/udateintervalformat.h b/icu4c/source/i18n/unicode/udateintervalformat.h
index 9300ddc..b42223a 100644
--- a/icu4c/source/i18n/unicode/udateintervalformat.h
+++ b/icu4c/source/i18n/unicode/udateintervalformat.h
@@ -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 @@
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_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 @@
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
diff --git a/icu4c/source/i18n/unicode/uformattedvalue.h b/icu4c/source/i18n/unicode/uformattedvalue.h
index 4dff30f..d9600d9 100644
--- a/icu4c/source/i18n/unicode/uformattedvalue.h
+++ b/icu4c/source/i18n/unicode/uformattedvalue.h
@@ -67,11 +67,25 @@
*/
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;
diff --git a/icu4c/source/test/cintltst/cdateintervalformattest.c b/icu4c/source/test/cintltst/cdateintervalformattest.c
index 3c3326c..d8a8066 100644
--- a/icu4c/source/test/cintltst/cdateintervalformattest.c
+++ b/icu4c/source/test/cintltst/cdateintervalformattest.c
@@ -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 @@
{
TESTCASE(TestDateIntervalFormat);
TESTCASE(TestFPos_SkelWithSeconds);
+ TESTCASE(TestFormatToResult);
}
static const char tzUSPacific[] = "US/Pacific";
@@ -316,4 +319,65 @@
}
}
+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:00–22: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 */
diff --git a/icu4c/source/test/intltest/dtifmtts.cpp b/icu4c/source/test/intltest/dtifmtts.cpp
index c5b9631..745aa03 100644
--- a/icu4c/source/test/intltest/dtifmtts.cpp
+++ b/icu4c/source/test/intltest/dtifmtts.cpp
@@ -55,6 +55,7 @@
TESTCASE(7, testTicket11985);
TESTCASE(8, testTicket11669);
TESTCASE(9, testTicket12065);
+ TESTCASE(10, testFormattedDateInterval);
default: name = ""; break;
}
}
@@ -1620,4 +1621,67 @@
}
+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 */
diff --git a/icu4c/source/test/intltest/dtifmtts.h b/icu4c/source/test/intltest/dtifmtts.h
index b6cc097..9a72d6e 100644
--- a/icu4c/source/test/intltest/dtifmtts.h
+++ b/icu4c/source/test/intltest/dtifmtts.h
@@ -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 @@
void testTicket12065();
+ void testFormattedDateInterval();
+
private:
/**
* Test formatting against expected result
diff --git a/icu4c/source/test/intltest/simpleformattertest.cpp b/icu4c/source/test/intltest/simpleformattertest.cpp
index 3a77768..8c230ff 100644
--- a/icu4c/source/test/intltest/simpleformattertest.cpp
+++ b/icu4c/source/test/intltest/simpleformattertest.cpp
@@ -329,10 +329,21 @@
}
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() {