diff --git a/icu4c/source/common/simpleformatter.cpp b/icu4c/source/common/simpleformatter.cpp index f866e0a1a12..76d8f54efd4 100644 --- a/icu4c/source/common/simpleformatter.cpp +++ b/icu4c/source/common/simpleformatter.cpp @@ -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; diff --git a/icu4c/source/common/unicode/simpleformatter.h b/icu4c/source/common/unicode/simpleformatter.h index 850949caaf5..3f7d93dc094 100644 --- a/icu4c/source/common/unicode/simpleformatter.h +++ b/icu4c/source/common/unicode/simpleformatter.h @@ -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, diff --git a/icu4c/source/i18n/dtitvfmt.cpp b/icu4c/source/i18n/dtitvfmt.cpp index d952cbf509d..1de7652335b 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 @@ -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 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 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 diff --git a/icu4c/source/i18n/formattedval_impl.h b/icu4c/source/i18n/formattedval_impl.h index b08a3e61d4d..613ca27648b 100644 --- a/icu4c/source/i18n/formattedval_impl.h +++ b/icu4c/source/i18n/formattedval_impl.h @@ -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; diff --git a/icu4c/source/i18n/formattedval_iterimpl.cpp b/icu4c/source/i18n/formattedval_iterimpl.cpp index 35ce55c8b6d..0b148bd1bea 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::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 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 68bc1054c0b..f51bf4bae78 100644 --- a/icu4c/source/i18n/fphdlimp.cpp +++ b/icu4c/source/i18n/fphdlimp.cpp @@ -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 diff --git a/icu4c/source/i18n/fphdlimp.h b/icu4c/source/i18n/fphdlimp.h index 3cec1e471f0..d4a76859025 100644 --- a/icu4c/source/i18n/fphdlimp.h +++ b/icu4c/source/i18n/fphdlimp.h @@ -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); }; diff --git a/icu4c/source/i18n/udateintervalformat.cpp b/icu4c/source/i18n/udateintervalformat.cpp index 44ba6b9fb1d..d9eaae4d3e2 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 @@ 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(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 5eaa559d0ea..e2f46850efe 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 @@ U_NAMESPACE_BEGIN * \endcode * */ - 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. *

@@ -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[]; diff --git a/icu4c/source/i18n/unicode/dtitvinf.h b/icu4c/source/i18n/unicode/dtitvinf.h index 726e34a745a..a5b7f8f797f 100644 --- a/icu4c/source/i18n/unicode/dtitvinf.h +++ b/icu4c/source/i18n/unicode/dtitvinf.h @@ -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: /** diff --git a/icu4c/source/i18n/unicode/smpdtfmt.h b/icu4c/source/i18n/unicode/smpdtfmt.h index 929c1b4675b..f1e7c96ee44 100644 --- a/icu4c/source/i18n/unicode/smpdtfmt.h +++ b/icu4c/source/i18n/unicode/smpdtfmt.h @@ -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); diff --git a/icu4c/source/i18n/unicode/udateintervalformat.h b/icu4c/source/i18n/unicode/udateintervalformat.h index 9300ddcf82b..b42223a5db3 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 @@ 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 diff --git a/icu4c/source/i18n/unicode/uformattedvalue.h b/icu4c/source/i18n/unicode/uformattedvalue.h index 4dff30f20d8..d9600d90379 100644 --- a/icu4c/source/i18n/unicode/uformattedvalue.h +++ b/icu4c/source/i18n/unicode/uformattedvalue.h @@ -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; diff --git a/icu4c/source/test/cintltst/cdateintervalformattest.c b/icu4c/source/test/cintltst/cdateintervalformattest.c index 3c3326c7704..d8a8066824a 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 @@ 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: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 c5b96310833..745aa039f5b 100644 --- a/icu4c/source/test/intltest/dtifmtts.cpp +++ b/icu4c/source/test/intltest/dtifmtts.cpp @@ -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 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 input1(Calendar::createInstance("en-GB", status)); + LocalPointer 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 input1(Calendar::createInstance("en-GB", status)); + LocalPointer 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 b6cc0970270..9a72d6ebd1e 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 @@ public: 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 3a777688e79..8c230ff2b01 100644 --- a/icu4c/source/test/intltest/simpleformattertest.cpp +++ b/icu4c/source/test/intltest/simpleformattertest.cpp @@ -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() {