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() {