ICU-13256 Implementing FormattedRelativeDateTime in C, C++, and Java.

- Adds additional logic to NumberStringBuilder.
- Extends logic of number::impl::Field type.
- Adds tests for RBNF support.
- Adds tests from ftang's original PR.
diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in
index d030efa..2d00187 100644
--- a/icu4c/source/i18n/Makefile.in
+++ b/icu4c/source/i18n/Makefile.in
@@ -112,7 +112,7 @@
 numparse_symbols.o numparse_decimal.o numparse_scientific.o numparse_currency.o \
 numparse_affixes.o numparse_compositions.o numparse_validators.o \
 numrange_fluent.o numrange_impl.o \
-erarules.o formattedvalue.o formattedval_iterimpl.o
+erarules.o formattedvalue.o formattedval_iterimpl.o formattedval_sbimpl.o
 
 ## Header files to install
 HEADERS = $(srcdir)/unicode/*.h
diff --git a/icu4c/source/i18n/formattedval_impl.h b/icu4c/source/i18n/formattedval_impl.h
index d3b49ec..b08a3e6 100644
--- a/icu4c/source/i18n/formattedval_impl.h
+++ b/icu4c/source/i18n/formattedval_impl.h
@@ -18,6 +18,7 @@
 #include "fphdlimp.h"
 #include "util.h"
 #include "uvectr32.h"
+#include "number_stringbuilder.h"
 
 U_NAMESPACE_BEGIN
 
@@ -44,12 +45,35 @@
     void appendString(UnicodeString string, UErrorCode& status);
 
 private:
-    // Final data:
     UnicodeString fString;
     UVector32 fFields;
 };
 
 
+class FormattedValueNumberStringBuilderImpl : public UMemory, public FormattedValue {
+public:
+
+    FormattedValueNumberStringBuilderImpl(number::impl::Field numericField);
+
+    virtual ~FormattedValueNumberStringBuilderImpl();
+
+    // Implementation of FormattedValue (const):
+
+    UnicodeString toString(UErrorCode& status) const U_OVERRIDE;
+    UnicodeString toTempString(UErrorCode& status) const U_OVERRIDE;
+    Appendable& appendTo(Appendable& appendable, UErrorCode& status) const U_OVERRIDE;
+    UBool nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const U_OVERRIDE;
+
+    inline number::impl::NumberStringBuilder& getStringRef() {
+        return fString;
+    }
+
+private:
+    number::impl::NumberStringBuilder fString;
+    number::impl::Field fNumericField;
+};
+
+
 // C API Helpers for FormattedValue
 // Magic number as ASCII == "UFV"
 struct UFormattedValueImpl;
diff --git a/icu4c/source/i18n/formattedval_sbimpl.cpp b/icu4c/source/i18n/formattedval_sbimpl.cpp
new file mode 100644
index 0000000..1fbecf2
--- /dev/null
+++ b/icu4c/source/i18n/formattedval_sbimpl.cpp
@@ -0,0 +1,46 @@
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+#include "unicode/utypes.h"
+
+#if !UCONFIG_NO_FORMATTING
+
+// This file contains one implementation of FormattedValue.
+// Other independent implementations should go into their own cpp file for
+// better dependency modularization.
+
+#include "formattedval_impl.h"
+
+U_NAMESPACE_BEGIN
+
+
+FormattedValueNumberStringBuilderImpl::FormattedValueNumberStringBuilderImpl(number::impl::Field numericField)
+        : fNumericField(numericField) {
+}
+
+FormattedValueNumberStringBuilderImpl::~FormattedValueNumberStringBuilderImpl() {
+}
+
+
+UnicodeString FormattedValueNumberStringBuilderImpl::toString(UErrorCode&) const {
+    return fString.toUnicodeString();
+}
+
+UnicodeString FormattedValueNumberStringBuilderImpl::toTempString(UErrorCode&) const {
+    return fString.toTempUnicodeString();
+}
+
+Appendable& FormattedValueNumberStringBuilderImpl::appendTo(Appendable& appendable, UErrorCode&) const {
+    appendable.appendString(fString.chars(), fString.length());
+    return appendable;
+}
+
+UBool FormattedValueNumberStringBuilderImpl::nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const {
+    // NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
+    return fString.nextPosition(cfpos, fNumericField, status) ? TRUE : FALSE;
+}
+
+
+U_NAMESPACE_END
+
+#endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/i18n/formattedvalue.cpp b/icu4c/source/i18n/formattedvalue.cpp
index 4d33a4d..17bec7b 100644
--- a/icu4c/source/i18n/formattedvalue.cpp
+++ b/icu4c/source/i18n/formattedvalue.cpp
@@ -51,7 +51,7 @@
     fLimit = limit;
 }
 
-UBool ConstrainedFieldPosition::matchesField(UFieldCategory category, int32_t field) {
+UBool ConstrainedFieldPosition::matchesField(int32_t category, int32_t field) {
     switch (fConstraint) {
     case UCFPOS_CONSTRAINT_NONE:
         return TRUE;
diff --git a/icu4c/source/i18n/i18n.vcxproj b/icu4c/source/i18n/i18n.vcxproj
index 6072a1c..8a53977 100644
--- a/icu4c/source/i18n/i18n.vcxproj
+++ b/icu4c/source/i18n/i18n.vcxproj
@@ -239,6 +239,7 @@
     <ClCompile Include="fmtable_cnv.cpp" />
     <ClCompile Include="format.cpp" />
     <ClCompile Include="formattedval_iterimpl.cpp" />
+    <ClCompile Include="formattedval_sbimpl.cpp" />
     <ClCompile Include="formattedvalue.cpp" />
     <ClCompile Include="fphdlimp.cpp" />
     <ClCompile Include="fpositer.cpp" />
diff --git a/icu4c/source/i18n/i18n.vcxproj.filters b/icu4c/source/i18n/i18n.vcxproj.filters
index 6227485..5cf3799 100644
--- a/icu4c/source/i18n/i18n.vcxproj.filters
+++ b/icu4c/source/i18n/i18n.vcxproj.filters
@@ -159,6 +159,9 @@
     <ClCompile Include="formattedval_iterimpl.cpp">
       <Filter>formatting</Filter>
     </ClCompile>
+    <ClCompile Include="formattedval_sbimpl.cpp">
+      <Filter>formatting</Filter>
+    </ClCompile>
     <ClCompile Include="formattedvalue.cpp">
       <Filter>formatting</Filter>
     </ClCompile>
diff --git a/icu4c/source/i18n/i18n_uwp.vcxproj b/icu4c/source/i18n/i18n_uwp.vcxproj
index bde119f..20c43aa 100644
--- a/icu4c/source/i18n/i18n_uwp.vcxproj
+++ b/icu4c/source/i18n/i18n_uwp.vcxproj
@@ -346,6 +346,7 @@
     <ClCompile Include="fmtable_cnv.cpp" />
     <ClCompile Include="format.cpp" />
     <ClCompile Include="formattedval_iterimpl.cpp" />
+    <ClCompile Include="formattedval_sbimpl.cpp" />
     <ClCompile Include="formattedvalue.cpp" />
     <ClCompile Include="fphdlimp.cpp" />
     <ClCompile Include="fpositer.cpp" />
diff --git a/icu4c/source/i18n/number_affixutils.cpp b/icu4c/source/i18n/number_affixutils.cpp
index d4588bf..3eb9c59 100644
--- a/icu4c/source/i18n/number_affixutils.cpp
+++ b/icu4c/source/i18n/number_affixutils.cpp
@@ -131,25 +131,25 @@
 Field AffixUtils::getFieldForType(AffixPatternType type) {
     switch (type) {
         case TYPE_MINUS_SIGN:
-            return Field::UNUM_SIGN_FIELD;
+            return UNUM_SIGN_FIELD;
         case TYPE_PLUS_SIGN:
-            return Field::UNUM_SIGN_FIELD;
+            return UNUM_SIGN_FIELD;
         case TYPE_PERCENT:
-            return Field::UNUM_PERCENT_FIELD;
+            return UNUM_PERCENT_FIELD;
         case TYPE_PERMILLE:
-            return Field::UNUM_PERMILL_FIELD;
+            return UNUM_PERMILL_FIELD;
         case TYPE_CURRENCY_SINGLE:
-            return Field::UNUM_CURRENCY_FIELD;
+            return UNUM_CURRENCY_FIELD;
         case TYPE_CURRENCY_DOUBLE:
-            return Field::UNUM_CURRENCY_FIELD;
+            return UNUM_CURRENCY_FIELD;
         case TYPE_CURRENCY_TRIPLE:
-            return Field::UNUM_CURRENCY_FIELD;
+            return UNUM_CURRENCY_FIELD;
         case TYPE_CURRENCY_QUAD:
-            return Field::UNUM_CURRENCY_FIELD;
+            return UNUM_CURRENCY_FIELD;
         case TYPE_CURRENCY_QUINT:
-            return Field::UNUM_CURRENCY_FIELD;
+            return UNUM_CURRENCY_FIELD;
         case TYPE_CURRENCY_OVERFLOW:
-            return Field::UNUM_CURRENCY_FIELD;
+            return UNUM_CURRENCY_FIELD;
         default:
             UPRV_UNREACHABLE;
     }
diff --git a/icu4c/source/i18n/number_modifiers.cpp b/icu4c/source/i18n/number_modifiers.cpp
index 5875bbd..1fcbe7b 100644
--- a/icu4c/source/i18n/number_modifiers.cpp
+++ b/icu4c/source/i18n/number_modifiers.cpp
@@ -156,7 +156,7 @@
 
 int32_t SimpleModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
                               UErrorCode &status) const {
-    return formatAsPrefixSuffix(output, leftIndex, rightIndex, fField, status);
+    return formatAsPrefixSuffix(output, leftIndex, rightIndex, status);
 }
 
 int32_t SimpleModifier::getPrefixLength() const {
@@ -204,13 +204,13 @@
 
 int32_t
 SimpleModifier::formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startIndex, int32_t endIndex,
-                                     Field field, UErrorCode &status) const {
+                                     UErrorCode &status) const {
     if (fSuffixOffset == -1 && fPrefixLength + fSuffixLength > 0) {
         // There is no argument for the inner number; overwrite the entire segment with our string.
-        return result.splice(startIndex, endIndex, fCompiledPattern, 2, 2 + fPrefixLength, field, status);
+        return result.splice(startIndex, endIndex, fCompiledPattern, 2, 2 + fPrefixLength, fField, status);
     } else {
         if (fPrefixLength > 0) {
-            result.insert(startIndex, fCompiledPattern, 2, 2 + fPrefixLength, field, status);
+            result.insert(startIndex, fCompiledPattern, 2, 2 + fPrefixLength, fField, status);
         }
         if (fSuffixLength > 0) {
             result.insert(
@@ -218,7 +218,7 @@
                     fCompiledPattern,
                     1 + fSuffixOffset,
                     1 + fSuffixOffset + fSuffixLength,
-                    field,
+                    fField,
                     status);
         }
         return fPrefixLength + fSuffixLength;
diff --git a/icu4c/source/i18n/number_modifiers.h b/icu4c/source/i18n/number_modifiers.h
index 65ada93..495128b 100644
--- a/icu4c/source/i18n/number_modifiers.h
+++ b/icu4c/source/i18n/number_modifiers.h
@@ -100,7 +100,7 @@
      * @return The number of characters (UTF-16 code points) that were added to the StringBuilder.
      */
     int32_t
-    formatAsPrefixSuffix(NumberStringBuilder& result, int32_t startIndex, int32_t endIndex, Field field,
+    formatAsPrefixSuffix(NumberStringBuilder& result, int32_t startIndex, int32_t endIndex,
                          UErrorCode& status) const;
 
     /**
diff --git a/icu4c/source/i18n/number_output.cpp b/icu4c/source/i18n/number_output.cpp
index e39f575..378b7b0 100644
--- a/icu4c/source/i18n/number_output.cpp
+++ b/icu4c/source/i18n/number_output.cpp
@@ -74,7 +74,7 @@
         return FALSE;
     }
     // NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
-    return fResults->string.nextPosition(cfpos, status) ? TRUE : FALSE;
+    return fResults->string.nextPosition(cfpos, 0, status) ? TRUE : FALSE;
 }
 
 UBool FormattedNumber::nextFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) const {
diff --git a/icu4c/source/i18n/number_stringbuilder.cpp b/icu4c/source/i18n/number_stringbuilder.cpp
index f756edc..03300b3 100644
--- a/icu4c/source/i18n/number_stringbuilder.cpp
+++ b/icu4c/source/i18n/number_stringbuilder.cpp
@@ -8,6 +8,7 @@
 #include "number_stringbuilder.h"
 #include "static_unicode_sets.h"
 #include "unicode/utf16.h"
+#include "number_utils.h"
 
 using namespace icu;
 using namespace icu::number;
@@ -41,7 +42,7 @@
         getCharPtr()[i] = 1;
     }
 #endif
-};
+}
 
 NumberStringBuilder::~NumberStringBuilder() {
     if (fUsingHeap) {
@@ -449,7 +450,7 @@
     ConstrainedFieldPosition cfpos;
     cfpos.constrainField(UFIELD_CATEGORY_NUMBER, rawField);
     cfpos.setState(UFIELD_CATEGORY_NUMBER, rawField, fp.getBeginIndex(), fp.getEndIndex());
-    if (nextPosition(cfpos, status)) {
+    if (nextPosition(cfpos, 0, status)) {
         fp.setBeginIndex(cfpos.getStart());
         fp.setEndIndex(cfpos.getLimit());
         return true;
@@ -476,25 +477,21 @@
 void NumberStringBuilder::getAllFieldPositions(FieldPositionIteratorHandler& fpih,
                                                UErrorCode& status) const {
     ConstrainedFieldPosition cfpos;
-    while (nextPosition(cfpos, status)) {
+    while (nextPosition(cfpos, 0, status)) {
         fpih.addAttribute(cfpos.getField(), cfpos.getStart(), cfpos.getLimit());
     }
 }
 
-bool NumberStringBuilder::nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& /*status*/) const {
-    bool isSearchingForField = false;
-    if (cfpos.getConstraintType() == UCFPOS_CONSTRAINT_CATEGORY) {
-        if (cfpos.getCategory() != UFIELD_CATEGORY_NUMBER) {
-            return false;
-        }
-    } else if (cfpos.getConstraintType() == UCFPOS_CONSTRAINT_FIELD) {
-        isSearchingForField = true;
-    }
+// Signal the end of the string using a field that doesn't exist and that is
+// different from UNUM_FIELD_COUNT, which is used for "null number field".
+static constexpr Field kEndField = 0xff;
 
+bool NumberStringBuilder::nextPosition(ConstrainedFieldPosition& cfpos, Field numericField, UErrorCode& /*status*/) const {
+    auto numericCAF = NumFieldUtils::expand(numericField);
     int32_t fieldStart = -1;
-    int32_t currField = UNUM_FIELD_COUNT;
+    Field currField = UNUM_FIELD_COUNT;
     for (int32_t i = fZero + cfpos.getLimit(); i <= fZero + fLength; i++) {
-        Field _field = (i < fZero + fLength) ? getFieldPtr()[i] : UNUM_FIELD_COUNT;
+        Field _field = (i < fZero + fLength) ? getFieldPtr()[i] : kEndField;
         // Case 1: currently scanning a field.
         if (currField != UNUM_FIELD_COUNT) {
             if (currField != _field) {
@@ -514,15 +511,17 @@
                 if (currField != UNUM_GROUPING_SEPARATOR_FIELD) {
                     start = trimFront(start);
                 }
-                cfpos.setState(UFIELD_CATEGORY_NUMBER, currField, start, end);
+                auto caf = NumFieldUtils::expand(currField);
+                cfpos.setState(caf.category, caf.field, start, end);
                 return true;
             }
             continue;
         }
         // Special case: coalesce the INTEGER if we are pointing at the end of the INTEGER.
-        if ((!isSearchingForField || cfpos.getField() == UNUM_INTEGER_FIELD)
+        if (cfpos.matchesField(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)
                 && i > fZero
-                && i - fZero > cfpos.getLimit()  // don't return the same field twice in a row
+                // don't return the same field twice in a row:
+                && i - fZero > cfpos.getLimit()
                 && isIntOrGroup(getFieldPtr()[i - 1])
                 && !isIntOrGroup(_field)) {
             int j = i - 1;
@@ -530,16 +529,32 @@
             cfpos.setState(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, j - fZero + 1, i - fZero);
             return true;
         }
+        // Special case: coalesce NUMERIC if we are pointing at the end of the NUMERIC.
+        if (numericField != 0
+                && cfpos.matchesField(numericCAF.category, numericCAF.field)
+                && i > fZero
+                // don't return the same field twice in a row:
+                && (i - fZero > cfpos.getLimit()
+                    || cfpos.getCategory() != numericCAF.category
+                    || cfpos.getField() != numericCAF.field)
+                && isNumericField(getFieldPtr()[i - 1])
+                && !isNumericField(_field)) {
+            int j = i - 1;
+            for (; j >= fZero && isNumericField(getFieldPtr()[j]); j--) {}
+            cfpos.setState(numericCAF.category, numericCAF.field, j - fZero + 1, i - fZero);
+            return true;
+        }
         // Special case: skip over INTEGER; will be coalesced later.
         if (_field == UNUM_INTEGER_FIELD) {
             _field = UNUM_FIELD_COUNT;
         }
         // Case 2: no field starting at this position.
-        if (_field == UNUM_FIELD_COUNT) {
+        if (_field == UNUM_FIELD_COUNT || _field == kEndField) {
             continue;
         }
         // Case 3: check for field starting at this position
-        if (!isSearchingForField || cfpos.getField() == _field) {
+        auto caf = NumFieldUtils::expand(_field);
+        if (cfpos.matchesField(caf.category, caf.field)) {
             fieldStart = i - fZero;
             currField = _field;
         }
@@ -560,7 +575,11 @@
 
 bool NumberStringBuilder::isIntOrGroup(Field field) {
     return field == UNUM_INTEGER_FIELD
-        || field ==UNUM_GROUPING_SEPARATOR_FIELD;
+        || field == UNUM_GROUPING_SEPARATOR_FIELD;
+}
+
+bool NumberStringBuilder::isNumericField(Field field) {
+    return NumFieldUtils::isNumericField(field);
 }
 
 int32_t NumberStringBuilder::trimBack(int32_t limit) const {
diff --git a/icu4c/source/i18n/number_stringbuilder.h b/icu4c/source/i18n/number_stringbuilder.h
index ca3d6be..d48f6e1 100644
--- a/icu4c/source/i18n/number_stringbuilder.h
+++ b/icu4c/source/i18n/number_stringbuilder.h
@@ -108,7 +108,7 @@
 
     void getAllFieldPositions(FieldPositionIteratorHandler& fpih, UErrorCode& status) const;
 
-    bool nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const;
+    bool nextPosition(ConstrainedFieldPosition& cfpos, Field numericField, UErrorCode& status) const;
 
     bool containsField(Field field) const;
 
@@ -147,6 +147,8 @@
 
     static bool isIntOrGroup(Field field);
 
+    static bool isNumericField(Field field);
+
     int32_t trimBack(int32_t limit) const;
 
     int32_t trimFront(int32_t start) const;
diff --git a/icu4c/source/i18n/number_types.h b/icu4c/source/i18n/number_types.h
index 00a6818..225d1e5 100644
--- a/icu4c/source/i18n/number_types.h
+++ b/icu4c/source/i18n/number_types.h
@@ -23,7 +23,11 @@
 
 // Typedef several enums for brevity and for easier comparison to Java.
 
-typedef UNumberFormatFields Field;
+// Convention: bottom 4 bits for field, top 4 bits for field category.
+// Field category 0 implies the number category so that the number field
+// literals can be directly passed as a Field type.
+// See the helper functions in "NumFieldUtils" in number_utils.h
+typedef uint8_t Field;
 
 typedef UNumberFormatRoundingMode RoundingMode;
 
@@ -346,6 +350,7 @@
     T fValue;
 };
 
+
 } // namespace impl
 } // namespace number
 U_NAMESPACE_END
diff --git a/icu4c/source/i18n/number_utils.h b/icu4c/source/i18n/number_utils.h
index c367166..05b8ec0 100644
--- a/icu4c/source/i18n/number_utils.h
+++ b/icu4c/source/i18n/number_utils.h
@@ -32,6 +32,48 @@
     CLDR_PATTERN_STYLE_COUNT,
 };
 
+
+/**
+ * Helper functions for dealing with the Field typedef, which stores fields
+ * in a compressed format.
+ */
+class NumFieldUtils {
+public:
+    struct CategoryFieldPair {
+        int32_t category;
+        int32_t field;
+    };
+
+    /** Compile-time function to construct a Field from a category and a field */
+    template <int32_t category, int32_t field>
+    static constexpr Field compress() {
+        static_assert(category != 0, "cannot use Undefined category in NumFieldUtils");
+        static_assert(category <= 0xf, "only 4 bits for category");
+        static_assert(field <= 0xf, "only 4 bits for field");
+        return static_cast<int8_t>((category << 4) | field);
+    }
+
+    /** Runtime inline function to unpack the category and field from the Field */
+    static inline CategoryFieldPair expand(Field field) {
+        if (field == UNUM_FIELD_COUNT) {
+            return {UFIELD_CATEGORY_UNDEFINED, 0};
+        }
+        CategoryFieldPair ret = {
+            (field >> 4),
+            (field & 0xf)
+        };
+        if (ret.category == 0) {
+            ret.category = UFIELD_CATEGORY_NUMBER;
+        }
+        return ret;
+    }
+
+    static inline bool isNumericField(Field field) {
+        int8_t category = field >> 4;
+        return category == 0 || category == UFIELD_CATEGORY_NUMBER;
+    }
+};
+
 // Namespace for naked functions
 namespace utils {
 
diff --git a/icu4c/source/i18n/numrange_fluent.cpp b/icu4c/source/i18n/numrange_fluent.cpp
index 10479f9..929cac9 100644
--- a/icu4c/source/i18n/numrange_fluent.cpp
+++ b/icu4c/source/i18n/numrange_fluent.cpp
@@ -435,7 +435,7 @@
         return FALSE;
     }
     // NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
-    return fResults->string.nextPosition(cfpos, status) ? TRUE : FALSE;
+    return fResults->string.nextPosition(cfpos, 0, status) ? TRUE : FALSE;
 }
 
 UBool FormattedNumberRange::nextFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) const {
diff --git a/icu4c/source/i18n/quantityformatter.cpp b/icu4c/source/i18n/quantityformatter.cpp
index ba06ba0..7235fa6 100644
--- a/icu4c/source/i18n/quantityformatter.cpp
+++ b/icu4c/source/i18n/quantityformatter.cpp
@@ -25,6 +25,8 @@
 #include "standardplural.h"
 #include "uassert.h"
 #include "number_decimalquantity.h"
+#include "number_utypes.h"
+#include "number_stringbuilder.h"
 
 U_NAMESPACE_BEGIN
 
@@ -174,6 +176,39 @@
     return StandardPlural::orOtherFromString(pluralKeyword);
 }
 
+void QuantityFormatter::formatAndSelect(
+        double quantity,
+        const NumberFormat& fmt,
+        const PluralRules& rules,
+        number::impl::NumberStringBuilder& output,
+        StandardPlural::Form& pluralForm,
+        UErrorCode& status) {
+    UnicodeString pluralKeyword;
+    const DecimalFormat* df = dynamic_cast<const DecimalFormat*>(&fmt);
+    if (df != nullptr) {
+        number::impl::UFormattedNumberData fn;
+        fn.quantity.setToDouble(quantity);
+        df->toNumberFormatter().formatImpl(&fn, status);
+        if (U_FAILURE(status)) {
+            return;
+        }
+        output = std::move(fn.string);
+        pluralKeyword = rules.select(fn.quantity);
+    } else {
+        UnicodeString result;
+        fmt.format(quantity, result, status);
+        if (U_FAILURE(status)) {
+            return;
+        }
+        output.append(result, UNUM_FIELD_COUNT, status);
+        if (U_FAILURE(status)) {
+            return;
+        }
+        pluralKeyword = rules.select(quantity);
+    }
+    pluralForm = StandardPlural::orOtherFromString(pluralKeyword);
+}
+
 UnicodeString &QuantityFormatter::format(
             const SimpleFormatter &pattern,
             const UnicodeString &value,
diff --git a/icu4c/source/i18n/quantityformatter.h b/icu4c/source/i18n/quantityformatter.h
index ca3fb3d..0069cb2 100644
--- a/icu4c/source/i18n/quantityformatter.h
+++ b/icu4c/source/i18n/quantityformatter.h
@@ -27,6 +27,12 @@
 class Formattable;
 class FieldPosition;
 
+namespace number {
+namespace impl {
+class NumberStringBuilder;
+}
+}
+
 /**
  * A plural aware formatter that is good for expressing a single quantity and
  * a unit.
@@ -111,6 +117,7 @@
 
     /**
      * Selects the standard plural form for the number/formatter/rules.
+     * TODO(13591): Remove this method.
      */
     static StandardPlural::Form selectPlural(
             const Formattable &number,
@@ -121,6 +128,27 @@
             UErrorCode &status);
 
     /**
+     * Formats a quantity and selects its plural form. The output is appended
+     * to a NumberStringBuilder in order to retain field information.
+     *
+     * @param quantity The number to format.
+     * @param fmt The formatter to use to format the number.
+     * @param rules The rules to use to select the plural form of the
+     *              formatted number.
+     * @param output Where to append the result of the format operation.
+     * @param pluralForm Output variable populated with the plural form of the
+     *                   formatted number.
+     * @param status Set if an error occurs.
+     */
+    static void formatAndSelect(
+            double quantity,
+            const NumberFormat& fmt,
+            const PluralRules& rules,
+            number::impl::NumberStringBuilder& output,
+            StandardPlural::Form& pluralForm,
+            UErrorCode& status);
+
+    /**
      * Formats the pattern with the value and adjusts the FieldPosition.
      */
     static UnicodeString &format(
diff --git a/icu4c/source/i18n/reldatefmt.cpp b/icu4c/source/i18n/reldatefmt.cpp
index 06c52bd..4f6f723 100644
--- a/icu4c/source/i18n/reldatefmt.cpp
+++ b/icu4c/source/i18n/reldatefmt.cpp
@@ -15,6 +15,7 @@
 #if !UCONFIG_NO_FORMATTING && !UCONFIG_NO_BREAK_ITERATION
 
 #include <cmath>
+#include <functional>
 #include "unicode/dtfmtsym.h"
 #include "unicode/ucasemap.h"
 #include "unicode/ureldatefmt.h"
@@ -41,6 +42,12 @@
 #include "sharednumberformat.h"
 #include "standardplural.h"
 #include "unifiedcache.h"
+#include "util.h"
+#include "number_stringbuilder.h"
+#include "number_utypes.h"
+#include "number_modifiers.h"
+#include "formattedval_impl.h"
+#include "number_utils.h"
 
 // Copied from uscript_props.cpp
 
@@ -717,6 +724,26 @@
     return result.orphan();
 }
 
+
+
+static constexpr number::impl::Field kRDTNumericField
+    = number::impl::NumFieldUtils::compress<UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD>();
+
+static constexpr number::impl::Field kRDTLiteralField
+    = number::impl::NumFieldUtils::compress<UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD>();
+
+class FormattedRelativeDateTimeData : public FormattedValueNumberStringBuilderImpl {
+public:
+    FormattedRelativeDateTimeData() : FormattedValueNumberStringBuilderImpl(kRDTNumericField) {}
+    virtual ~FormattedRelativeDateTimeData();
+};
+
+FormattedRelativeDateTimeData::~FormattedRelativeDateTimeData() = default;
+
+
+UPRV_FORMATTED_VALUE_SUBCLASS_AUTO_IMPL(FormattedRelativeDateTime)
+
+
 RelativeDateTimeFormatter::RelativeDateTimeFormatter(UErrorCode& status) :
         fCache(nullptr),
         fNumberFormat(nullptr),
@@ -841,43 +868,142 @@
     return fStyle;
 }
 
-UnicodeString& RelativeDateTimeFormatter::format(
-        double quantity, UDateDirection direction, UDateRelativeUnit unit,
-        UnicodeString& appendTo, UErrorCode& status) const {
+
+// To reduce boilerplate code, we use a helper function that forwards variadic
+// arguments to the formatImpl function.
+
+template<typename F, typename... Args>
+UnicodeString& RelativeDateTimeFormatter::doFormat(
+        F callback,
+        UnicodeString& appendTo,
+        UErrorCode& status,
+        Args... args) const {
+    FormattedRelativeDateTimeData output;
+    (this->*callback)(std::forward<Args>(args)..., output, status);
     if (U_FAILURE(status)) {
         return appendTo;
     }
+    UnicodeString result = output.getStringRef().toUnicodeString();
+    return appendTo.append(adjustForContext(result));
+}
+
+template<typename F, typename... Args>
+FormattedRelativeDateTime RelativeDateTimeFormatter::doFormatToValue(
+        F callback,
+        UErrorCode& status,
+        Args... args) const {
+    if (!checkNoAdjustForContext(status)) {
+        return FormattedRelativeDateTime(status);
+    }
+    LocalPointer<FormattedRelativeDateTimeData> output(
+        new FormattedRelativeDateTimeData(), status);
+    if (U_FAILURE(status)) {
+        return FormattedRelativeDateTime(status);
+    }
+    (this->*callback)(std::forward<Args>(args)..., *output, status);
+    output->getStringRef().writeTerminator(status);
+    return FormattedRelativeDateTime(output.orphan());
+}
+
+UnicodeString& RelativeDateTimeFormatter::format(
+        double quantity,
+        UDateDirection direction,
+        UDateRelativeUnit unit,
+        UnicodeString& appendTo,
+        UErrorCode& status) const {
+    return doFormat(
+        &RelativeDateTimeFormatter::formatImpl,
+        appendTo,
+        status,
+        quantity,
+        direction,
+        unit);
+}
+
+FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue(
+        double quantity,
+        UDateDirection direction,
+        UDateRelativeUnit unit,
+        UErrorCode& status) const {
+    return doFormatToValue(
+        &RelativeDateTimeFormatter::formatImpl,
+        status,
+        quantity,
+        direction,
+        unit);
+}
+
+void RelativeDateTimeFormatter::formatImpl(
+        double quantity,
+        UDateDirection direction,
+        UDateRelativeUnit unit,
+        FormattedRelativeDateTimeData& output,
+        UErrorCode& status) const {
+    if (U_FAILURE(status)) {
+        return;
+    }
     if (direction != UDAT_DIRECTION_LAST && direction != UDAT_DIRECTION_NEXT) {
         status = U_ILLEGAL_ARGUMENT_ERROR;
-        return appendTo;
+        return;
     }
     int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0;
-    FieldPosition pos(FieldPosition::DONT_CARE);
 
-    UnicodeString result;
-    UnicodeString formattedNumber;
-
-    StandardPlural::Form pluralIndex = QuantityFormatter::selectPlural(
-        quantity, **fNumberFormat, **fPluralRules, formattedNumber, pos,
+    StandardPlural::Form pluralForm;
+    QuantityFormatter::formatAndSelect(
+        quantity,
+        **fNumberFormat,
+        **fPluralRules,
+        output.getStringRef(),
+        pluralForm,
         status);
+    if (U_FAILURE(status)) {
+        return;
+    }
 
     const SimpleFormatter* formatter =
-        fCache->getRelativeUnitFormatter(fStyle, unit, bFuture, pluralIndex);
+        fCache->getRelativeUnitFormatter(fStyle, unit, bFuture, pluralForm);
     if (formatter == nullptr) {
         // TODO: WARN - look at quantity formatter's action with an error.
         status = U_INVALID_FORMAT_ERROR;
-        return appendTo;
+        return;
     }
-    formatter->format(formattedNumber, result, status);
-    adjustForContext(result);
-    return appendTo.append(result);
+
+    number::impl::SimpleModifier modifier(*formatter, kRDTLiteralField, false);
+    modifier.formatAsPrefixSuffix(
+        output.getStringRef(), 0, output.getStringRef().length(), status);
 }
 
 UnicodeString& RelativeDateTimeFormatter::formatNumeric(
-        double offset, URelativeDateTimeUnit unit,
-        UnicodeString& appendTo, UErrorCode& status) const {
+        double offset,
+        URelativeDateTimeUnit unit,
+        UnicodeString& appendTo,
+        UErrorCode& status) const {
+    return doFormat(
+        &RelativeDateTimeFormatter::formatNumericImpl,
+        appendTo,
+        status,
+        offset,
+        unit);
+}
+
+FormattedRelativeDateTime RelativeDateTimeFormatter::formatNumericToValue(
+        double offset,
+        URelativeDateTimeUnit unit,
+        UErrorCode& status) const {
+    return doFormatToValue(
+        &RelativeDateTimeFormatter::formatNumericImpl,
+        status,
+        offset,
+        unit);
+}
+
+void RelativeDateTimeFormatter::formatNumericImpl(
+        double offset,
+        URelativeDateTimeUnit unit,
+        FormattedRelativeDateTimeData& output,
+        UErrorCode& status) const {
     if (U_FAILURE(status)) {
-        return appendTo;
+        return;
     }
     UDateDirection direction = UDAT_DIRECTION_NEXT;
     if (std::signbit(offset)) { // needed to handle -0.0
@@ -886,55 +1012,110 @@
     }
     if (direction != UDAT_DIRECTION_LAST && direction != UDAT_DIRECTION_NEXT) {
         status = U_ILLEGAL_ARGUMENT_ERROR;
-        return appendTo;
+        return;
     }
     int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0;
-    FieldPosition pos(FieldPosition::DONT_CARE);
 
-    UnicodeString result;
-    UnicodeString formattedNumber;
-
-    StandardPlural::Form pluralIndex = QuantityFormatter::selectPlural(
-        offset, **fNumberFormat, **fPluralRules, formattedNumber, pos,
+    StandardPlural::Form pluralForm;
+    QuantityFormatter::formatAndSelect(
+        offset,
+        **fNumberFormat,
+        **fPluralRules,
+        output.getStringRef(),
+        pluralForm,
         status);
+    if (U_FAILURE(status)) {
+        return;
+    }
 
     const SimpleFormatter* formatter =
-        fCache->getRelativeDateTimeUnitFormatter(fStyle, unit, bFuture, pluralIndex);
+        fCache->getRelativeDateTimeUnitFormatter(fStyle, unit, bFuture, pluralForm);
     if (formatter == nullptr) {
         // TODO: WARN - look at quantity formatter's action with an error.
         status = U_INVALID_FORMAT_ERROR;
-        return appendTo;
+        return;
     }
-    formatter->format(formattedNumber, result, status);
-    adjustForContext(result);
-    return appendTo.append(result);
+
+    number::impl::SimpleModifier modifier(*formatter, kRDTLiteralField, false);
+    modifier.formatAsPrefixSuffix(
+        output.getStringRef(), 0, output.getStringRef().length(), status);
 }
 
 UnicodeString& RelativeDateTimeFormatter::format(
-        UDateDirection direction, UDateAbsoluteUnit unit,
-        UnicodeString& appendTo, UErrorCode& status) const {
+        UDateDirection direction,
+        UDateAbsoluteUnit unit,
+        UnicodeString& appendTo,
+        UErrorCode& status) const {
+    return doFormat(
+        &RelativeDateTimeFormatter::formatAbsoluteImpl,
+        appendTo,
+        status,
+        direction,
+        unit);
+}
+
+FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue(
+        UDateDirection direction,
+        UDateAbsoluteUnit unit,
+        UErrorCode& status) const {
+    return doFormatToValue(
+        &RelativeDateTimeFormatter::formatAbsoluteImpl,
+        status,
+        direction,
+        unit);
+}
+
+void RelativeDateTimeFormatter::formatAbsoluteImpl(
+        UDateDirection direction,
+        UDateAbsoluteUnit unit,
+        FormattedRelativeDateTimeData& output,
+        UErrorCode& status) const {
     if (U_FAILURE(status)) {
-        return appendTo;
+        return;
     }
     if (unit == UDAT_ABSOLUTE_NOW && direction != UDAT_DIRECTION_PLAIN) {
         status = U_ILLEGAL_ARGUMENT_ERROR;
-        return appendTo;
+        return;
     }
 
     // Get string using fallback.
-    UnicodeString result;
-    result.fastCopyFrom(fCache->getAbsoluteUnitString(fStyle, unit, direction));
-    if (fOptBreakIterator != nullptr) {
-        adjustForContext(result);
-    }
-    return appendTo.append(result);
+    output.getStringRef().append(
+        fCache->getAbsoluteUnitString(fStyle, unit, direction),
+        kRDTLiteralField,
+        status);
 }
 
 UnicodeString& RelativeDateTimeFormatter::format(
-        double offset, URelativeDateTimeUnit unit,
-        UnicodeString& appendTo, UErrorCode& status) const {
+        double offset,
+        URelativeDateTimeUnit unit,
+        UnicodeString& appendTo,
+        UErrorCode& status) const {
+    return doFormat(
+        &RelativeDateTimeFormatter::formatRelativeImpl,
+        appendTo,
+        status,
+        offset,
+        unit);
+}
+
+FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue(
+        double offset,
+        URelativeDateTimeUnit unit,
+        UErrorCode& status) const {
+    return doFormatToValue(
+        &RelativeDateTimeFormatter::formatRelativeImpl,
+        status,
+        offset,
+        unit);
+}
+
+void RelativeDateTimeFormatter::formatRelativeImpl(
+        double offset,
+        URelativeDateTimeUnit unit,
+        FormattedRelativeDateTimeData& output,
+        UErrorCode& status) const {
     if (U_FAILURE(status)) {
-        return appendTo;
+        return;
     }
     // TODO:
     // The full implementation of this depends on CLDR data that is not yet available,
@@ -981,20 +1162,13 @@
         default: break;
     }
     if (direction != UDAT_DIRECTION_COUNT && absunit != UDAT_ABSOLUTE_UNIT_COUNT) {
-        const UnicodeString &unitFormatString =
-            fCache->getAbsoluteUnitString(fStyle, absunit, direction);
-        if (!unitFormatString.isEmpty()) {
-            if (fOptBreakIterator != nullptr) {
-                UnicodeString result(unitFormatString);
-                adjustForContext(result);
-                return appendTo.append(result);
-            } else {
-                return appendTo.append(unitFormatString);
-            }
+        formatAbsoluteImpl(direction, absunit, output, status);
+        if (output.getStringRef().length() != 0) {
+            return;
         }
     }
     // otherwise fallback to formatNumeric
-    return formatNumeric(offset, unit, appendTo, status);
+    formatNumericImpl(offset, unit, output, status);
 }
 
 UnicodeString& RelativeDateTimeFormatter::combineDateAndTime(
@@ -1004,10 +1178,10 @@
             timeString, relativeDateString, appendTo, status);
 }
 
-void RelativeDateTimeFormatter::adjustForContext(UnicodeString &str) const {
+UnicodeString& RelativeDateTimeFormatter::adjustForContext(UnicodeString &str) const {
     if (fOptBreakIterator == nullptr
         || str.length() == 0 || !u_islower(str.char32At(0))) {
-        return;
+        return str;
     }
 
     // Must guarantee that one thread at a time accesses the shared break
@@ -1017,6 +1191,17 @@
             fOptBreakIterator->get(),
             fLocale,
             U_TITLECASE_NO_LOWERCASE | U_TITLECASE_NO_BREAK_ADJUSTMENT);
+    return str;
+}
+
+UBool RelativeDateTimeFormatter::checkNoAdjustForContext(UErrorCode& status) const {
+    // This is unsupported because it's hard to keep fields in sync with title
+    // casing. The code could be written and tested if there is demand.
+    if (fOptBreakIterator != nullptr) {
+        status = U_UNSUPPORTED_ERROR;
+        return FALSE;
+    }
+    return TRUE;
 }
 
 void RelativeDateTimeFormatter::init(
@@ -1072,6 +1257,17 @@
 
 U_NAMESPACE_USE
 
+
+// Magic number: "FRDT" (FormattedRelativeDateTime) in ASCII
+UPRV_FORMATTED_VALUE_CAPI_AUTO_IMPL(
+    FormattedRelativeDateTime,
+    UFormattedRelativeDateTime,
+    UFormattedRelativeDateTimeImpl,
+    UFormattedRelativeDateTimeApiHelper,
+    ureldatefmt,
+    0x46524454)
+
+
 U_CAPI URelativeDateTimeFormatter* U_EXPORT2
 ureldatefmt_open( const char*          locale,
                   UNumberFormat*       nfToAdopt,
@@ -1125,6 +1321,21 @@
     return res.extract(result, resultCapacity, *status);
 }
 
+U_STABLE void U_EXPORT2
+ureldatefmt_formatNumericToResult(
+        const URelativeDateTimeFormatter* reldatefmt,
+        double                            offset,
+        URelativeDateTimeUnit             unit,
+        UFormattedRelativeDateTime*       result,
+        UErrorCode*                       status) {
+    if (U_FAILURE(*status)) {
+        return;
+    }
+    auto* fmt = reinterpret_cast<const RelativeDateTimeFormatter*>(reldatefmt);
+    auto* resultImpl = UFormattedRelativeDateTimeApiHelper::validate(result, *status);
+    resultImpl->fImpl = fmt->formatNumericToValue(offset, unit, *status);
+}
+
 U_CAPI int32_t U_EXPORT2
 ureldatefmt_format( const URelativeDateTimeFormatter* reldatefmt,
                     double                offset,
@@ -1153,6 +1364,21 @@
     return res.extract(result, resultCapacity, *status);
 }
 
+U_DRAFT void U_EXPORT2
+ureldatefmt_formatToResult(
+        const URelativeDateTimeFormatter* reldatefmt,
+        double                            offset,
+        URelativeDateTimeUnit             unit,
+        UFormattedRelativeDateTime*       result,
+        UErrorCode*                       status) {
+    if (U_FAILURE(*status)) {
+        return;
+    }
+    auto* fmt = reinterpret_cast<const RelativeDateTimeFormatter*>(reldatefmt);
+    auto* resultImpl = UFormattedRelativeDateTimeApiHelper::validate(result, *status);
+    resultImpl->fImpl = fmt->formatToValue(offset, unit, *status);
+}
+
 U_CAPI int32_t U_EXPORT2
 ureldatefmt_combineDateAndTime( const URelativeDateTimeFormatter* reldatefmt,
                     const UChar *     relativeDateString,
diff --git a/icu4c/source/i18n/unicode/formattedvalue.h b/icu4c/source/i18n/unicode/formattedvalue.h
index 3f195fb..457f119 100644
--- a/icu4c/source/i18n/unicode/formattedvalue.h
+++ b/icu4c/source/i18n/unicode/formattedvalue.h
@@ -222,7 +222,7 @@
         int32_t limit);
 
     /** @internal */
-    UBool matchesField(UFieldCategory category, int32_t field);
+    UBool matchesField(int32_t category, int32_t field);
 
   private:
     int64_t fContext = 0LL;
diff --git a/icu4c/source/i18n/unicode/reldatefmt.h b/icu4c/source/i18n/unicode/reldatefmt.h
index be06b10..995fa2d 100644
--- a/icu4c/source/i18n/unicode/reldatefmt.h
+++ b/icu4c/source/i18n/unicode/reldatefmt.h
@@ -19,6 +19,7 @@
 #include "unicode/udisplaycontext.h"
 #include "unicode/ureldatefmt.h"
 #include "unicode/locid.h"
+#include "unicode/formattedvalue.h"
 
 /**
  * \file
@@ -245,6 +246,70 @@
 class SharedBreakIterator;
 class NumberFormat;
 class UnicodeString;
+class FormattedRelativeDateTimeData;
+
+
+/**
+ * An immutable class containing the result of a relative datetime formatting operation.
+ *
+ * Not intended for public subclassing.
+ *
+ * @draft ICU 64
+ */
+class U_I18N_API FormattedRelativeDateTime : public UMemory, public FormattedValue {
+  public:
+    /**
+     * Default constructor; makes an empty FormattedRelativeDateTime.
+     * @draft ICU 64
+     */
+    FormattedRelativeDateTime() : fData(nullptr), fErrorCode(U_INVALID_STATE_ERROR) {};
+
+    /**
+     * Move constructor: Leaves the source FormattedRelativeDateTime in an undefined state.
+     * @draft ICU 64
+     */
+    FormattedRelativeDateTime(FormattedRelativeDateTime&& src) U_NOEXCEPT;
+
+    /**
+     * Destruct an instance of FormattedRelativeDateTime.
+     * @draft ICU 64
+     */
+    virtual ~FormattedRelativeDateTime() U_OVERRIDE;
+
+    /** Copying not supported; use move constructor instead. */
+    FormattedRelativeDateTime(const FormattedRelativeDateTime&) = delete;
+
+    /** Copying not supported; use move assignment instead. */
+    FormattedRelativeDateTime& operator=(const FormattedRelativeDateTime&) = delete;
+
+    /**
+     * Move assignment: Leaves the source FormattedRelativeDateTime in an undefined state.
+     * @draft ICU 64
+     */
+    FormattedRelativeDateTime& operator=(FormattedRelativeDateTime&& src) U_NOEXCEPT;
+
+    /** @copydoc FormattedValue::toString() */
+    UnicodeString toString(UErrorCode& status) const U_OVERRIDE;
+
+    /** @copydoc FormattedValue::toTempString() */
+    UnicodeString toTempString(UErrorCode& status) const U_OVERRIDE;
+
+    /** @copydoc FormattedValue::appendTo() */
+    Appendable &appendTo(Appendable& appendable, UErrorCode& status) const U_OVERRIDE;
+
+    /** @copydoc FormattedValue::nextPosition() */
+    UBool nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const U_OVERRIDE;
+
+  private:
+    FormattedRelativeDateTimeData *fData;
+    UErrorCode fErrorCode;
+    explicit FormattedRelativeDateTime(FormattedRelativeDateTimeData *results)
+        : fData(results), fErrorCode(U_ZERO_ERROR) {};
+    explicit FormattedRelativeDateTime(UErrorCode errorCode)
+        : fData(nullptr), fErrorCode(errorCode) {};
+    friend class RelativeDateTimeFormatter;
+};
+
 
 /**
  * Formats simple relative dates. There are two types of relative dates that
@@ -386,6 +451,10 @@
     /**
      * Formats a relative date with a quantity such as "in 5 days" or
      * "3 months ago"
+     *
+     * This method returns a String. To get more information about the
+     * formatting result, use formatToValue().
+     *
      * @param quantity The numerical amount e.g 5. This value is formatted
      * according to this object's NumberFormat object.
      * @param direction NEXT means a future relative date; LAST means a past
@@ -406,7 +475,34 @@
             UErrorCode& status) const;
 
     /**
+     * Formats a relative date with a quantity such as "in 5 days" or
+     * "3 months ago"
+     *
+     * This method returns a FormattedRelativeDateTime, which exposes more
+     * information than the String returned by format().
+     *
+     * @param quantity The numerical amount e.g 5. This value is formatted
+     * according to this object's NumberFormat object.
+     * @param direction NEXT means a future relative date; LAST means a past
+     * relative date. If direction is anything else, this method sets
+     * status to U_ILLEGAL_ARGUMENT_ERROR.
+     * @param unit the unit e.g day? month? year?
+     * @param status ICU error code returned here.
+     * @return The formatted relative datetime
+     * @draft ICU 64
+     */
+    FormattedRelativeDateTime formatToValue(
+            double quantity,
+            UDateDirection direction,
+            UDateRelativeUnit unit,
+            UErrorCode& status) const;
+
+    /**
      * Formats a relative date without a quantity.
+     *
+     * This method returns a String. To get more information about the
+     * formatting result, use formatToValue().
+     *
      * @param direction NEXT, LAST, THIS, etc.
      * @param unit e.g SATURDAY, DAY, MONTH
      * @param appendTo The string to which the formatted result will be
@@ -424,9 +520,32 @@
             UErrorCode& status) const;
 
     /**
+     * Formats a relative date without a quantity.
+     *
+     * This method returns a FormattedRelativeDateTime, which exposes more
+     * information than the String returned by format().
+     *
+     * If the string is not available in the requested locale, the return
+     * value will be empty (calling toString will give an empty string).
+     *
+     * @param direction NEXT, LAST, THIS, etc.
+     * @param unit e.g SATURDAY, DAY, MONTH
+     * @param status ICU error code returned here.
+     * @return The formatted relative datetime
+     * @draft ICU 64
+     */
+    FormattedRelativeDateTime formatToValue(
+            UDateDirection direction,
+            UDateAbsoluteUnit unit,
+            UErrorCode& status) const;
+
+    /**
      * Format a combination of URelativeDateTimeUnit and numeric offset
      * using a numeric style, e.g. "1 week ago", "in 1 week",
      * "5 weeks ago", "in 5 weeks".
+     *
+     * This method returns a String. To get more information about the
+     * formatting result, use formatNumericToValue().
      * 
      * @param offset    The signed offset for the specified unit. This
      *                  will be formatted according to this object's
@@ -448,11 +567,37 @@
 
     /**
      * Format a combination of URelativeDateTimeUnit and numeric offset
+     * using a numeric style, e.g. "1 week ago", "in 1 week",
+     * "5 weeks ago", "in 5 weeks".
+     *
+     * This method returns a FormattedRelativeDateTime, which exposes more
+     * information than the String returned by formatNumeric().
+     * 
+     * @param offset    The signed offset for the specified unit. This
+     *                  will be formatted according to this object's
+     *                  NumberFormat object.
+     * @param unit      The unit to use when formatting the relative
+     *                  date, e.g. UDAT_REL_UNIT_WEEK,
+     *                  UDAT_REL_UNIT_FRIDAY.
+     * @param status    ICU error code returned here.
+     * @return          The formatted relative datetime
+     * @draft ICU 64
+     */
+    FormattedRelativeDateTime formatNumericToValue(
+            double offset,
+            URelativeDateTimeUnit unit,
+            UErrorCode& status) const;
+
+    /**
+     * Format a combination of URelativeDateTimeUnit and numeric offset
      * using a text style if possible, e.g. "last week", "this week",
      * "next week", "yesterday", "tomorrow". Falls back to numeric
      * style if no appropriate text term is available for the specified
      * offset in the object's locale.
      *
+     * This method returns a String. To get more information about the
+     * formatting result, use formatToValue().
+     *
      * @param offset    The signed offset for the specified unit.
      * @param unit      The unit to use when formatting the relative
      *                  date, e.g. UDAT_REL_UNIT_WEEK,
@@ -470,6 +615,29 @@
             UErrorCode& status) const;
 
     /**
+     * Format a combination of URelativeDateTimeUnit and numeric offset
+     * using a text style if possible, e.g. "last week", "this week",
+     * "next week", "yesterday", "tomorrow". Falls back to numeric
+     * style if no appropriate text term is available for the specified
+     * offset in the object's locale.
+     *
+     * This method returns a FormattedRelativeDateTime, which exposes more
+     * information than the String returned by format().
+     *
+     * @param offset    The signed offset for the specified unit.
+     * @param unit      The unit to use when formatting the relative
+     *                  date, e.g. UDAT_REL_UNIT_WEEK,
+     *                  UDAT_REL_UNIT_FRIDAY.
+     * @param status    ICU error code returned here.
+     * @return          The formatted relative datetime
+     * @draft ICU 64
+     */
+    FormattedRelativeDateTime formatToValue(
+            double offset,
+            URelativeDateTimeUnit unit,
+            UErrorCode& status) const;
+
+    /**
      * Combines a relative date string and a time string in this object's
      * locale. This is done with the same date-time separator used for the
      * default calendar in this locale.
@@ -520,7 +688,43 @@
             NumberFormat *nfToAdopt,
             BreakIterator *brkIter,
             UErrorCode &status);
-    void adjustForContext(UnicodeString &) const;
+    UnicodeString& adjustForContext(UnicodeString &) const;
+    UBool checkNoAdjustForContext(UErrorCode& status) const;
+
+    template<typename F, typename... Args>
+    UnicodeString& doFormat(
+            F callback,
+            UnicodeString& appendTo,
+            UErrorCode& status,
+            Args... args) const;
+
+    template<typename F, typename... Args>
+    FormattedRelativeDateTime doFormatToValue(
+            F callback,
+            UErrorCode& status,
+            Args... args) const;
+
+    void formatImpl(
+            double quantity,
+            UDateDirection direction,
+            UDateRelativeUnit unit,
+            FormattedRelativeDateTimeData& output,
+            UErrorCode& status) const;
+    void formatAbsoluteImpl(
+            UDateDirection direction,
+            UDateAbsoluteUnit unit,
+            FormattedRelativeDateTimeData& output,
+            UErrorCode& status) const;
+    void formatNumericImpl(
+            double offset,
+            URelativeDateTimeUnit unit,
+            FormattedRelativeDateTimeData& output,
+            UErrorCode& status) const;
+    void formatRelativeImpl(
+            double offset,
+            URelativeDateTimeUnit unit,
+            FormattedRelativeDateTimeData& output,
+            UErrorCode& status) const;
 };
 
 U_NAMESPACE_END
diff --git a/icu4c/source/i18n/unicode/uformattedvalue.h b/icu4c/source/i18n/unicode/uformattedvalue.h
index e4d0002..4dff30f 100644
--- a/icu4c/source/i18n/unicode/uformattedvalue.h
+++ b/icu4c/source/i18n/unicode/uformattedvalue.h
@@ -60,6 +60,13 @@
      */
     UFIELD_CATEGORY_LIST,
 
+    /**
+     * For fields in URelativeDateTimeFormatterField (ureldatefmt.h), from ICU 64.
+     *
+     * @draft ICU 64
+     */
+    UFIELD_CATEGORY_RELATIVE_DATETIME,
+
 #ifndef U_HIDE_INTERNAL_API
     /** @internal */
     UFIELD_CATEGORY_COUNT
diff --git a/icu4c/source/i18n/unicode/unumberformatter.h b/icu4c/source/i18n/unicode/unumberformatter.h
index 96bbbd2..7b520cb 100644
--- a/icu4c/source/i18n/unicode/unumberformatter.h
+++ b/icu4c/source/i18n/unicode/unumberformatter.h
@@ -551,15 +551,17 @@
 
 
 /**
- * Returns a representation of a UFormattedNumber as a UFormattedValue, which can be
- * subsequently passed to any API requiring that type.
+ * Returns a representation of a UFormattedNumber as a UFormattedValue,
+ * which can be subsequently passed to any API requiring that type.
  *
- * The returned object is owned by the UFormattedNumber and is valid only as long as the
- * UFormattedNumber is present and unchanged in memory.
+ * The returned object is owned by the UFormattedNumber and is valid
+ * only as long as the UFormattedNumber is present and unchanged in memory.
  *
- * @param uresult The object containing the formatted number.
+ * You can think of this method as a cast between types.
+ *
+ * @param uresult The object containing the formatted string.
  * @param ec Set if an error occurs.
- * @return A representation of the given UFormattedNumber as a UFormattedValue.
+ * @return A UFormattedValue owned by the input object.
  * @draft ICU 64
  */
 U_DRAFT const UFormattedValue* U_EXPORT2
diff --git a/icu4c/source/i18n/unicode/ureldatefmt.h b/icu4c/source/i18n/unicode/ureldatefmt.h
index 0fde188..9140c55 100644
--- a/icu4c/source/i18n/unicode/ureldatefmt.h
+++ b/icu4c/source/i18n/unicode/ureldatefmt.h
@@ -17,6 +17,7 @@
 #include "unicode/unum.h"
 #include "unicode/udisplaycontext.h"
 #include "unicode/localpointer.h"
+#include "unicode/uformattedvalue.h"
 
 /**
  * \file
@@ -174,6 +175,27 @@
 #endif  /* U_HIDE_DEPRECATED_API */
 } URelativeDateTimeUnit;
 
+#ifndef U_HIDE_DRAFT_API
+/**
+ * FieldPosition and UFieldPosition selectors for format fields
+ * defined by RelativeDateTimeFormatter.
+ * @draft ICU 64
+ */
+typedef enum URelativeDateTimeFormatterField {
+    /**
+     * Represents a literal text string, like "tomorrow" or "days ago".
+     * @draft ICU 64
+     */
+    UDAT_REL_LITERAL_FIELD,
+    /**
+     * Represents a number quantity, like "3" in "3 days ago".
+     * @draft ICU 64
+     */
+    UDAT_REL_NUMERIC_FIELD,
+} URelativeDateTimeFormatterField;
+#endif // U_HIDE_DRAFT_API
+
+
 /**
  * Opaque URelativeDateTimeFormatter object for use in C programs.
  * @stable ICU 57
@@ -230,6 +252,53 @@
 U_STABLE void U_EXPORT2
 ureldatefmt_close(URelativeDateTimeFormatter *reldatefmt);
 
+
+struct UFormattedRelativeDateTime;
+/**
+ * Opaque struct to contain the results of a URelativeDateTimeFormatter operation.
+ * @draft ICU 64
+ */
+typedef struct UFormattedRelativeDateTime UFormattedRelativeDateTime;
+
+/**
+ * Creates an object to hold the result of a URelativeDateTimeFormatter
+ * operation. The object can be used repeatedly; it is cleared whenever
+ * passed to a format function.
+ *
+ * @param ec Set if an error occurs.
+ * @return A pointer needing ownership.
+ * @draft ICU 64
+ */
+U_DRAFT UFormattedRelativeDateTime* U_EXPORT2
+ureldatefmt_openResult(UErrorCode* ec);
+
+/**
+ * Returns a representation of a UFormattedRelativeDateTime as a UFormattedValue,
+ * which can be subsequently passed to any API requiring that type.
+ *
+ * The returned object is owned by the UFormattedRelativeDateTime and is valid
+ * only as long as the UFormattedRelativeDateTime is present and unchanged in memory.
+ *
+ * You can think of this method as a cast between types.
+ *
+ * @param ufrdt The object containing the formatted string.
+ * @param ec Set if an error occurs.
+ * @return A UFormattedValue owned by the input object.
+ * @draft ICU 64
+ */
+U_DRAFT const UFormattedValue* U_EXPORT2
+ureldatefmt_resultAsValue(const UFormattedRelativeDateTime* ufrdt, UErrorCode* ec);
+
+/**
+ * Releases the UFormattedRelativeDateTime created by ureldatefmt_openResult.
+ *
+ * @param ufrdt The object to release.
+ * @draft ICU 64
+ */
+U_DRAFT void U_EXPORT2
+ureldatefmt_closeResult(UFormattedRelativeDateTime* ufrdt);
+
+
 #if U_SHOW_CPLUSPLUS_API
 
 U_NAMESPACE_BEGIN
@@ -245,6 +314,17 @@
  */
 U_DEFINE_LOCAL_OPEN_POINTER(LocalURelativeDateTimeFormatterPointer, URelativeDateTimeFormatter, ureldatefmt_close);
 
+/**
+ * \class LocalUFormattedRelativeDateTimePointer
+ * "Smart pointer" class, closes a UFormattedRelativeDateTime via ureldatefmt_closeResult().
+ * For most methods see the LocalPointerBase base class.
+ *
+ * @see LocalPointerBase
+ * @see LocalPointer
+ * @draft ICU 64
+ */
+U_DEFINE_LOCAL_OPEN_POINTER(LocalUFormattedRelativeDateTimePointer, UFormattedRelativeDateTime, ureldatefmt_closeResult);
+
 U_NAMESPACE_END
 
 #endif
@@ -286,6 +366,37 @@
                     UErrorCode*           status);
 
 /**
+ * Format a combination of URelativeDateTimeUnit and numeric
+ * offset using a numeric style, e.g. "1 week ago", "in 1 week",
+ * "5 weeks ago", "in 5 weeks".
+ *
+ * @param reldatefmt
+ *          The URelativeDateTimeFormatter object specifying the
+ *          format conventions.
+ * @param offset
+ *          The signed offset for the specified unit. This will
+ *          be formatted according to this object's UNumberFormat
+ *          object.
+ * @param unit
+ *          The unit to use when formatting the relative
+ *          date, e.g. UDAT_REL_UNIT_WEEK, UDAT_REL_UNIT_FRIDAY.
+ * @param result
+ *          A pointer to a UFormattedRelativeDateTime to populate.
+ * @param status
+ *          A pointer to a UErrorCode to receive any errors. In
+ *          case of error status, the contents of result are
+ *          undefined.
+ * @draft ICU 64
+ */
+U_DRAFT void U_EXPORT2
+ureldatefmt_formatNumericToResult(
+    const URelativeDateTimeFormatter* reldatefmt,
+    double                            offset,
+    URelativeDateTimeUnit             unit,
+    UFormattedRelativeDateTime*       result,
+    UErrorCode*                       status);
+
+/**
  * Format a combination of URelativeDateTimeUnit and numeric offset
  * using a text style if possible, e.g. "last week", "this week",
  * "next week", "yesterday", "tomorrow". Falls back to numeric
@@ -322,6 +433,40 @@
                     UErrorCode*           status);
 
 /**
+ * Format a combination of URelativeDateTimeUnit and numeric offset
+ * using a text style if possible, e.g. "last week", "this week",
+ * "next week", "yesterday", "tomorrow". Falls back to numeric
+ * style if no appropriate text term is available for the specified
+ * offset in the object's locale.
+ *
+ * This method populates a UFormattedRelativeDateTime, which exposes more
+ * information than the string populated by format().
+ *
+ * @param reldatefmt
+ *          The URelativeDateTimeFormatter object specifying the
+ *          format conventions.
+ * @param offset
+ *          The signed offset for the specified unit.
+ * @param unit
+ *          The unit to use when formatting the relative
+ *          date, e.g. UDAT_REL_UNIT_WEEK, UDAT_REL_UNIT_FRIDAY.
+ * @param result
+ *          A pointer to a UFormattedRelativeDateTime to populate.
+ * @param status
+ *          A pointer to a UErrorCode to receive any errors. In
+ *          case of error status, the contents of result are
+ *          undefined.
+ * @draft ICU 64
+ */
+U_DRAFT void U_EXPORT2
+ureldatefmt_formatToResult(
+    const URelativeDateTimeFormatter* reldatefmt,
+    double                            offset,
+    URelativeDateTimeUnit             unit,
+    UFormattedRelativeDateTime*       result,
+    UErrorCode*                       status);
+
+/**
  * Combines a relative date string and a time string in this object's
  * locale. This is done with the same date-time separator used for the
  * default calendar in this locale to produce a result such as
diff --git a/icu4c/source/test/cintltst/crelativedateformattest.c b/icu4c/source/test/cintltst/crelativedateformattest.c
index 13fe42e..46c98a7 100644
--- a/icu4c/source/test/cintltst/crelativedateformattest.c
+++ b/icu4c/source/test/cintltst/crelativedateformattest.c
@@ -16,9 +16,12 @@
 #include "unicode/ustring.h"
 #include "cintltst.h"
 #include "cmemory.h"
+#include "cformtst.h"
 
 static void TestRelDateFmt(void);
+static void TestNumericField(void);
 static void TestCombineDateTime(void);
+static void TestFields(void);
 
 void addRelativeDateFormatTest(TestNode** root);
 
@@ -27,12 +30,20 @@
 void addRelativeDateFormatTest(TestNode** root)
 {
     TESTCASE(TestRelDateFmt);
+    TESTCASE(TestNumericField);
     TESTCASE(TestCombineDateTime);
+    TESTCASE(TestFields);
 }
 
 static const double offsets[] = { -5.0, -2.2, -2.0, -1.0, -0.7, -0.0, 0.0, 0.7, 1.0, 2.0, 5.0 };
 enum { kNumOffsets = UPRV_LENGTHOF(offsets) };
 
+typedef struct {
+    int32_t field;
+    int32_t beginPos;
+    int32_t endPos;
+} FieldsDat;
+
 static const char* en_decDef_long_midSent_sec[kNumOffsets*2] = {
 /*  text                    numeric */
     "5 seconds ago",        "5 seconds ago",      /* -5   */
@@ -48,6 +59,21 @@
     "in 5 seconds",         "in 5 seconds"        /*  5   */
 };
 
+static const FieldsDat en_attrDef_long_midSent_sec[kNumOffsets*2] = {
+/*  text           numeric           text                    numeric */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "5 seconds ago",        "5 seconds ago",       -5   */
+    {UDAT_REL_NUMERIC_FIELD,  0,  3}, {UDAT_REL_NUMERIC_FIELD,  0,  3}, /* "2.2 seconds ago",      "2.2 seconds ago",     -2.2 */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "2 seconds ago",        "2 seconds ago",       -2   */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "1 second ago",         "1 second ago",        -1   */
+    {UDAT_REL_NUMERIC_FIELD,  0,  3}, {UDAT_REL_NUMERIC_FIELD,  0,  3}, /* "0.7 seconds ago",      "0.7 seconds ago",     -0.7 */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "now",                  "0 seconds ago",        -0  */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "now",                  "in 0 seconds",         0   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  6}, {UDAT_REL_NUMERIC_FIELD,  3,  6}, /* "in 0.7 seconds",       "in 0.7 seconds",       0.7 */
+    {UDAT_REL_NUMERIC_FIELD,  3,  4}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 1 second",          "in 1 second",          1   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  4}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 2 seconds",         "in 2 seconds",         2   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  4}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 5 seconds",         "in 5 seconds"          5   */
+};
+
 static const char* en_decDef_long_midSent_week[kNumOffsets*2] = {
 /*  text                    numeric */
     "5 weeks ago",          "5 weeks ago",        /* -5   */
@@ -63,6 +89,21 @@
     "in 5 weeks",           "in 5 weeks"          /*  5   */
 };
 
+static const FieldsDat en_attrDef_long_midSent_week[kNumOffsets*2] = {
+/*  text           numeric           text                    numeric */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "5 weeks ago",          "5 weeks ago",         -5   */
+    {UDAT_REL_NUMERIC_FIELD,  0,  3}, {UDAT_REL_NUMERIC_FIELD,  0,  3}, /* "2.2 weeks ago",        "2.2 weeks ago",       -2.2 */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "2 weeks ago",          "2 weeks ago",         -2   */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "last week",            "1 week ago",          -1   */
+    {UDAT_REL_NUMERIC_FIELD,  0,  3}, {UDAT_REL_NUMERIC_FIELD,  0,  3}, /* "0.7 weeks ago",        "0.7 weeks ago",       -0.7 */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "this week",            "0 weeks ago",          -0  */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "this week",            "in 0 weeks",           0   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  6}, {UDAT_REL_NUMERIC_FIELD,  3,  6}, /* "in 0.7 weeks",         "in 0.7 weeks",         0.7 */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "next week",            "in 1 week",            1   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  4}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 2 weeks",           "in 2 weeks",           2   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  4}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 5 weeks",           "in 5 weeks"            5   */
+};
+
 static const char* en_dec0_long_midSent_week[kNumOffsets*2] = {
 /*  text                    numeric */
     "5 weeks ago",          "5 weeks ago",        /* -5   */
@@ -78,6 +119,21 @@
     "in 5 weeks",           "in 5 weeks"          /*  5   */
 };
 
+static const FieldsDat en_attr0_long_midSent_week[kNumOffsets*2] = {
+/*  text           numeric           text                    numeric */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "5 weeks ago",          "5 weeks ago",         -5   */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "2 weeks ago",          "2 weeks ago",         -2.2 */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "2 weeks ago",          "2 weeks ago",         -2  */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "last week",            "1 week ago",          -1   */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "0 weeks ago",          "0 weeks ago",         -0.7 */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "this week",            "0 weeks ago",         -0  */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "this week",            "in 0 weeks",           0   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  4}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 0 weeks",           "in 0 weeks",           0.7 */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "next week",            "in 1 week",            1   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  4}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 2 weeks",           "in 2 weeks",           2   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  4}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 5 weeks",           "in 5 weeks"            5   */
+};
+
 static const char* en_decDef_short_midSent_week[kNumOffsets*2] = {
 /*  text                    numeric */
     "5 wk. ago",            "5 wk. ago",          /* -5   */
@@ -93,6 +149,21 @@
     "in 5 wk.",             "in 5 wk."            /*  5   */
 };
 
+static const FieldsDat en_attrDef_short_midSent_week[kNumOffsets*2] = {
+/*  text           numeric           text                    numeric */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "5 wk. ago",            "5 wk. ago",           -5   */
+    {UDAT_REL_NUMERIC_FIELD,  0,  3}, {UDAT_REL_NUMERIC_FIELD,  0,  3}, /* "2.2 wk. ago",          "2.2 wk. ago",         -2.2 */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "2 wk. ago",            "2 wk. ago",           -2   */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "last wk.",             "1 wk. ago",           -1   */
+    {UDAT_REL_NUMERIC_FIELD,  0,  3}, {UDAT_REL_NUMERIC_FIELD,  0,  3}, /* "0.7 wk. ago",          "0.7 wk. ago",         -0.7 */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "this wk.",             "0 wk. ago",           -0   */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "this wk.",             "in 0 wk.",             0   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  6}, {UDAT_REL_NUMERIC_FIELD,  3,  6}, /* "in 0.7 wk.",           "in 0.7 wk.",           0.7 */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "next wk.",             "in 1 wk.",             1   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  4}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 2 wk.",             "in 2 wk.",             2   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  4}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 5 wk.",             "in 5 wk."              5   */
+};
+
 static const char* en_decDef_long_midSent_min[kNumOffsets*2] = {
 /*  text                    numeric */
     "5 minutes ago",        "5 minutes ago",      /* -5   */
@@ -108,6 +179,21 @@
     "in 5 minutes",         "in 5 minutes"        /*  5   */
 };
 
+static const FieldsDat en_attrDef_long_midSent_min[kNumOffsets*2] = {
+/*  text           numeric           text                    numeric */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "5 minutes ago",        "5 minutes ago",       -5   */
+    {UDAT_REL_NUMERIC_FIELD,  0,  3}, {UDAT_REL_NUMERIC_FIELD,  0,  3}, /* "2.2 minutes ago",      "2.2 minutes ago",     -2.2 */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "2 minutes ago",        "2 minutes ago",       -2   */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "1 minute ago",         "1 minute ago",        -1   */
+    {UDAT_REL_NUMERIC_FIELD,  0,  3}, {UDAT_REL_NUMERIC_FIELD,  0,  3}, /* "0.7 minutes ago",      "0.7 minutes ago",     -0.7 */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "0 minutes ago",        "0 minutes ago",       -0   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  4}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 0 minutes",         "in 0 minutes",         0   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  6}, {UDAT_REL_NUMERIC_FIELD,  3,  6}, /* "in 0.7 minutes",       "in 0.7 minutes",       0.7 */
+    {UDAT_REL_NUMERIC_FIELD,  3,  4}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 1 minute",          "in 1 minute",          1   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  4}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 2 minutes",         "in 2 minutes",         2   */
+    {UDAT_REL_NUMERIC_FIELD,  3,  4}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 5 minutes",         "in 5 minutes"          5   */
+};
+
 static const char* en_dec0_long_midSent_tues[kNumOffsets*2] = {
 /*  text                    numeric */
     "5 Tuesdays ago",       "5 Tuesdays ago",     /* -5   */
@@ -123,6 +209,21 @@
     "in 5 Tuesdays",        "in 5 Tuesdays",      /*  5   */
 };
 
+static const FieldsDat en_attr0_long_midSent_tues[kNumOffsets*2] = {
+/*  text           numeric           text                    numeric */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "5 Tuesdays ago",       "5 Tuesdays ago",      -5   */
+    { -1, -1, -1},                    { -1, -1, -1},                    /* ""            ,         ""            ,        -2.2 */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "2 Tuesdays ago",       "2 Tuesdays ago",      -2   */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "last Tuesday",         "1 Tuesday ago",       -1   */
+    { -1, -1, -1},                    { -1, -1, -1},                    /* ""            ,         ""            ,        -0.7 */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  0,  1}, /* "this Tuesday",         "0 Tuesdays ago",      -0   */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "this Tuesday",         "in 0 Tuesdays",        0   */
+    { -1, -1, -1},                    { -1, -1, -1},                    /* ""            ,         ""            ,         0.7 */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "next Tuesday",         "in 1 Tuesday",         1   */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 2 Tuesdays",        "in 2 Tuesdays",        2   */
+    {UDAT_REL_NUMERIC_FIELD,  0,  1}, {UDAT_REL_NUMERIC_FIELD,  3,  4}, /* "in 5 Tuesdays",        "in 5 Tuesdays",        5   */
+};
+
 static const char* fr_decDef_long_midSent_day[kNumOffsets*2] = {
 /*  text                    numeric */
     "il y a 5 jours",       "il y a 5 jours",     /* -5   */
@@ -138,6 +239,21 @@
     "dans 5 jours",         "dans 5 jours"        /*  5   */
 };
 
+static const FieldsDat fr_attrDef_long_midSent_day[kNumOffsets*2] = {
+/*  text           numeric           text                    numeric */
+    {UDAT_REL_NUMERIC_FIELD,  7,  8}, {UDAT_REL_NUMERIC_FIELD,  7,  8}, /* "il y a 5 jours",       "il y a 5 jours",      -5   */
+    {UDAT_REL_NUMERIC_FIELD,  7, 10}, {UDAT_REL_NUMERIC_FIELD,  7, 10}, /* "il y a 2,2 jours",     "il y a 2,2 jours",    -2.2 */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  7,  8}, /* "avant-hier",           "il y a 2 jours",      -2   */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  7,  8}, /* "hier",                 "il y a 1 jour",       -1   */
+    {UDAT_REL_NUMERIC_FIELD,  7, 10}, {UDAT_REL_NUMERIC_FIELD,  7, 10}, /* "il y a 0,7 jour",      "il y a 0,7 jour",     -0.7 */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  7,  8}, /* "aujourd\\u2019hui",    "il y a 0 jour",       -0   */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  5,  6}, /* "aujourd\\u2019hui",    "dans 0 jour",          0   */
+    {UDAT_REL_NUMERIC_FIELD,  5,  8}, {UDAT_REL_NUMERIC_FIELD,  5,  8}, /* "dans 0,7 jour",        "dans 0,7 jour",        0.7 */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  5,  6}, /* "demain",               "dans 1 jour",          1   */
+    { -1, -1, -1},                    {UDAT_REL_NUMERIC_FIELD,  5,  6}, /* "apr\\u00E8s-demain",   "dans 2 jours",         2   */
+    {UDAT_REL_NUMERIC_FIELD,  5,  6}, {UDAT_REL_NUMERIC_FIELD,  5,  6}, /* "dans 5 jours",         "dans 5 jours"          5   */
+};
+
 static const char* ak_decDef_long_stdAlon_sec[kNumOffsets*2] = { // falls back to root
 /*  text                    numeric */
     "-5 s",                 "-5 s",               /* -5   */
@@ -153,6 +269,20 @@
     "+5 s",                 "+5 s",               /*  5   */
 };
 
+static const FieldsDat ak_attrDef_long_stdAlon_sec[kNumOffsets*2] = {
+    {UDAT_REL_NUMERIC_FIELD, 1, 2}, {UDAT_REL_NUMERIC_FIELD, 1, 2},
+    {UDAT_REL_NUMERIC_FIELD, 1, 4}, {UDAT_REL_NUMERIC_FIELD, 1, 4},
+    {UDAT_REL_NUMERIC_FIELD, 1, 2}, {UDAT_REL_NUMERIC_FIELD, 1, 2},
+    {UDAT_REL_NUMERIC_FIELD, 1, 2}, {UDAT_REL_NUMERIC_FIELD, 1, 2},
+    {UDAT_REL_NUMERIC_FIELD, 1, 4}, {UDAT_REL_NUMERIC_FIELD, 1, 4},
+    { -1, -1, -1},                  {UDAT_REL_NUMERIC_FIELD, 1, 2},
+    { -1, -1, -1},                  {UDAT_REL_NUMERIC_FIELD, 1, 2},
+    {UDAT_REL_NUMERIC_FIELD, 1, 4}, {UDAT_REL_NUMERIC_FIELD, 1, 4},
+    {UDAT_REL_NUMERIC_FIELD, 1, 2}, {UDAT_REL_NUMERIC_FIELD, 1, 2},
+    {UDAT_REL_NUMERIC_FIELD, 1, 2}, {UDAT_REL_NUMERIC_FIELD, 1, 2},
+    {UDAT_REL_NUMERIC_FIELD, 1, 2}, {UDAT_REL_NUMERIC_FIELD, 1, 2},
+};
+
 static const char* enIN_decDef_short_midSent_weds[kNumOffsets*2] = {
 /*  text                    numeric */
     "5 Wed. ago",           "5 Wed. ago",         /* -5   */
@@ -168,6 +298,20 @@
     "in 5  Wed.",           "in 5 Wed."           /*  5   */
 };
 
+static const FieldsDat enIN_attrDef_short_midSent_weds[kNumOffsets*2] = {
+    {UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1},
+    {UDAT_REL_NUMERIC_FIELD, 0, 3}, {UDAT_REL_NUMERIC_FIELD, 0, 3},
+    {UDAT_REL_NUMERIC_FIELD, 0, 1}, {UDAT_REL_NUMERIC_FIELD, 0, 1},
+    { -1, -1, -1},                  {UDAT_REL_NUMERIC_FIELD, 0, 1},
+    {UDAT_REL_NUMERIC_FIELD, 0, 3}, {UDAT_REL_NUMERIC_FIELD, 0, 3},
+    { -1, -1, -1},                  {UDAT_REL_NUMERIC_FIELD, 0, 1},
+    { -1, -1, -1},                  {UDAT_REL_NUMERIC_FIELD, 3, 4},
+    {UDAT_REL_NUMERIC_FIELD, 3, 6}, {UDAT_REL_NUMERIC_FIELD, 3, 6},
+    { -1, -1, -1},                  {UDAT_REL_NUMERIC_FIELD, 3, 4},
+    {UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4},
+    {UDAT_REL_NUMERIC_FIELD, 3, 4}, {UDAT_REL_NUMERIC_FIELD, 3, 4},
+};
+
 typedef struct {
     const char*                         locale;
     int32_t                             decPlaces; /* fixed decimal places; -1 to use default num formatter */
@@ -175,19 +319,29 @@
     UDisplayContext                     capContext;
     URelativeDateTimeUnit               unit;
     const char **                       expectedResults; /* for the various offsets */
+    const FieldsDat*                    expectedAttributes;
 } RelDateTimeFormatTestItem;
 
 static const RelDateTimeFormatTestItem fmtTestItems[] = {
-    { "en", -1, UDAT_STYLE_LONG,  UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_SECOND,  en_decDef_long_midSent_sec  },
-    { "en", -1, UDAT_STYLE_LONG,  UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEEK,    en_decDef_long_midSent_week  },
-    { "en",  0, UDAT_STYLE_LONG,  UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEEK,    en_dec0_long_midSent_week    },
-    { "en", -1, UDAT_STYLE_SHORT, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEEK,    en_decDef_short_midSent_week },
-    { "en", -1, UDAT_STYLE_LONG,  UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_MINUTE,  en_decDef_long_midSent_min   },
-    { "en", -1, UDAT_STYLE_LONG,  UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_TUESDAY, en_dec0_long_midSent_tues    },
-    { "fr", -1, UDAT_STYLE_LONG,  UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_DAY,     fr_decDef_long_midSent_day   },
-    { "ak", -1, UDAT_STYLE_LONG,  UDISPCTX_CAPITALIZATION_FOR_STANDALONE,         UDAT_REL_UNIT_SECOND,  ak_decDef_long_stdAlon_sec   },
-    { "en_IN", -1, UDAT_STYLE_SHORT, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEDNESDAY, enIN_decDef_short_midSent_weds },
-    { NULL,  0, (UDateRelativeDateTimeFormatterStyle)0, (UDisplayContext)0, (URelativeDateTimeUnit)0, NULL } /* terminator */
+    { "en", -1, UDAT_STYLE_LONG,  UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_SECOND,
+      en_decDef_long_midSent_sec,   en_attrDef_long_midSent_sec },
+    { "en", -1, UDAT_STYLE_LONG,  UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEEK,
+      en_decDef_long_midSent_week,  en_attrDef_long_midSent_week},
+    { "en",  0, UDAT_STYLE_LONG,  UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEEK,
+      en_dec0_long_midSent_week,    en_attr0_long_midSent_week},
+    { "en", -1, UDAT_STYLE_SHORT, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEEK,
+      en_decDef_short_midSent_week, en_attrDef_short_midSent_week},
+    { "en", -1, UDAT_STYLE_LONG,  UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_MINUTE,
+      en_decDef_long_midSent_min,   en_attrDef_long_midSent_min},
+    { "en", -1, UDAT_STYLE_LONG,  UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_TUESDAY,
+      en_dec0_long_midSent_tues,    en_attr0_long_midSent_tues},
+    { "fr", -1, UDAT_STYLE_LONG,  UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_DAY,
+      fr_decDef_long_midSent_day,   fr_attrDef_long_midSent_day},
+    { "ak", -1, UDAT_STYLE_LONG,  UDISPCTX_CAPITALIZATION_FOR_STANDALONE,         UDAT_REL_UNIT_SECOND,
+      ak_decDef_long_stdAlon_sec,   ak_attrDef_long_stdAlon_sec},
+    { "en_IN", -1, UDAT_STYLE_SHORT, UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, UDAT_REL_UNIT_WEDNESDAY,
+      enIN_decDef_short_midSent_weds, enIN_attrDef_short_midSent_weds},
+    { NULL,  0, (UDateRelativeDateTimeFormatterStyle)0, (UDisplayContext)0, (URelativeDateTimeUnit)0, NULL, NULL } /* terminator */
 };
 
 enum { kUBufMax = 64, kBBufMax = 256 };
@@ -273,6 +427,154 @@
     }
 }
 
+static void TestNumericField()
+{
+    const RelDateTimeFormatTestItem *itemPtr;
+    log_verbose("\nTesting ureldatefmt_open(), ureldatefmt_formatForFields(), ureldatefmt_formatNumericForFields() with various parameters\n");
+    for (itemPtr = fmtTestItems; itemPtr->locale != NULL; itemPtr++) {
+        URelativeDateTimeFormatter *reldatefmt = NULL;
+        UNumberFormat* nfToAdopt = NULL;
+        UErrorCode status = U_ZERO_ERROR;
+        int32_t iOffset;
+
+        if (itemPtr->decPlaces >= 0) {
+            nfToAdopt = unum_open(UNUM_DECIMAL, NULL, 0, itemPtr->locale, NULL, &status);
+            if ( U_FAILURE(status) ) {
+                log_data_err("FAIL: unum_open(UNUM_DECIMAL, ...) for locale %s: %s\n", itemPtr->locale, myErrorName(status));
+                continue;
+            }
+            unum_setAttribute(nfToAdopt, UNUM_MIN_FRACTION_DIGITS, itemPtr->decPlaces);
+            unum_setAttribute(nfToAdopt, UNUM_MAX_FRACTION_DIGITS, itemPtr->decPlaces);
+            unum_setAttribute(nfToAdopt, UNUM_ROUNDING_MODE, UNUM_ROUND_DOWN);
+        }
+        reldatefmt = ureldatefmt_open(itemPtr->locale, nfToAdopt, itemPtr->width, itemPtr->capContext, &status);
+        if ( U_FAILURE(status) ) {
+            log_data_err("FAIL: ureldatefmt_open() for locale %s, decPlaces %d, width %d, capContext %d: %s\n",
+                    itemPtr->locale, itemPtr->decPlaces, (int)itemPtr->width, (int)itemPtr->capContext,
+                    myErrorName(status) );
+            continue;
+        }
+
+        for (iOffset = 0; iOffset < kNumOffsets; iOffset++) {
+            if (itemPtr->unit >= UDAT_REL_UNIT_SUNDAY && offsets[iOffset] != -1.0 && offsets[iOffset] != 0.0 && offsets[iOffset] != 1.0) {
+                continue; /* we do not currently have data for this */
+            }
+
+            /* Depend on the next one to verify the data */
+            status = U_ZERO_ERROR;
+            UFormattedRelativeDateTime* fv = ureldatefmt_openResult(&status);
+            if ( U_FAILURE(status) ) {
+                log_err("ureldatefmt_openResult fails, status %s\n", u_errorName(status));
+                continue;
+            }
+            ureldatefmt_formatToResult(reldatefmt, offsets[iOffset], itemPtr->unit, fv, &status);
+            if ( U_FAILURE(status) ) {
+                log_err("FAIL: ureldatefmt_formatForFields() for locale %s, decPlaces %d, width %d, capContext %d, offset %.2f, unit %d: %s\n",
+                    itemPtr->locale, itemPtr->decPlaces, (int)itemPtr->width, (int)itemPtr->capContext,
+                    offsets[iOffset], (int)itemPtr->unit, myErrorName(status) );
+            } else {
+                UChar ubufexp[kUBufMax];
+                int32_t ulenexp = u_unescape(itemPtr->expectedResults[iOffset*2], ubufexp, kUBufMax);
+                int32_t ulenget;
+                const UChar* ubufget = ufmtval_getString(ureldatefmt_resultAsValue(fv, &status), &ulenget, &status);
+                assertUEquals("String content", ubufexp, ubufget);
+                assertIntEquals("String length", ulenexp, ulenget);
+
+                FieldsDat expectedAttr = itemPtr->expectedAttributes[iOffset*2];
+                UConstrainedFieldPosition* cfpos = ucfpos_open(&status);
+                UBool foundNumeric = FALSE;
+                while (TRUE) {
+                    foundNumeric = ufmtval_nextPosition(ureldatefmt_resultAsValue(fv, &status), cfpos, &status);
+                    if (!foundNumeric) {
+                        break;
+                    }
+                    if (ucfpos_getCategory(cfpos, &status) == UFIELD_CATEGORY_RELATIVE_DATETIME
+                        && ucfpos_getField(cfpos, &status) == UDAT_REL_NUMERIC_FIELD) {
+                        break;
+                    }
+                }
+                assertSuccess("Looking for numeric", &status);
+                int32_t beginPos, endPos;
+                ucfpos_getIndexes(cfpos, &beginPos, &endPos, &status);
+                if (expectedAttr.field == -1) {
+                    if (foundNumeric) {
+                        log_err("ureldatefmt_formatForFields as \"%s\"; expect no field, but got %d\n",
+                                itemPtr->expectedResults[iOffset*2],
+                                ucfpos_getField(cfpos, &status));
+                    }
+                } else {
+                    if (!foundNumeric ||
+                        beginPos != expectedAttr.beginPos ||
+                        endPos != expectedAttr.endPos) {
+                        log_err("ureldatefmt_formatForFields as \"%s\"; expect field %d range %d-%d, get range %d-%d\n",
+                                itemPtr->expectedResults[iOffset*2],
+                                expectedAttr.field, expectedAttr.beginPos, expectedAttr.endPos,
+                                beginPos, endPos);
+                    }
+                }
+                ucfpos_close(cfpos);
+            }
+
+            if (itemPtr->unit >= UDAT_REL_UNIT_SUNDAY) {
+                ureldatefmt_closeResult(fv);
+                continue; /* we do not currently have numeric-style data for this */
+            }
+
+            /* Depend on the next one to verify the data */
+            status = U_ZERO_ERROR;
+            ureldatefmt_formatNumericToResult(reldatefmt, offsets[iOffset], itemPtr->unit, fv, &status);
+            if ( U_FAILURE(status) ) {
+                log_err("FAIL: ureldatefmt_formatNumericForFields() for locale %s, decPlaces %d, width %d, capContext %d, offset %.2f, unit %d: %s\n",
+                    itemPtr->locale, itemPtr->decPlaces, (int)itemPtr->width, (int)itemPtr->capContext,
+                    offsets[iOffset], (int)itemPtr->unit, myErrorName(status) );
+            } else {
+                UChar ubufexp[kUBufMax];
+                int32_t ulenexp = u_unescape(itemPtr->expectedResults[iOffset*2 + 1], ubufexp, kUBufMax);
+                int32_t ulenget;
+                const UChar* ubufget = ufmtval_getString(ureldatefmt_resultAsValue(fv, &status), &ulenget, &status);
+                assertUEquals("String content", ubufexp, ubufget);
+                assertIntEquals("String length", ulenexp, ulenget);
+
+                FieldsDat expectedAttr = itemPtr->expectedAttributes[iOffset*2 + 1];
+                UConstrainedFieldPosition* cfpos = ucfpos_open(&status);
+                UBool foundNumeric = FALSE;
+                while (TRUE) {
+                    foundNumeric = ufmtval_nextPosition(ureldatefmt_resultAsValue(fv, &status), cfpos, &status);
+                    if (!foundNumeric) {
+                        break;
+                    }
+                    if (ucfpos_getCategory(cfpos, &status) == UFIELD_CATEGORY_RELATIVE_DATETIME
+                        && ucfpos_getField(cfpos, &status) == UDAT_REL_NUMERIC_FIELD) {
+                        break;
+                    }
+                }
+                assertSuccess("Looking for numeric", &status);
+                int32_t beginPos, endPos;
+                ucfpos_getIndexes(cfpos, &beginPos, &endPos, &status);
+                if (expectedAttr.field == -1) {
+                    if (foundNumeric) {
+                        log_err("ureldatefmt_formatForFields as \"%s\"; expect no field, but got %d rang %d-%d\n",
+                                itemPtr->expectedResults[iOffset*2],
+                                ucfpos_getField(cfpos, &status), beginPos, endPos);
+                    }
+                } else {
+                    if (!foundNumeric ||
+                        (beginPos != expectedAttr.beginPos || endPos != expectedAttr.endPos)) {
+                        log_err("ureldatefmt_formatForFields as \"%s\"; expect field %d range %d-%d, get field %d range %d-%d\n",
+                                itemPtr->expectedResults[iOffset*2 + 1],
+                                expectedAttr.field, expectedAttr.beginPos, expectedAttr.endPos,
+                                ucfpos_getField(cfpos, &status), beginPos, endPos);
+                    }
+                }
+                ucfpos_close(cfpos);
+            }
+            ureldatefmt_closeResult(fv);
+        }
+
+        ureldatefmt_close(reldatefmt);
+    }
+}
+
 typedef struct {
     const char*                         locale;
     UDateRelativeDateTimeFormatterStyle width;
@@ -341,4 +643,54 @@
     }
 }
 
+static void TestFields() {
+    UErrorCode ec = U_ZERO_ERROR;
+    URelativeDateTimeFormatter* fmt = ureldatefmt_open(
+        "en-us",
+        NULL,
+        UDAT_STYLE_SHORT,
+        UDISPCTX_CAPITALIZATION_NONE,
+        &ec);
+    assertSuccess("Creating RelDTFmt", &ec);
+    UFormattedRelativeDateTime* frdt = ureldatefmt_openResult(&ec);
+    assertSuccess("Creating FmtVal", &ec);
+
+    ureldatefmt_formatNumericToResult(fmt, -50, UDAT_REL_UNIT_SATURDAY, frdt, &ec);
+    assertSuccess("formatNumeric", &ec);
+    {
+        const UFormattedValue* fv = ureldatefmt_resultAsValue(frdt, &ec);
+        assertSuccess("Should convert without error", &ec);
+        static const UFieldPositionWithCategory expectedFieldPositions[] = {
+            // category, field, begin index, end index
+            {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 0, 2},
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD, 0, 2},
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 3, 11}};
+        checkMixedFormattedValue(
+            "FormattedRelativeDateTime as FormattedValue (numeric)",
+            fv,
+            u"50 Sat. ago",
+            expectedFieldPositions,
+            UPRV_LENGTHOF(expectedFieldPositions));
+    }
+
+    ureldatefmt_formatToResult(fmt, -1, UDAT_REL_UNIT_WEEK, frdt, &ec);
+    assertSuccess("format", &ec);
+    {
+        const UFormattedValue* fv = ureldatefmt_resultAsValue(frdt, &ec);
+        assertSuccess("Should convert without error", &ec);
+        static const UFieldPositionWithCategory expectedFieldPositions[] = {
+            // category, field, begin index, end index
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 0, 8}};
+        checkMixedFormattedValue(
+            "FormattedRelativeDateTime as FormattedValue (relative)",
+            fv,
+            u"last wk.",
+            expectedFieldPositions,
+            UPRV_LENGTHOF(expectedFieldPositions));
+    }
+
+    ureldatefmt_closeResult(frdt);
+    ureldatefmt_close(fmt);
+}
+
 #endif /* #if !UCONFIG_NO_FORMATTING && !UCONFIG_NO_BREAK_ITERATION */
diff --git a/icu4c/source/test/depstest/dependencies.txt b/icu4c/source/test/depstest/dependencies.txt
index 93c7919..d2682ab 100644
--- a/icu4c/source/test/depstest/dependencies.txt
+++ b/icu4c/source/test/depstest/dependencies.txt
@@ -1005,7 +1005,7 @@
     # messageformat
     choicfmt.o msgfmt.o plurfmt.o selfmt.o umsg.o
   deps
-    decnumber formattable format units numberformatter numberparser
+    decnumber formattable format units numberformatter numberparser formatted_value_sbimpl
     listformatter
     dayperiodrules
     collation collation_builder  # for rbnf
@@ -1051,6 +1051,11 @@
   deps
     formatted_value format uvector32
 
+group: formatted_value_sbimpl
+    formattedval_sbimpl.o
+  deps
+    number_representation
+
 group: format
     format.o fphdlimp.o fpositer.o ufieldpositer.o
   deps
diff --git a/icu4c/source/test/intltest/numbertest_stringbuilder.cpp b/icu4c/source/test/intltest/numbertest_stringbuilder.cpp
index 3106bed..eaf9a7a 100644
--- a/icu4c/source/test/intltest/numbertest_stringbuilder.cpp
+++ b/icu4c/source/test/intltest/numbertest_stringbuilder.cpp
@@ -184,8 +184,8 @@
         assertSuccess("Appending to sb", status);
         assertEquals("Reference string copied twice", str.length() * 2, sb.length());
         for (int32_t i = 0; i < str.length(); i++) {
-            assertEquals("Null field first", UNUM_FIELD_COUNT, sb.fieldAt(i));
-            assertEquals("Currency field second", UNUM_CURRENCY_FIELD, sb.fieldAt(i + str.length()));
+            assertEquals("Null field first", (Field) UNUM_FIELD_COUNT, sb.fieldAt(i));
+            assertEquals("Currency field second", (Field) UNUM_CURRENCY_FIELD, sb.fieldAt(i + str.length()));
         }
 
         // Very basic FieldPosition test. More robust tests happen in NumberFormatTest.
@@ -200,7 +200,7 @@
             sb.insertCodePoint(2, 100, UNUM_INTEGER_FIELD, status);
             assertSuccess("Inserting code point into sb", status);
             assertEquals("New length", str.length() * 2 + 1, sb.length());
-            assertEquals("Integer field", UNUM_INTEGER_FIELD, sb.fieldAt(2));
+            assertEquals("Integer field", (Field) UNUM_INTEGER_FIELD, sb.fieldAt(2));
         }
 
         NumberStringBuilder old(sb);
@@ -210,7 +210,7 @@
         int32_t numCurr = 0;
         int32_t numInt = 0;
         for (int32_t i = 0; i < sb.length(); i++) {
-            UNumberFormatFields field = sb.fieldAt(i);
+            Field field = sb.fieldAt(i);
             assertEquals("Field should equal location in old", old.fieldAt(i % old.length()), field);
             if (field == UNUM_FIELD_COUNT) {
                 numNull++;
diff --git a/icu4c/source/test/intltest/reldatefmttest.cpp b/icu4c/source/test/intltest/reldatefmttest.cpp
index 4481f42..f95e4bb 100644
--- a/icu4c/source/test/intltest/reldatefmttest.cpp
+++ b/icu4c/source/test/intltest/reldatefmttest.cpp
@@ -22,7 +22,9 @@
 #include "unicode/localpointer.h"
 #include "unicode/numfmt.h"
 #include "unicode/reldatefmt.h"
+#include "unicode/rbnf.h"
 #include "cmemory.h"
+#include "itformat.h"
 
 static const char *DirectionStr(UDateDirection direction);
 static const char *RelativeUnitStr(UDateRelativeUnit unit);
@@ -741,7 +743,7 @@
 };
 
 
-class RelativeDateTimeFormatterTest : public IntlTest {
+class RelativeDateTimeFormatterTest : public IntlTestWithFieldPosition {
 public:
     RelativeDateTimeFormatterTest() {
     }
@@ -768,6 +770,9 @@
     void TestFormat();
     void TestFormatNumeric();
     void TestLocales();
+    void TestFields();
+    void TestRBNF();
+
     void RunTest(
             const Locale& locale,
             const WithQuantityExpected* expectedResults,
@@ -858,6 +863,8 @@
     TESTCASE_AUTO(TestFormat);
     TESTCASE_AUTO(TestFormatNumeric);
     TESTCASE_AUTO(TestLocales);
+    TESTCASE_AUTO(TestFields);
+    TESTCASE_AUTO(TestRBNF);
     TESTCASE_AUTO_END;
 }
 
@@ -1313,6 +1320,137 @@
     }
 }
 
+void RelativeDateTimeFormatterTest::TestFields() {
+    IcuTestErrorCode status(*this, "TestFields");
+
+    RelativeDateTimeFormatter fmt("en-US", status);
+
+    {
+        const char16_t* message = u"automatic absolute unit";
+        FormattedRelativeDateTime fv = fmt.formatToValue(1, UDAT_REL_UNIT_DAY, status);
+        const char16_t* expectedString = u"tomorrow";
+        static const UFieldPositionWithCategory expectedFieldPositions[] = {
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 0, 8}};
+        checkMixedFormattedValue(
+            message,
+            fv,
+            expectedString,
+            expectedFieldPositions,
+            UPRV_LENGTHOF(expectedFieldPositions));
+    }
+    {
+        const char16_t* message = u"automatic numeric unit";
+        FormattedRelativeDateTime fv = fmt.formatToValue(3, UDAT_REL_UNIT_DAY, status);
+        const char16_t* expectedString = u"in 3 days";
+        static const UFieldPositionWithCategory expectedFieldPositions[] = {
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 0, 2},
+            {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 3, 4},
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD, 3, 4},
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 5, 9}};
+        checkMixedFormattedValue(
+            message,
+            fv,
+            expectedString,
+            expectedFieldPositions,
+            UPRV_LENGTHOF(expectedFieldPositions));
+    }
+    {
+        const char16_t* message = u"manual absolute unit";
+        FormattedRelativeDateTime fv = fmt.formatToValue(UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_MONDAY, status);
+        const char16_t* expectedString = u"next Monday";
+        static const UFieldPositionWithCategory expectedFieldPositions[] = {
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 0, 11}};
+        checkMixedFormattedValue(
+            message,
+            fv,
+            expectedString,
+            expectedFieldPositions,
+            UPRV_LENGTHOF(expectedFieldPositions));
+    }
+    {
+        const char16_t* message = u"manual numeric unit";
+        FormattedRelativeDateTime fv = fmt.formatNumericToValue(1.5, UDAT_REL_UNIT_WEEK, status);
+        const char16_t* expectedString = u"in 1.5 weeks";
+        static const UFieldPositionWithCategory expectedFieldPositions[] = {
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 0, 2},
+            {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 3, 4},
+            {UFIELD_CATEGORY_NUMBER, UNUM_DECIMAL_SEPARATOR_FIELD, 4, 5},
+            {UFIELD_CATEGORY_NUMBER, UNUM_FRACTION_FIELD, 5, 6},
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD, 3, 6},
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 7, 12}};
+        checkMixedFormattedValue(
+            message,
+            fv,
+            expectedString,
+            expectedFieldPositions,
+            UPRV_LENGTHOF(expectedFieldPositions));
+    }
+    {
+        const char16_t* message = u"manual numeric resolved unit";
+        FormattedRelativeDateTime fv = fmt.formatToValue(12, UDAT_DIRECTION_LAST, UDAT_RELATIVE_HOURS, status);
+        const char16_t* expectedString = u"12 hours ago";
+        static const UFieldPositionWithCategory expectedFieldPositions[] = {
+            {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 0, 2},
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD, 0, 2},
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 3, 12}};
+        checkMixedFormattedValue(
+            message,
+            fv,
+            expectedString,
+            expectedFieldPositions,
+            UPRV_LENGTHOF(expectedFieldPositions));
+    }
+
+    // Test when the number field is at the end
+    fmt = RelativeDateTimeFormatter("sw", status);
+    {
+        const char16_t* message = u"numeric field at end";
+        FormattedRelativeDateTime fv = fmt.formatToValue(12, UDAT_REL_UNIT_HOUR, status);
+        const char16_t* expectedString = u"baada ya saa 12";
+        static const UFieldPositionWithCategory expectedFieldPositions[] = {
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 0, 12},
+            {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 13, 15},
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD, 13, 15}};
+        checkMixedFormattedValue(
+            message,
+            fv,
+            expectedString,
+            expectedFieldPositions,
+            UPRV_LENGTHOF(expectedFieldPositions));
+    }
+}
+
+void RelativeDateTimeFormatterTest::TestRBNF() {
+    IcuTestErrorCode status(*this, "TestRBNF");
+
+    LocalPointer<RuleBasedNumberFormat> rbnf(new RuleBasedNumberFormat(URBNF_SPELLOUT, "en-us", status));
+    RelativeDateTimeFormatter fmt("en-us", rbnf.orphan(), status);
+    UnicodeString result;
+    assertEquals("format (direction)", "in five seconds",
+        fmt.format(5, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_SECONDS, result, status));
+    assertEquals("formatNumeric", "one week ago",
+        fmt.formatNumeric(-1, UDAT_REL_UNIT_WEEK, result.remove(), status));
+    assertEquals("format (absolute)", "yesterday",
+        fmt.format(UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_DAY, result.remove(), status));
+    assertEquals("format (relative)", "in forty-two months",
+        fmt.format(42, UDAT_REL_UNIT_MONTH, result.remove(), status));
+
+    {
+        const char16_t* message = u"formatToValue (relative)";
+        FormattedRelativeDateTime fv = fmt.formatToValue(-100, UDAT_REL_UNIT_YEAR, status);
+        const char16_t* expectedString = u"one hundred years ago";
+        static const UFieldPositionWithCategory expectedFieldPositions[] = {
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD, 0, 11},
+            {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD, 12, 21}};
+        checkMixedFormattedValue(
+            message,
+            fv,
+            expectedString,
+            expectedFieldPositions,
+            UPRV_LENGTHOF(expectedFieldPositions));
+    }
+}
+
 static const char *kLast2 = "Last_2";
 static const char *kLast = "Last";
 static const char *kThis = "This";
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantAffixModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantAffixModifier.java
index fc881a2..c82d50d 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantAffixModifier.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantAffixModifier.java
@@ -2,7 +2,7 @@
 // License & terms of use: http://www.unicode.org/copyright.html#License
 package com.ibm.icu.impl.number;
 
-import com.ibm.icu.text.NumberFormat.Field;
+import java.text.Format.Field;
 
 /**
  * The canonical implementation of {@link Modifier}, containing a prefix and suffix string.
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantMultiFieldModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantMultiFieldModifier.java
index d533492..6732960 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantMultiFieldModifier.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantMultiFieldModifier.java
@@ -2,10 +2,9 @@
 // License & terms of use: http://www.unicode.org/copyright.html#License
 package com.ibm.icu.impl.number;
 
+import java.text.Format.Field;
 import java.util.Arrays;
 
-import com.ibm.icu.text.NumberFormat.Field;
-
 /**
  * An implementation of {@link Modifier} that allows for multiple types of fields in the same modifier.
  * Constructed based on the contents of two {@link NumberStringBuilder} instances (one for the prefix,
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencySpacingEnabledModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencySpacingEnabledModifier.java
index 2cf8758..61c04c5 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencySpacingEnabledModifier.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencySpacingEnabledModifier.java
@@ -2,6 +2,8 @@
 // License & terms of use: http://www.unicode.org/copyright.html#License
 package com.ibm.icu.impl.number;
 
+import java.text.Format.Field;
+
 import com.ibm.icu.text.DecimalFormatSymbols;
 import com.ibm.icu.text.NumberFormat;
 import com.ibm.icu.text.UnicodeSet;
@@ -122,7 +124,7 @@
         // NOTE: For prefix, output.fieldAt(index-1) gets the last field type in the prefix.
         // This works even if the last code point in the prefix is 2 code units because the
         // field value gets populated to both indices in the field array.
-        NumberFormat.Field affixField = (affix == PREFIX) ? output.fieldAt(index - 1)
+        Field affixField = (affix == PREFIX) ? output.fieldAt(index - 1)
                 : output.fieldAt(index);
         if (affixField != NumberFormat.Field.CURRENCY) {
             return 0;
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java
index d0e74bb..f3f4635 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java
@@ -2,8 +2,9 @@
 // License & terms of use: http://www.unicode.org/copyright.html#License
 package com.ibm.icu.impl.number;
 
+import java.text.Format.Field;
+
 import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.text.NumberFormat.Field;
 
 /**
  * A Modifier is an object that can be passed through the formatting pipeline until it is finally applied
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java
index d887561..4cdde30 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java
@@ -326,7 +326,7 @@
     }
 
     @Override
-    public boolean containsField(Field field) {
+    public boolean containsField(java.text.Format.Field field) {
         // This method is not currently used. (unsafe path not used in range formatting)
         assert false;
         return false;
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/NumberStringBuilder.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/NumberStringBuilder.java
index 5102db4..0380b0e 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/NumberStringBuilder.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/NumberStringBuilder.java
@@ -5,15 +5,14 @@
 import java.text.AttributedCharacterIterator;
 import java.text.AttributedString;
 import java.text.FieldPosition;
+import java.text.Format.Field;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
 import com.ibm.icu.impl.StaticUnicodeSets;
 import com.ibm.icu.text.ConstrainedFieldPosition;
-import com.ibm.icu.text.ConstrainedFieldPosition.ConstraintType;
 import com.ibm.icu.text.NumberFormat;
-import com.ibm.icu.text.NumberFormat.Field;
 import com.ibm.icu.text.UnicodeSet;
 
 /**
@@ -521,7 +520,7 @@
         ConstrainedFieldPosition cfpos = new ConstrainedFieldPosition();
         cfpos.constrainField(rawField);
         cfpos.setState(rawField, null, fp.getBeginIndex(), fp.getEndIndex());
-        if (nextPosition(cfpos)) {
+        if (nextPosition(cfpos, null)) {
             fp.setBeginIndex(cfpos.getStart());
             fp.setEndIndex(cfpos.getLimit());
             return true;
@@ -545,28 +544,38 @@
         return false;
     }
 
-    public AttributedCharacterIterator toCharacterIterator() {
+    public AttributedCharacterIterator toCharacterIterator(Field numericField) {
         ConstrainedFieldPosition cfpos = new ConstrainedFieldPosition();
         AttributedString as = new AttributedString(toString());
-        while (this.nextPosition(cfpos)) {
+        while (this.nextPosition(cfpos, numericField)) {
             // Backwards compatibility: field value = field
             as.addAttribute(cfpos.getField(), cfpos.getField(), cfpos.getStart(), cfpos.getLimit());
         }
         return as.getIterator();
     }
 
-    public boolean nextPosition(ConstrainedFieldPosition cfpos) {
-        if (cfpos.getConstraintType() == ConstraintType.CLASS
-                && !cfpos.getClassConstraint().isAssignableFrom(NumberFormat.Field.class)) {
-            return false;
+    static class NullField extends Field {
+        private static final long serialVersionUID = 1L;
+        static final NullField END = new NullField("end");
+        private NullField(String name) {
+            super(name);
         }
+    }
 
-        boolean isSearchingForField = (cfpos.getConstraintType() == ConstraintType.FIELD);
-
+    /**
+     * Implementation of nextPosition consistent with the contract of FormattedValue.
+     *
+     * @param cfpos
+     *            The argument passed to the public API.
+     * @param numericField
+     *            Optional. If non-null, apply this field to the entire numeric portion of the string.
+     * @return See FormattedValue#nextPosition.
+     */
+    public boolean nextPosition(ConstrainedFieldPosition cfpos, Field numericField) {
         int fieldStart = -1;
         Field currField = null;
         for (int i = zero + cfpos.getLimit(); i <= zero + length; i++) {
-            Field _field = (i < zero + length) ? fields[i] : null;
+            Field _field = (i < zero + length) ? fields[i] : NullField.END;
             // Case 1: currently scanning a field.
             if (currField != null) {
                 if (currField != _field) {
@@ -592,9 +601,10 @@
                 continue;
             }
             // Special case: coalesce the INTEGER if we are pointing at the end of the INTEGER.
-            if ((!isSearchingForField || cfpos.getField() == NumberFormat.Field.INTEGER)
+            if (cfpos.matchesField(NumberFormat.Field.INTEGER)
                     && i > zero
-                    && i - zero > cfpos.getLimit()  // don't return the same field twice in a row
+                    // don't return the same field twice in a row:
+                    && i - zero > cfpos.getLimit()
                     && isIntOrGroup(fields[i - 1])
                     && !isIntOrGroup(_field)) {
                 int j = i - 1;
@@ -602,16 +612,29 @@
                 cfpos.setState(NumberFormat.Field.INTEGER, null, j - zero + 1, i - zero);
                 return true;
             }
+            // Special case: coalesce NUMERIC if we are pointing at the end of the NUMERIC.
+            if (numericField != null
+                    && cfpos.matchesField(numericField)
+                    && i > zero
+                    // don't return the same field twice in a row:
+                    && (i - zero > cfpos.getLimit() || cfpos.getField() != numericField)
+                    && isNumericField(fields[i - 1])
+                    && !isNumericField(_field)) {
+                int j = i - 1;
+                for (; j >= zero && isNumericField(fields[j]); j--) {}
+                cfpos.setState(numericField, null, j - zero + 1, i - zero);
+                return true;
+            }
             // Special case: skip over INTEGER; will be coalesced later.
             if (_field == NumberFormat.Field.INTEGER) {
                 _field = null;
             }
             // Case 2: no field starting at this position.
-            if (_field == null) {
+            if (_field == null || _field == NullField.END) {
                 continue;
             }
             // Case 3: check for field starting at this position
-            if (!isSearchingForField || cfpos.getField() == _field) {
+            if (cfpos.matchesField(_field)) {
                 fieldStart = i - zero;
                 currField = _field;
             }
@@ -621,10 +644,14 @@
         return false;
     }
 
-    private static boolean isIntOrGroup(NumberFormat.Field field) {
+    private static boolean isIntOrGroup(Field field) {
         return field == NumberFormat.Field.INTEGER || field == NumberFormat.Field.GROUPING_SEPARATOR;
     }
 
+    private static boolean isNumericField(Field field) {
+        return field == null || NumberFormat.Field.class.isAssignableFrom(field.getClass());
+    }
+
     private int trimBack(int limit) {
         return StaticUnicodeSets.get(StaticUnicodeSets.Key.DEFAULT_IGNORABLES)
                 .spanBack(this, limit, UnicodeSet.SpanCondition.CONTAINED);
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/SimpleModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/SimpleModifier.java
index 30c12d6..26cb275 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/SimpleModifier.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/SimpleModifier.java
@@ -2,9 +2,10 @@
 // License & terms of use: http://www.unicode.org/copyright.html#License
 package com.ibm.icu.impl.number;
 
+import java.text.Format.Field;
+
 import com.ibm.icu.impl.SimpleFormatterImpl;
 import com.ibm.icu.impl.number.range.PrefixInfixSuffixLengthHelper;
-import com.ibm.icu.text.NumberFormat.Field;
 import com.ibm.icu.util.ICUException;
 
 /**
@@ -65,7 +66,7 @@
 
     @Override
     public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
-        return formatAsPrefixSuffix(output, leftIndex, rightIndex, field);
+        return formatAsPrefixSuffix(output, leftIndex, rightIndex);
     }
 
     @Override
@@ -139,8 +140,7 @@
     public int formatAsPrefixSuffix(
             NumberStringBuilder result,
             int startIndex,
-            int endIndex,
-            Field field) {
+            int endIndex) {
         if (suffixOffset == -1) {
             // There is no argument for the inner number; overwrite the entire segment with our string.
             return result.splice(startIndex, endIndex, compiledPattern, 2, 2 + prefixLength, field);
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java b/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java
index 24a78ae..09da424 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumber.java
@@ -101,7 +101,7 @@
      */
     @Override
     public boolean nextPosition(ConstrainedFieldPosition cfpos) {
-        return nsb.nextPosition(cfpos);
+        return nsb.nextPosition(cfpos, null);
     }
 
     /**
@@ -150,7 +150,7 @@
      */
     @Override
     public AttributedCharacterIterator toCharacterIterator() {
-        return nsb.toCharacterIterator();
+        return nsb.toCharacterIterator(null);
     }
 
     /**
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumberRange.java b/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumberRange.java
index 56f2481..3b37559 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumberRange.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumberRange.java
@@ -107,7 +107,7 @@
      */
     @Override
     public boolean nextPosition(ConstrainedFieldPosition cfpos) {
-        return string.nextPosition(cfpos);
+        return string.nextPosition(cfpos, null);
     }
 
     /**
@@ -151,7 +151,7 @@
      */
     @Override
     public AttributedCharacterIterator toCharacterIterator() {
-        return string.toCharacterIterator();
+        return string.toCharacterIterator(null);
     }
 
     /**
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java
index 6452130..281aca5 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java
@@ -133,6 +133,13 @@
         return new LocalizedNumberFormatterAsFormat(this, resolve().loc);
     }
 
+    /** Helper method that creates a NumberStringBuilder and formats. */
+    private FormattedNumber format(DecimalQuantity fq) {
+        NumberStringBuilder string = new NumberStringBuilder();
+        formatImpl(fq, string);
+        return new FormattedNumber(string, fq);
+    }
+
     /**
      * This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a
      * static code path for the first few calls, and compiling a more efficient data structure if called
@@ -143,20 +150,19 @@
      *
      * @param fq
      *            The quantity to be formatted.
-     * @return The formatted number result.
+     * @param string
+     *            The string builder into which to insert the result.
      *
      * @internal
      * @deprecated ICU 60 This API is ICU internal only.
      */
     @Deprecated
-    public FormattedNumber format(DecimalQuantity fq) {
-        NumberStringBuilder string = new NumberStringBuilder();
+    public void formatImpl(DecimalQuantity fq, NumberStringBuilder string) {
         if (computeCompiled()) {
             compiled.format(fq, string);
         } else {
             NumberFormatterImpl.formatStatic(resolve(), fq, string);
         }
-        return new FormattedNumber(string, fq);
     }
 
     /**
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java
index 0f2f0e7..e94aa98 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java
@@ -2,6 +2,8 @@
 // License & terms of use: http://www.unicode.org/copyright.html#License
 package com.ibm.icu.number;
 
+import java.text.Format.Field;
+
 import com.ibm.icu.impl.number.DecimalQuantity;
 import com.ibm.icu.impl.number.MicroProps;
 import com.ibm.icu.impl.number.MicroPropsGenerator;
@@ -13,7 +15,6 @@
 import com.ibm.icu.number.Precision.SignificantRounderImpl;
 import com.ibm.icu.text.DecimalFormatSymbols;
 import com.ibm.icu.text.NumberFormat;
-import com.ibm.icu.text.NumberFormat.Field;
 
 /**
  * A class that defines the scientific notation style to be used when formatting numbers in
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/NumberFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/NumberFormat.java
index 4304bfa..64e84f8 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/NumberFormat.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/text/NumberFormat.java
@@ -2009,6 +2009,10 @@
                 return PERMILLE;
             if (this.getName().equals(SIGN.getName()))
                 return SIGN;
+            if (this.getName().equals(MEASURE_UNIT.getName()))
+                return MEASURE_UNIT;
+            if (this.getName().equals(COMPACT.getName()))
+                return COMPACT;
 
             throw new InvalidObjectException("An invalid object.");
         }
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/QuantityFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/text/QuantityFormatter.java
index 562439f..b97066d 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/QuantityFormatter.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/text/QuantityFormatter.java
@@ -12,12 +12,11 @@
 
 import com.ibm.icu.impl.SimpleFormatterImpl;
 import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.text.PluralRules.FixedDecimal;
 
 /**
  * QuantityFormatter represents an unknown quantity of something and formats a known quantity
  * in terms of that something. For example, a QuantityFormatter that represents X apples may
- * format 1 as "1 apple" and 3 as "3 apples" 
+ * format 1 as "1 apple" and 3 as "3 apples"
  * <p>
  * QuanitityFormatter appears here instead of in com.ibm.icu.impl because it depends on
  * PluralRules and DecimalFormat. It is package-protected as it is not meant for public use.
@@ -102,24 +101,6 @@
     }
 
     /**
-     * Selects the standard plural form for the number/formatter/rules.
-     */
-    public static StandardPlural selectPlural(
-            Number number, NumberFormat fmt, PluralRules rules,
-            StringBuffer formattedNumber, FieldPosition pos) {
-        UFieldPosition fpos = new UFieldPosition(pos.getFieldAttribute(), pos.getField());
-        fmt.format(number, formattedNumber, fpos);
-        // TODO: Long, BigDecimal & BigInteger may not fit into doubleValue().
-        FixedDecimal fd = new FixedDecimal(
-                number.doubleValue(),
-                fpos.getCountVisibleFractionDigits(), fpos.getFractionDigits());
-        String pluralKeyword = rules.select(fd);
-        pos.setBeginIndex(fpos.getBeginIndex());
-        pos.setEndIndex(fpos.getEndIndex());
-        return StandardPlural.orOtherFromString(pluralKeyword);
-    }
-
-    /**
      * Formats the pattern with the value and adjusts the FieldPosition.
      */
     public static StringBuilder format(String compiledPattern, CharSequence value,
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/RelativeDateTimeFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/text/RelativeDateTimeFormatter.java
index 3cba6ac..f451fdf 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/RelativeDateTimeFormatter.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/text/RelativeDateTimeFormatter.java
@@ -8,20 +8,28 @@
  */
 package com.ibm.icu.text;
 
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.text.AttributedCharacterIterator;
+import java.text.Format;
 import java.util.EnumMap;
 import java.util.Locale;
 
 import com.ibm.icu.impl.CacheBase;
-import com.ibm.icu.impl.DontCareFieldPosition;
 import com.ibm.icu.impl.ICUData;
 import com.ibm.icu.impl.ICUResourceBundle;
 import com.ibm.icu.impl.SimpleFormatterImpl;
 import com.ibm.icu.impl.SoftCache;
 import com.ibm.icu.impl.StandardPlural;
 import com.ibm.icu.impl.UResource;
+import com.ibm.icu.impl.number.DecimalQuantity;
+import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
+import com.ibm.icu.impl.number.NumberStringBuilder;
+import com.ibm.icu.impl.number.SimpleModifier;
 import com.ibm.icu.lang.UCharacter;
 import com.ibm.icu.util.Calendar;
 import com.ibm.icu.util.ICUException;
+import com.ibm.icu.util.ICUUncheckedIOException;
 import com.ibm.icu.util.ULocale;
 import com.ibm.icu.util.UResourceBundle;
 
@@ -387,6 +395,156 @@
     }
 
     /**
+     * Field constants used when accessing field information for relative
+     * datetime strings in FormattedValue.
+     * <p>
+     * There is no public constructor to this class; the only instances are the
+     * constants defined here.
+     * <p>
+     * @draft ICU 64
+     * @provisional This API might change or be removed in a future release.
+     */
+    public static class Field extends Format.Field {
+        private static final long serialVersionUID = -5327685528663492325L;
+
+        /**
+         * Represents a literal text string, like "tomorrow" or "days ago".
+         *
+         * @draft ICU 64
+         * @provisional This API might change or be removed in a future release.
+         */
+        public static final Field LITERAL = new Field("literal");
+
+        /**
+         * Represents a number quantity, like "3" in "3 days ago".
+         *
+         * @draft ICU 64
+         * @provisional This API might change or be removed in a future release.
+         */
+        public static final Field NUMERIC = new Field("numeric");
+
+        private Field(String fieldName) {
+            super(fieldName);
+        }
+
+        /**
+         * Serizalization method resolve instances to the constant Field values
+         *
+         * @draft ICU 64
+         * @provisional This API might change or be removed in a future release.
+         */
+        @Override
+        protected Object readResolve() throws InvalidObjectException {
+            if (this.getName().equals(LITERAL.getName()))
+                return LITERAL;
+            if (this.getName().equals(NUMERIC.getName()))
+                return NUMERIC;
+
+            throw new InvalidObjectException("An invalid object.");
+        }
+    }
+
+    /**
+     * Represents the result of a formatting operation of a relative datetime.
+     * Access the string value or field information.
+     *
+     * @author sffc
+     * @draft ICU 64
+     * @provisional This API might change or be removed in a future release.
+     */
+    public static class FormattedRelativeDateTime implements FormattedValue {
+
+        private final NumberStringBuilder string;
+
+        private FormattedRelativeDateTime(NumberStringBuilder string) {
+            this.string = string;
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @draft ICU 64
+         * @provisional This API might change or be removed in a future release.
+         */
+        @Override
+        public String toString() {
+            return string.toString();
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @draft ICU 64
+         * @provisional This API might change or be removed in a future release.
+         */
+        @Override
+        public int length() {
+            return string.length();
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @draft ICU 64
+         * @provisional This API might change or be removed in a future release.
+         */
+        @Override
+        public char charAt(int index) {
+            return string.charAt(index);
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @draft ICU 64
+         * @provisional This API might change or be removed in a future release.
+         */
+        @Override
+        public CharSequence subSequence(int start, int end) {
+            return string.subString(start, end);
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @draft ICU 64
+         * @provisional This API might change or be removed in a future release.
+         */
+        @Override
+        public <A extends Appendable> A appendTo(A appendable) {
+            try {
+                appendable.append(string);
+            } catch (IOException e) {
+                // Throw as an unchecked exception to avoid users needing try/catch
+                throw new ICUUncheckedIOException(e);
+            }
+            return appendable;
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @draft ICU 64
+         * @provisional This API might change or be removed in a future release.
+         */
+        @Override
+        public boolean nextPosition(ConstrainedFieldPosition cfpos) {
+            return string.nextPosition(cfpos, Field.NUMERIC);
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @draft ICU 64
+         * @provisional This API might change or be removed in a future release.
+         */
+        @Override
+        public AttributedCharacterIterator toCharacterIterator() {
+            return string.toCharacterIterator(Field.NUMERIC);
+        }
+    }
+
+    /**
      * Returns a RelativeDateTimeFormatter for the default locale.
      * @stable ICU 53
      */
@@ -482,7 +640,11 @@
 
     /**
      * Formats a relative date with a quantity such as "in 5 days" or
-     * "3 months ago"
+     * "3 months ago".
+     *
+     * This method returns a String. To get more information about the
+     * formatting result, use formatToValue().
+     *
      * @param quantity The numerical amount e.g 5. This value is formatted
      * according to this object's {@link NumberFormat} object.
      * @param direction NEXT means a future relative date; LAST means a past
@@ -494,25 +656,57 @@
      * @stable ICU 53
      */
     public String format(double quantity, Direction direction, RelativeUnit unit) {
+        NumberStringBuilder output = formatImpl(quantity, direction, unit);
+        return adjustForContext(output.toString());
+    }
+
+    /**
+     * Formats a relative date with a quantity such as "in 5 days" or
+     * "3 months ago".
+     *
+     * This method returns a FormattedRelativeDateTime, which exposes more
+     * information than the String returned by format().
+     *
+     * @param quantity The numerical amount e.g 5. This value is formatted
+     * according to this object's {@link NumberFormat} object.
+     * @param direction NEXT means a future relative date; LAST means a past
+     * relative date.
+     * @param unit the unit e.g day? month? year?
+     * @return the formatted relative datetime
+     * @throws IllegalArgumentException if direction is something other than
+     * NEXT or LAST.
+     * @draft ICU 64
+     * @provisional This API might change or be removed in a future release.
+     */
+    public FormattedRelativeDateTime formatToValue(double quantity, Direction direction, RelativeUnit unit) {
+        checkNoAdjustForContext();
+        return new FormattedRelativeDateTime(formatImpl(quantity, direction, unit));
+    }
+
+    /** Implementation method for format and formatToValue with RelativeUnit */
+    private NumberStringBuilder formatImpl(double quantity, Direction direction, RelativeUnit unit) {
         if (direction != Direction.LAST && direction != Direction.NEXT) {
             throw new IllegalArgumentException("direction must be NEXT or LAST");
         }
-        String result;
         int pastFutureIndex = (direction == Direction.NEXT ? 1 : 0);
 
-        // This class is thread-safe, yet numberFormat is not. To ensure thread-safety of this
-        // class we must guarantee that only one thread at a time uses our numberFormat.
-        synchronized (numberFormat) {
-            StringBuffer formatStr = new StringBuffer();
-            DontCareFieldPosition fieldPosition = DontCareFieldPosition.INSTANCE;
-            StandardPlural pluralForm = QuantityFormatter.selectPlural(quantity,
-                    numberFormat, pluralRules, formatStr, fieldPosition);
-
-            String formatter = getRelativeUnitPluralPattern(style, unit, pastFutureIndex, pluralForm);
-            result = SimpleFormatterImpl.formatCompiledPattern(formatter, formatStr);
+        NumberStringBuilder output = new NumberStringBuilder();
+        String pluralKeyword;
+        if (numberFormat instanceof DecimalFormat) {
+            DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(quantity);
+            ((DecimalFormat) numberFormat).toNumberFormatter().formatImpl(dq, output);
+            pluralKeyword = pluralRules.select(dq);
+        } else {
+            String result = numberFormat.format(quantity);
+            output.append(result, null);
+            pluralKeyword = pluralRules.select(quantity);
         }
-        return adjustForContext(result);
+        StandardPlural pluralForm = StandardPlural.orOtherFromString(pluralKeyword);
 
+        String compiledPattern = getRelativeUnitPluralPattern(style, unit, pastFutureIndex, pluralForm);
+        SimpleModifier modifier = new SimpleModifier(compiledPattern, Field.LITERAL, false);
+        modifier.formatAsPrefixSuffix(output, 0, output.length());
+        return output;
     }
 
     /**
@@ -520,6 +714,9 @@
      * using a numeric style, e.g. "1 week ago", "in 1 week",
      * "5 weeks ago", "in 5 weeks".
      *
+     * This method returns a String. To get more information about the
+     * formatting result, use formatNumericToValue().
+     *
      * @param offset    The signed offset for the specified unit. This
      *                  will be formatted according to this object's
      *                  NumberFormat object.
@@ -530,6 +727,35 @@
      * @stable ICU 57
      */
     public String formatNumeric(double offset, RelativeDateTimeUnit unit) {
+        NumberStringBuilder output = formatNumericImpl(offset, unit);
+        return adjustForContext(output.toString());
+    }
+
+    /**
+     * Format a combination of RelativeDateTimeUnit and numeric offset
+     * using a numeric style, e.g. "1 week ago", "in 1 week",
+     * "5 weeks ago", "in 5 weeks".
+     *
+     * This method returns a FormattedRelativeDateTime, which exposes more
+     * information than the String returned by formatNumeric().
+     *
+     * @param offset    The signed offset for the specified unit. This
+     *                  will be formatted according to this object's
+     *                  NumberFormat object.
+     * @param unit      The unit to use when formatting the relative
+     *                  date, e.g. RelativeDateTimeUnit.WEEK,
+     *                  RelativeDateTimeUnit.FRIDAY.
+     * @return          The formatted string (may be empty in case of error)
+     * @draft ICU 64
+     * @provisional This API might change or be removed in a future release.
+     */
+    public FormattedRelativeDateTime formatNumericToValue(double offset, RelativeDateTimeUnit unit) {
+        checkNoAdjustForContext();
+        return new FormattedRelativeDateTime(formatNumericImpl(offset, unit));
+    }
+
+    /** Implementation method for formatNumeric and formatNumericToValue */
+    private NumberStringBuilder formatNumericImpl(double offset, RelativeDateTimeUnit unit) {
         // TODO:
         // The full implementation of this depends on CLDR data that is not yet available,
         // see: http://unicode.org/cldr/trac/ticket/9165 Add more relative field data.
@@ -555,8 +781,7 @@
             direction = Direction.LAST;
             offset = -offset;
         }
-        String result = format(offset, direction, relunit);
-        return (result != null)? result: "";
+        return formatImpl(offset, direction, relunit);
     }
 
     private int[] styleToDateFormatSymbolsWidth = {
@@ -565,6 +790,10 @@
 
     /**
      * Formats a relative date without a quantity.
+     *
+     * This method returns a String. To get more information about the
+     * formatting result, use formatToValue().
+     *
      * @param direction NEXT, LAST, THIS, etc.
      * @param unit e.g SATURDAY, DAY, MONTH
      * @return the formatted string. If direction has a value that is documented as not being
@@ -575,6 +804,39 @@
      * @stable ICU 53
      */
     public String format(Direction direction, AbsoluteUnit unit) {
+        String result = formatAbsoluteImpl(direction, unit);
+        return result != null ? adjustForContext(result) : null;
+    }
+
+    /**
+     * Formats a relative date without a quantity.
+     *
+     * This method returns a FormattedRelativeDateTime, which exposes more
+     * information than the String returned by format().
+     *
+     * @param direction NEXT, LAST, THIS, etc.
+     * @param unit e.g SATURDAY, DAY, MONTH
+     * @return the formatted string. If direction has a value that is documented as not being
+     *  fully supported in every locale (for example NEXT_2 or LAST_2) then this function may
+     *  return null to signal that no formatted string is available.
+     * @throws IllegalArgumentException if the direction is incompatible with
+     * unit this can occur with NOW which can only take PLAIN.
+     * @draft ICU 64
+     * @provisional This API might change or be removed in a future release.
+     */
+    public FormattedRelativeDateTime formatToValue(Direction direction, AbsoluteUnit unit) {
+        checkNoAdjustForContext();
+        String string = formatAbsoluteImpl(direction, unit);
+        if (string == null) {
+            return null;
+        }
+        NumberStringBuilder nsb = new NumberStringBuilder();
+        nsb.append(string, Field.LITERAL);
+        return new FormattedRelativeDateTime(nsb);
+    }
+
+    /** Implementation method for format and formatToValue with AbsoluteUnit */
+    private String formatAbsoluteImpl(Direction direction, AbsoluteUnit unit) {
         if (unit == AbsoluteUnit.NOW && direction != Direction.PLAIN) {
             throw new IllegalArgumentException("NOW can only accept direction PLAIN.");
         }
@@ -592,7 +854,7 @@
             // Not PLAIN, or not a weekday.
             result = getAbsoluteUnitString(style, unit, direction);
         }
-        return result != null ? adjustForContext(result) : null;
+        return result;
     }
 
     /**
@@ -602,6 +864,9 @@
      * style if no appropriate text term is available for the specified
      * offset in the object’s locale.
      *
+     * This method returns a String. To get more information about the
+     * formatting result, use formatToValue().
+     *
      * @param offset    The signed offset for the specified field.
      * @param unit      The unit to use when formatting the relative
      *                  date, e.g. RelativeDateTimeUnit.WEEK,
@@ -610,6 +875,43 @@
      * @stable ICU 57
      */
     public String format(double offset, RelativeDateTimeUnit unit) {
+        return adjustForContext(formatRelativeImpl(offset, unit).toString());
+    }
+
+    /**
+     * Format a combination of RelativeDateTimeUnit and numeric offset
+     * using a text style if possible, e.g. "last week", "this week",
+     * "next week", "yesterday", "tomorrow". Falls back to numeric
+     * style if no appropriate text term is available for the specified
+     * offset in the object’s locale.
+     *
+     * This method returns a FormattedRelativeDateTime, which exposes more
+     * information than the String returned by format().
+     *
+     * @param offset    The signed offset for the specified field.
+     * @param unit      The unit to use when formatting the relative
+     *                  date, e.g. RelativeDateTimeUnit.WEEK,
+     *                  RelativeDateTimeUnit.FRIDAY.
+     * @return          The formatted string (may be empty in case of error)
+     * @draft ICU 64
+     * @provisional This API might change or be removed in a future release.
+     */
+    public FormattedRelativeDateTime formatToValue(double offset, RelativeDateTimeUnit unit) {
+        checkNoAdjustForContext();
+        CharSequence cs = formatRelativeImpl(offset, unit);
+        NumberStringBuilder nsb;
+        if (cs instanceof NumberStringBuilder) {
+            nsb = (NumberStringBuilder) cs;
+        } else {
+            nsb = new NumberStringBuilder();
+            nsb.append(cs, Field.LITERAL);
+        }
+        return new FormattedRelativeDateTime(nsb);
+    }
+
+
+    /** Implementation method for format and formatToValue with RelativeDateTimeUnit. */
+    private CharSequence formatRelativeImpl(double offset, RelativeDateTimeUnit unit) {
         // TODO:
         // The full implementation of this depends on CLDR data that is not yet available,
         // see: http://unicode.org/cldr/trac/ticket/9165 Add more relative field data.
@@ -661,13 +963,13 @@
                 break;
         }
         if (!useNumeric) {
-            String result = format(direction, absunit);
+            String result = formatAbsoluteImpl(direction, absunit);
             if (result != null && result.length() > 0) {
                 return result;
             }
         }
         // otherwise fallback to formatNumeric
-        return formatNumeric(offset, unit);
+        return formatNumericImpl(offset, unit);
     }
 
     /**
@@ -756,6 +1058,12 @@
         }
     }
 
+    private void checkNoAdjustForContext() {
+        if (breakIterator != null) {
+            throw new UnsupportedOperationException("Capitalization context is not supported in formatV");
+        }
+    }
+
     private RelativeDateTimeFormatter(
             EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap,
             EnumMap<Style, EnumMap<RelativeUnit, String[][]>> patternMap,
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/FormattedValueTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/FormattedValueTest.java
index fc50106..0811a7a 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/FormattedValueTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/FormattedValueTest.java
@@ -128,7 +128,7 @@
         String baseMessage = message + ": " + fv.toString() + ": ";
 
         // Check the String and CharSequence
-        assertEquals(baseMessage + "string", expectedString, fv.toString());
+        assertEquals(baseMessage + " string", expectedString, fv.toString());
         assertCharSequenceEquals(expectedString, fv);
 
         // Check the AttributedCharacterIterator
@@ -159,7 +159,8 @@
                 assertEquals(baseMessage + expectedField + " end @" + i, expectedEndIndex, actualEndIndex);
                 attributesRemaining--;
             }
-            assertEquals(baseMessage + "Should have looked at every field", 0, attributesRemaining);
+            assertEquals(baseMessage + "Should have looked at every field: " + i + ": " + currentAttributes,
+                    0, attributesRemaining);
         }
         assertEquals(baseMessage + "Should have looked at every character", stringLength, i);
 
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/RelativeDateTimeFormatterTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/RelativeDateTimeFormatterTest.java
index a7c14d4..bada1be 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/RelativeDateTimeFormatterTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/RelativeDateTimeFormatterTest.java
@@ -21,9 +21,11 @@
 import com.ibm.icu.text.RelativeDateTimeFormatter;
 import com.ibm.icu.text.RelativeDateTimeFormatter.AbsoluteUnit;
 import com.ibm.icu.text.RelativeDateTimeFormatter.Direction;
+import com.ibm.icu.text.RelativeDateTimeFormatter.FormattedRelativeDateTime;
 import com.ibm.icu.text.RelativeDateTimeFormatter.RelativeDateTimeUnit;
 import com.ibm.icu.text.RelativeDateTimeFormatter.RelativeUnit;
 import com.ibm.icu.text.RelativeDateTimeFormatter.Style;
+import com.ibm.icu.text.RuleBasedNumberFormat;
 import com.ibm.icu.util.ULocale;
 
 @RunWith(JUnit4.class)
@@ -1025,12 +1027,101 @@
         assertEquals("narrow: in 6 qtr", "in 6 qtr", w);
     }
 
-@Test
-public void TestLocales() {
-    ULocale[] availableLocales = ULocale.getAvailableLocales();
-    for (ULocale loc: availableLocales) {
-        RelativeDateTimeFormatter.getInstance(loc);
+    @Test
+    public void TestLocales() {
+        ULocale[] availableLocales = ULocale.getAvailableLocales();
+        for (ULocale loc: availableLocales) {
+            RelativeDateTimeFormatter.getInstance(loc);
+        }
     }
-}
+
+    @Test
+    public void TestFields() {
+        RelativeDateTimeFormatter fmt = RelativeDateTimeFormatter.getInstance(ULocale.US);
+
+        {
+            String message = "automatic absolute unit";
+            FormattedRelativeDateTime fv = fmt.formatToValue(1, RelativeDateTimeUnit.DAY);
+            String expectedString = "tomorrow";
+            Object[][] expectedFieldPositions = new Object[][]{
+                {RelativeDateTimeFormatter.Field.LITERAL, 0, 8}};
+            FormattedValueTest.checkFormattedValue(message, fv, expectedString, expectedFieldPositions);
+        }
+        {
+            String message = "automatic numeric unit";
+            FormattedRelativeDateTime fv = fmt.formatToValue(3, RelativeDateTimeUnit.DAY);
+            String expectedString = "in 3 days";
+            Object[][] expectedFieldPositions = new Object[][]{
+                {RelativeDateTimeFormatter.Field.LITERAL, 0, 2},
+                {NumberFormat.Field.INTEGER, 3, 4},
+                {RelativeDateTimeFormatter.Field.NUMERIC, 3, 4},
+                {RelativeDateTimeFormatter.Field.LITERAL, 5, 9}};
+            FormattedValueTest.checkFormattedValue(message, fv, expectedString, expectedFieldPositions);
+        }
+        {
+            String message = "manual absolute unit";
+            FormattedRelativeDateTime fv = fmt.formatToValue(Direction.NEXT, AbsoluteUnit.MONDAY);
+            String expectedString = "next Monday";
+            Object[][] expectedFieldPositions = new Object[][]{
+                {RelativeDateTimeFormatter.Field.LITERAL, 0, 11}};
+            FormattedValueTest.checkFormattedValue(message, fv, expectedString, expectedFieldPositions);
+        }
+        {
+            String message = "manual numeric unit";
+            FormattedRelativeDateTime fv = fmt.formatNumericToValue(1.5, RelativeDateTimeUnit.WEEK);
+            String expectedString = "in 1.5 weeks";
+            Object[][] expectedFieldPositions = new Object[][]{
+                {RelativeDateTimeFormatter.Field.LITERAL, 0, 2},
+                {NumberFormat.Field.INTEGER, 3, 4},
+                {NumberFormat.Field.DECIMAL_SEPARATOR, 4, 5},
+                {NumberFormat.Field.FRACTION, 5, 6},
+                {RelativeDateTimeFormatter.Field.NUMERIC, 3, 6},
+                {RelativeDateTimeFormatter.Field.LITERAL, 7, 12}};
+            FormattedValueTest.checkFormattedValue(message, fv, expectedString, expectedFieldPositions);
+        }
+        {
+            String message = "manual numeric resolved unit";
+            FormattedRelativeDateTime fv = fmt.formatToValue(12, Direction.LAST, RelativeUnit.HOURS);
+            String expectedString = "12 hours ago";
+            Object[][] expectedFieldPositions = new Object[][]{
+                {NumberFormat.Field.INTEGER, 0, 2},
+                {RelativeDateTimeFormatter.Field.NUMERIC, 0, 2},
+                {RelativeDateTimeFormatter.Field.LITERAL, 3, 12}};
+            FormattedValueTest.checkFormattedValue(message, fv, expectedString, expectedFieldPositions);
+        }
+
+        // Test when the number field is at the end
+        fmt = RelativeDateTimeFormatter.getInstance(new ULocale("sw"));
+        {
+            String message = "numeric field at end";
+            FormattedRelativeDateTime fv = fmt.formatToValue(12, RelativeDateTimeUnit.HOUR);
+            String expectedString = "baada ya saa 12";
+            Object[][] expectedFieldPositions = new Object[][]{
+                {RelativeDateTimeFormatter.Field.LITERAL, 0, 12},
+                {NumberFormat.Field.INTEGER, 13, 15},
+                {RelativeDateTimeFormatter.Field.NUMERIC, 13, 15}};
+            FormattedValueTest.checkFormattedValue(message, fv, expectedString, expectedFieldPositions);
+        }
+    }
+
+    @Test
+    public void TestRBNF() {
+        RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ULocale.US, RuleBasedNumberFormat.SPELLOUT);
+        RelativeDateTimeFormatter fmt = RelativeDateTimeFormatter.getInstance(ULocale.US, rbnf);
+        assertEquals("format (direction)", "in five seconds", fmt.format(5, Direction.NEXT, RelativeUnit.SECONDS));
+        assertEquals("formatNumeric", "one week ago", fmt.formatNumeric(-1, RelativeDateTimeUnit.WEEK));
+        assertEquals("format (absolute)", "yesterday", fmt.format(Direction.LAST, AbsoluteUnit.DAY));
+        assertEquals("format (relative)", "in forty-two months", fmt.format(42, RelativeDateTimeUnit.MONTH));
+
+        {
+            String message = "formatToValue (relative)";
+            FormattedRelativeDateTime fv = fmt.formatToValue(-100, RelativeDateTimeUnit.YEAR);
+            String expectedString = "one hundred years ago";
+            Object[][] expectedFieldPositions = new Object[][]{
+                {RelativeDateTimeFormatter.Field.NUMERIC, 0, 11},
+                {RelativeDateTimeFormatter.Field.LITERAL, 12, 21}};
+            FormattedValueTest.checkFormattedValue(message, fv, expectedString, expectedFieldPositions);
+        }
+    }
 
 }
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java
index 3b7199e..9271d94 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java
@@ -22,6 +22,7 @@
 import com.ibm.icu.impl.number.DecimalFormatProperties;
 import com.ibm.icu.impl.number.DecimalQuantity;
 import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
+import com.ibm.icu.impl.number.NumberStringBuilder;
 import com.ibm.icu.impl.number.RoundingUtils;
 import com.ibm.icu.number.LocalizedNumberFormatter;
 import com.ibm.icu.number.NumberFormatter;
@@ -235,8 +236,12 @@
         for (LocalizedNumberFormatter format : formats) {
             DecimalQuantity q0 = rq0.createCopy();
             DecimalQuantity q1 = rq1.createCopy();
-            String s1 = format.format(q0).toString();
-            String s2 = format.format(q1).toString();
+            NumberStringBuilder nsb1 = new NumberStringBuilder();
+            NumberStringBuilder nsb2 = new NumberStringBuilder();
+            format.formatImpl(q0, nsb1);
+            format.formatImpl(q1, nsb2);
+            String s1 = nsb1.toString();
+            String s2 = nsb2.toString();
             assertEquals("Different output from formatter (" + q0 + ", " + q1 + ")", s1, s2);
         }
     }
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/FormatHandler.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/FormatHandler.java
index 7bb5e98..d34b644 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/FormatHandler.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/FormatHandler.java
@@ -34,6 +34,7 @@
 import com.ibm.icu.text.NumberFormat;
 import com.ibm.icu.text.PluralFormat;
 import com.ibm.icu.text.PluralRules;
+import com.ibm.icu.text.RelativeDateTimeFormatter;
 import com.ibm.icu.text.RuleBasedNumberFormat;
 import com.ibm.icu.text.SelectFormat;
 import com.ibm.icu.text.SimpleDateFormat;
@@ -1800,6 +1801,21 @@
         }
     }
 
+    public static class RelativeDateTimeFormatterFieldHandler implements SerializableTestUtility.Handler
+    {
+        @Override
+        public Object[] getTestObjects()
+        {
+            return new Object[] {RelativeDateTimeFormatter.Field.LITERAL};
+        }
+
+        @Override
+        public boolean hasSameBehavior(Object a, Object b)
+        {
+            return (a == b);
+        }
+    }
+
     public static class DateFormatHandler implements SerializableTestUtility.Handler
     {
         static HashMap cannedPatterns = new HashMap();
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTestUtility.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTestUtility.java
index c1e97c7..fe9a601 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTestUtility.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTestUtility.java
@@ -818,6 +818,7 @@
         map.put("com.ibm.icu.text.DateFormat$Field", new FormatHandler.DateFormatFieldHandler());
         map.put("com.ibm.icu.text.ChineseDateFormat$Field", new FormatHandler.ChineseDateFormatFieldHandler());
         map.put("com.ibm.icu.text.MessageFormat$Field", new FormatHandler.MessageFormatFieldHandler());
+        map.put("com.ibm.icu.text.RelativeDateTimeFormatter$Field", new FormatHandler.RelativeDateTimeFormatterFieldHandler());
 
         map.put("com.ibm.icu.impl.duration.BasicDurationFormat", new FormatHandler.BasicDurationFormatHandler());
         map.put("com.ibm.icu.impl.RelativeDateFormat", new FormatHandler.RelativeDateFormatHandler());