]> git.saurik.com Git - apple/icu.git/blobdiff - icuSources/i18n/reldatefmt.cpp
ICU-62123.0.1.tar.gz
[apple/icu.git] / icuSources / i18n / reldatefmt.cpp
index 1832c4428f3b5feaf9e95041cb8dd59e06dc6074..aad026cb62d12897c307d57a07430908a950952e 100644 (file)
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
 /*
 ******************************************************************************
-* Copyright (C) 2014, International Business Machines Corporation and         
-* others. All Rights Reserved.                                                
+* Copyright (C) 2014-2016, International Business Machines Corporation and
+* others. All Rights Reserved.
 ******************************************************************************
-*                                                                             
-* File RELDATEFMT.CPP                                                             
+*
+* File reldatefmt.cpp
 ******************************************************************************
 */
 
 #include "unicode/reldatefmt.h"
 
-#if !UCONFIG_NO_FORMATTING
+#if !UCONFIG_NO_FORMATTING && !UCONFIG_NO_BREAK_ITERATION
 
+#include <cmath>
+#include "unicode/dtfmtsym.h"
+#include "unicode/ucasemap.h"
+#include "unicode/ureldatefmt.h"
+#include "unicode/udisplaycontext.h"
+#include "unicode/unum.h"
 #include "unicode/localpointer.h"
-#include "quantityformatter.h"
 #include "unicode/plurrule.h"
-#include "unicode/msgfmt.h"
+#include "unicode/simpleformatter.h"
 #include "unicode/decimfmt.h"
 #include "unicode/numfmt.h"
-#include "lrucache.h"
+#include "unicode/brkiter.h"
+#include "unicode/simpleformatter.h"
 #include "uresimp.h"
 #include "unicode/ures.h"
 #include "cstring.h"
 #include "ucln_in.h"
 #include "mutex.h"
 #include "charstr.h"
-
-#include "sharedptr.h"
+#include "uassert.h"
+#include "quantityformatter.h"
+#include "resource.h"
+#include "sharedbreakiterator.h"
 #include "sharedpluralrules.h"
 #include "sharednumberformat.h"
+#include "standardplural.h"
+#include "unifiedcache.h"
 
 // Copied from uscript_props.cpp
-#define LENGTHOF(array) (int32_t)(sizeof(array)/sizeof((array)[0]))
 
-static icu::LRUCache *gCache = NULL;
-static UMutex gCacheMutex = U_MUTEX_INITIALIZER;
-static icu::UInitOnce gCacheInitOnce = U_INITONCE_INITIALIZER;
-
-U_CDECL_BEGIN
-static UBool U_CALLCONV reldatefmt_cleanup() {
-    gCacheInitOnce.reset();
-    if (gCache) {
-        delete gCache;
-        gCache = NULL;
-    }
-    return TRUE;
-}
-U_CDECL_END
+static UMutex gBrkIterMutex = U_MUTEX_INITIALIZER;
 
 U_NAMESPACE_BEGIN
 
 // RelativeDateTimeFormatter specific data for a single locale
 class RelativeDateTimeCacheData: public SharedObject {
 public:
-    RelativeDateTimeCacheData() : combinedDateAndTime(NULL) { }
+    RelativeDateTimeCacheData() : combinedDateAndTime(NULL) {
+        // Initialize the cache arrays
+        for (int32_t style = 0; style < UDAT_STYLE_COUNT; ++style) {
+            for (int32_t relUnit = 0; relUnit < UDAT_RELATIVE_UNIT_COUNT; ++relUnit) {
+                for (int32_t pl = 0; pl < StandardPlural::COUNT; ++pl) {
+                    relativeUnitsFormatters[style][relUnit][0][pl] = NULL;
+                    relativeUnitsFormatters[style][relUnit][1][pl] = NULL;
+                }
+            }
+        }
+        for (int32_t i = 0; i < UDAT_STYLE_COUNT; ++i) {
+          fallBackCache[i] = -1;
+        }
+    }
     virtual ~RelativeDateTimeCacheData();
 
     // no numbers: e.g Next Tuesday; Yesterday; etc.
-    UnicodeString absoluteUnits[UDAT_ABSOLUTE_UNIT_COUNT][UDAT_DIRECTION_COUNT];
+    UnicodeString absoluteUnits[UDAT_STYLE_COUNT][UDAT_ABSOLUTE_UNIT_COUNT][UDAT_DIRECTION_COUNT];
+
+    // SimpleFormatter pointers for relative unit format,
+    // e.g., Next Tuesday; Yesterday; etc. For third index, 0
+    // means past, e.g., 5 days ago; 1 means future, e.g., in 5 days.
+    SimpleFormatter *relativeUnitsFormatters[UDAT_STYLE_COUNT]
+        [UDAT_RELATIVE_UNIT_COUNT][2][StandardPlural::COUNT];
+
+    const UnicodeString& getAbsoluteUnitString(int32_t fStyle,
+                                               UDateAbsoluteUnit unit,
+                                               UDateDirection direction) const;
+    const SimpleFormatter* getRelativeUnitFormatter(int32_t fStyle,
+                                                    UDateRelativeUnit unit,
+                                                    int32_t pastFutureIndex,
+                                                    int32_t pluralUnit) const;
 
-    // has numbers: e.g Next Tuesday; Yesterday; etc. For second index, 0
-    // means past e.g 5 days ago; 1 means future e.g in 5 days.
-    QuantityFormatter relativeUnits[UDAT_RELATIVE_UNIT_COUNT][2];
+    const UnicodeString emptyString;
 
-    void adoptCombinedDateAndTime(MessageFormat *mfToAdopt) {
+    // Mappping from source to target styles for alias fallback.
+    int32_t fallBackCache[UDAT_STYLE_COUNT];
+
+    void adoptCombinedDateAndTime(SimpleFormatter *fmtToAdopt) {
         delete combinedDateAndTime;
-        combinedDateAndTime = mfToAdopt;
+        combinedDateAndTime = fmtToAdopt;
     }
-    const MessageFormat *getCombinedDateAndTime() const {
+    const SimpleFormatter *getCombinedDateAndTime() const {
         return combinedDateAndTime;
     }
+
 private:
-    MessageFormat *combinedDateAndTime;
+    SimpleFormatter *combinedDateAndTime;
     RelativeDateTimeCacheData(const RelativeDateTimeCacheData &other);
     RelativeDateTimeCacheData& operator=(
             const RelativeDateTimeCacheData &other);
 };
 
 RelativeDateTimeCacheData::~RelativeDateTimeCacheData() {
+    // clear out the cache arrays
+    for (int32_t style = 0; style < UDAT_STYLE_COUNT; ++style) {
+        for (int32_t relUnit = 0; relUnit < UDAT_RELATIVE_UNIT_COUNT; ++relUnit) {
+            for (int32_t pl = 0; pl < StandardPlural::COUNT; ++pl) {
+                delete relativeUnitsFormatters[style][relUnit][0][pl];
+                delete relativeUnitsFormatters[style][relUnit][1][pl];
+            }
+        }
+    }
     delete combinedDateAndTime;
 }
 
-static UBool getStringWithFallback(
-        const UResourceBundle *resource, 
-        const char *key,
-        UnicodeString &result,
-        UErrorCode &status) {
-    int32_t len = 0;
-    const UChar *resStr = ures_getStringByKeyWithFallback(
-        resource, key, &len, &status);
-    if (U_FAILURE(status)) {
-        return FALSE;
-    }
-    result.setTo(TRUE, resStr, len);
-    return TRUE;
+
+// Use fallback cache for absolute units.
+const UnicodeString& RelativeDateTimeCacheData::getAbsoluteUnitString(
+        int32_t fStyle, UDateAbsoluteUnit unit, UDateDirection direction) const {
+    int32_t style = fStyle;
+    do {
+        if (!absoluteUnits[style][unit][direction].isEmpty()) {
+            return absoluteUnits[style][unit][direction];
+        }
+        style = fallBackCache[style];
+    } while (style != -1);
+    return emptyString;
 }
 
-static UBool getOptionalStringWithFallback(
-        const UResourceBundle *resource, 
+ // Use fallback cache for SimpleFormatter relativeUnits.
+ const SimpleFormatter* RelativeDateTimeCacheData::getRelativeUnitFormatter(
+        int32_t fStyle,
+        UDateRelativeUnit unit,
+        int32_t pastFutureIndex,
+        int32_t pluralUnit) const {
+    int32_t style = fStyle;
+    do {
+        if (relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit] != NULL) {
+            return relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit];
+        }
+        style = fallBackCache[style];
+    } while (style != -1);
+    return NULL;  // No formatter found.
+ }
+
+static UBool getStringWithFallback(
+        const UResourceBundle *resource,
         const char *key,
         UnicodeString &result,
         UErrorCode &status) {
-    if (U_FAILURE(status)) {
-        return FALSE;
-    }
     int32_t len = 0;
-    const UChar *resStr = ures_getStringByKey(
+    const UChar *resStr = ures_getStringByKeyWithFallback(
         resource, key, &len, &status);
-    if (status == U_MISSING_RESOURCE_ERROR) {
-        result.remove();
-        status = U_ZERO_ERROR;
-        return TRUE;
-    }
     if (U_FAILURE(status)) {
         return FALSE;
     }
@@ -119,21 +162,9 @@ static UBool getOptionalStringWithFallback(
     return TRUE;
 }
 
-static UBool getString(
-        const UResourceBundle *resource, 
-        UnicodeString &result,
-        UErrorCode &status) {
-    int32_t len = 0;
-    const UChar *resStr = ures_getString(resource, &len, &status);
-    if (U_FAILURE(status)) {
-        return FALSE;
-    }
-    result.setTo(TRUE, resStr, len);
-    return TRUE;
-}
 
 static UBool getStringByIndex(
-        const UResourceBundle *resource, 
+        const UResourceBundle *resource,
         int32_t idx,
         UnicodeString &result,
         UErrorCode &status) {
@@ -147,292 +178,426 @@ static UBool getStringByIndex(
     return TRUE;
 }
 
-static void initAbsoluteUnit(
-            const UResourceBundle *resource,
-            const UnicodeString &unitName,
-            UnicodeString *absoluteUnit,
-            UErrorCode &status) {
-    getStringWithFallback(
-            resource, 
-            "-1",
-            absoluteUnit[UDAT_DIRECTION_LAST],
-            status);
-    getStringWithFallback(
-            resource, 
-            "0",
-            absoluteUnit[UDAT_DIRECTION_THIS],
-            status);
-    getStringWithFallback(
-            resource, 
-            "1",
-            absoluteUnit[UDAT_DIRECTION_NEXT],
-            status);
-    getOptionalStringWithFallback(
-            resource,
-            "-2",
-            absoluteUnit[UDAT_DIRECTION_LAST_2],
-            status);
-    getOptionalStringWithFallback(
-            resource,
-            "2",
-            absoluteUnit[UDAT_DIRECTION_NEXT_2],
-            status);
-    absoluteUnit[UDAT_DIRECTION_PLAIN] = unitName;
-}
+namespace {
 
-static void initQuantityFormatter(
-        const UResourceBundle *resource,
-        QuantityFormatter &formatter,
-        UErrorCode &status) {
-    if (U_FAILURE(status)) {
-        return;
+/**
+ * Sink for enumerating all of the measurement unit display names.
+ *
+ * More specific bundles (en_GB) are enumerated before their parents (en_001, en, root):
+ * Only store a value if it is still missing, that is, it has not been overridden.
+ */
+struct RelDateTimeFmtDataSink : public ResourceSink {
+
+    /**
+     * Sink for patterns for relative dates and times. For example,
+     * fields/relative/...
+     */
+
+    // Generic unit enum for storing Unit info.
+    typedef enum RelAbsUnit {
+        INVALID_UNIT = -1,
+        SECOND,
+        MINUTE,
+        HOUR,
+        DAY,
+        WEEK,
+        MONTH,
+        QUARTER,
+        YEAR,
+        SUNDAY,
+        MONDAY,
+        TUESDAY,
+        WEDNESDAY,
+        THURSDAY,
+        FRIDAY,
+        SATURDAY
+    } RelAbsUnit;
+
+    static int32_t relUnitFromGeneric(RelAbsUnit genUnit) {
+        // Converts the generic units to UDAT_RELATIVE version.
+        switch (genUnit) {
+            case SECOND:
+                return UDAT_RELATIVE_SECONDS;
+            case MINUTE:
+                return UDAT_RELATIVE_MINUTES;
+            case HOUR:
+                return UDAT_RELATIVE_HOURS;
+            case DAY:
+                return UDAT_RELATIVE_DAYS;
+            case WEEK:
+                return UDAT_RELATIVE_WEEKS;
+            case MONTH:
+                return UDAT_RELATIVE_MONTHS;
+            /*
+             * case QUARTER:
+             * return UDATE_RELATIVE_QUARTERS;
+             */
+            case YEAR:
+                return UDAT_RELATIVE_YEARS;
+            default:
+                return -1;
+        }
     }
-    int32_t size = ures_getSize(resource);
-    for (int32_t i = 0; i < size; ++i) {
-        LocalUResourceBundlePointer pluralBundle(
-                ures_getByIndex(resource, i, NULL, &status));
-        if (U_FAILURE(status)) {
-            return;
+
+    static int32_t absUnitFromGeneric(RelAbsUnit genUnit) {
+        // Converts the generic units to UDAT_RELATIVE version.
+        switch (genUnit) {
+            case DAY:
+                return UDAT_ABSOLUTE_DAY;
+            case WEEK:
+                return UDAT_ABSOLUTE_WEEK;
+            case MONTH:
+                return UDAT_ABSOLUTE_MONTH;
+            /* TODO: Add in QUARTER
+             *  case QUARTER:
+             * return UDAT_ABSOLUTE_QUARTER;
+             */
+            case YEAR:
+                return UDAT_ABSOLUTE_YEAR;
+            case SUNDAY:
+                return UDAT_ABSOLUTE_SUNDAY;
+            case MONDAY:
+                return UDAT_ABSOLUTE_MONDAY;
+            case TUESDAY:
+                return UDAT_ABSOLUTE_TUESDAY;
+            case WEDNESDAY:
+                return UDAT_ABSOLUTE_WEDNESDAY;
+            case THURSDAY:
+                return UDAT_ABSOLUTE_THURSDAY;
+            case FRIDAY:
+                return UDAT_ABSOLUTE_FRIDAY;
+            case SATURDAY:
+                return UDAT_ABSOLUTE_SATURDAY;
+            default:
+                return -1;
         }
-        UnicodeString rawPattern;
-        if (!getString(pluralBundle.getAlias(), rawPattern, status)) {
-            return;
+    }
+
+    static int32_t keyToDirection(const char* key) {
+        if (uprv_strcmp(key, "-2") == 0) {
+            return UDAT_DIRECTION_LAST_2;
         }
-        if (!formatter.add(
-                ures_getKey(pluralBundle.getAlias()),
-                rawPattern,
-                status)) {
-            return;
+        if (uprv_strcmp(key, "-1") == 0) {
+            return UDAT_DIRECTION_LAST;
+        }
+        if (uprv_strcmp(key, "0") == 0) {
+            return UDAT_DIRECTION_THIS;
+        }
+        if (uprv_strcmp(key, "1") == 0) {
+            return UDAT_DIRECTION_NEXT;
         }
+        if (uprv_strcmp(key, "2") == 0) {
+            return UDAT_DIRECTION_NEXT_2;
+        }
+        return -1;
     }
-}
 
-static void initRelativeUnit(
-        const UResourceBundle *resource,
-        QuantityFormatter *relativeUnit,
-        UErrorCode &status) {
-    LocalUResourceBundlePointer topLevel(
-            ures_getByKeyWithFallback(
-                    resource, "relativeTime", NULL, &status));
-    if (U_FAILURE(status)) {
-        return;
+    // Values kept between levels of parsing the CLDR data.
+    int32_t pastFutureIndex;  // 0 == past or 1 ==  future
+    UDateRelativeDateTimeFormatterStyle style;  // {LONG, SHORT, NARROW}
+    RelAbsUnit genericUnit;
+
+    RelativeDateTimeCacheData &outputData;
+
+    // Constructor
+    RelDateTimeFmtDataSink(RelativeDateTimeCacheData& cacheData)
+        : outputData(cacheData) {
+        // Clear cacheData.fallBackCache
+        cacheData.fallBackCache[UDAT_STYLE_LONG] = -1;
+        cacheData.fallBackCache[UDAT_STYLE_SHORT] = -1;
+        cacheData.fallBackCache[UDAT_STYLE_NARROW] = -1;
     }
-    LocalUResourceBundlePointer futureBundle(ures_getByKeyWithFallback(
-            topLevel.getAlias(), "future", NULL, &status));
-    if (U_FAILURE(status)) {
-        return;
+
+    ~RelDateTimeFmtDataSink();
+
+    // Utility functions
+    static UDateRelativeDateTimeFormatterStyle styleFromString(const char *s) {
+        int32_t len = uprv_strlen(s);
+        if (len >= 7 && uprv_strcmp(s + len - 7, "-narrow") == 0) {
+            return UDAT_STYLE_NARROW;
+        }
+        if (len >= 6 && uprv_strcmp(s + len - 6, "-short") == 0) {
+            return UDAT_STYLE_SHORT;
+        }
+        return UDAT_STYLE_LONG;
     }
-    initQuantityFormatter(
-            futureBundle.getAlias(),
-            relativeUnit[1],
-            status);
-    LocalUResourceBundlePointer pastBundle(ures_getByKeyWithFallback(
-            topLevel.getAlias(), "past", NULL, &status));
-    if (U_FAILURE(status)) {
-        return;
+
+    static int32_t styleSuffixLength(UDateRelativeDateTimeFormatterStyle style) {
+        switch (style) {
+            case UDAT_STYLE_NARROW:
+                return 7;
+            case UDAT_STYLE_SHORT:
+                return 6;
+            default:
+                return 0;
+        }
     }
-    initQuantityFormatter(
-            pastBundle.getAlias(),
-            relativeUnit[0],
-            status);
-}
 
-static void initRelativeUnit(
-        const UResourceBundle *resource,
-        const char *path,
-        QuantityFormatter *relativeUnit,
-        UErrorCode &status) {
-    LocalUResourceBundlePointer topLevel(
-            ures_getByKeyWithFallback(resource, path, NULL, &status));
-    if (U_FAILURE(status)) {
-        return;
+    // Utility functions
+    static UDateRelativeDateTimeFormatterStyle styleFromAliasUnicodeString(UnicodeString s) {
+        static const UChar narrow[7] = {0x002D, 0x006E, 0x0061, 0x0072, 0x0072, 0x006F, 0x0077};
+        static const UChar sshort[6] = {0x002D, 0x0073, 0x0068, 0x006F, 0x0072, 0x0074,};
+        if (s.endsWith(narrow, 7)) {
+            return UDAT_STYLE_NARROW;
+        }
+        if (s.endsWith(sshort, 6)) {
+            return UDAT_STYLE_SHORT;
+        }
+        return UDAT_STYLE_LONG;
     }
-    initRelativeUnit(topLevel.getAlias(), relativeUnit, status);
-}
 
-static void addTimeUnit(
-        const UResourceBundle *resource,
-        const char *path,
-        QuantityFormatter *relativeUnit,
-        UnicodeString *absoluteUnit,
-        UErrorCode &status) {
-    LocalUResourceBundlePointer topLevel(
-            ures_getByKeyWithFallback(resource, path, NULL, &status));
-    if (U_FAILURE(status)) {
-        return;
+    static RelAbsUnit unitOrNegativeFromString(const char* keyword, int32_t length) {
+        // Quick check from string to enum.
+        switch (length) {
+            case 3:
+                if (uprv_strncmp(keyword, "day", length) == 0) {
+                    return DAY;
+                } else if (uprv_strncmp(keyword, "sun", length) == 0) {
+                    return SUNDAY;
+                } else if (uprv_strncmp(keyword, "mon", length) == 0) {
+                    return MONDAY;
+                } else if (uprv_strncmp(keyword, "tue", length) == 0) {
+                    return TUESDAY;
+                } else if (uprv_strncmp(keyword, "wed", length) == 0) {
+                    return WEDNESDAY;
+                } else if (uprv_strncmp(keyword, "thu", length) == 0) {
+                    return THURSDAY;
+                } else if (uprv_strncmp(keyword, "fri", length) == 0) {
+                    return FRIDAY;
+                } else if (uprv_strncmp(keyword, "sat", length) == 0) {
+                    return SATURDAY;
+                }
+                break;
+            case 4:
+                if (uprv_strncmp(keyword, "hour", length) == 0) {
+                    return HOUR;
+                } else if (uprv_strncmp(keyword, "week", length) == 0) {
+                    return WEEK;
+                } else if (uprv_strncmp(keyword, "year", length) == 0) {
+                    return YEAR;
+                }
+                break;
+            case 5:
+                if (uprv_strncmp(keyword, "month", length) == 0) {
+                    return MONTH;
+                }
+                break;
+            case 6:
+                if (uprv_strncmp(keyword, "minute", length) == 0) {
+                    return MINUTE;
+                } else if (uprv_strncmp(keyword, "second", length) == 0) {
+                    return SECOND;
+                }
+                break;
+            case 7:
+                if (uprv_strncmp(keyword, "quarter", length) == 0) {
+                    return QUARTER;  // TODO: Check @provisional
+                  }
+                break;
+            default:
+                break;
+        }
+        return INVALID_UNIT;
     }
-    initRelativeUnit(topLevel.getAlias(), relativeUnit, status);
-    UnicodeString unitName;
-    if (!getStringWithFallback(topLevel.getAlias(), "dn", unitName, status)) {
-        return;
+
+    void handlePlainDirection(ResourceValue &value, UErrorCode &errorCode) {
+        // Handle Display Name for PLAIN direction for some units.
+        if (U_FAILURE(errorCode)) { return; }
+
+        int32_t absUnit = absUnitFromGeneric(genericUnit);
+        if (absUnit < 0) {
+          return;  // Not interesting.
+        }
+
+        // Store displayname if not set.
+        if (outputData.absoluteUnits[style]
+            [absUnit][UDAT_DIRECTION_PLAIN].isEmpty()) {
+            outputData.absoluteUnits[style]
+                [absUnit][UDAT_DIRECTION_PLAIN].fastCopyFrom(value.getUnicodeString(errorCode));
+            return;
+        }
     }
-    // TODO(Travis Keep): This is a hack to get around CLDR bug 6818.
-    const char *localeId = ures_getLocaleByType(
-            topLevel.getAlias(), ULOC_ACTUAL_LOCALE, &status);
-    if (U_FAILURE(status)) {
-        return;
+
+    void consumeTableRelative(const char *key, ResourceValue &value, UErrorCode &errorCode) {
+        ResourceTable unitTypesTable = value.getTable(errorCode);
+        if (U_FAILURE(errorCode)) { return; }
+
+        for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) {
+            if (value.getType() == URES_STRING) {
+                int32_t direction = keyToDirection(key);
+                if (direction < 0) {
+                  continue;
+                }
+
+                int32_t relUnitIndex = relUnitFromGeneric(genericUnit);
+                if (relUnitIndex == UDAT_RELATIVE_SECONDS && uprv_strcmp(key, "0") == 0 &&
+                    outputData.absoluteUnits[style][UDAT_ABSOLUTE_NOW][UDAT_DIRECTION_PLAIN].isEmpty()) {
+                    // Handle "NOW"
+                    outputData.absoluteUnits[style][UDAT_ABSOLUTE_NOW]
+                        [UDAT_DIRECTION_PLAIN].fastCopyFrom(value.getUnicodeString(errorCode));
+                }
+
+                int32_t absUnitIndex = absUnitFromGeneric(genericUnit);
+                if (absUnitIndex < 0) {
+                    continue;
+                }
+                // Only reset if slot is empty.
+                if (outputData.absoluteUnits[style][absUnitIndex][direction].isEmpty()) {
+                    outputData.absoluteUnits[style][absUnitIndex]
+                        [direction].fastCopyFrom(value.getUnicodeString(errorCode));
+                }
+            }
+        }
     }
-    Locale locale(localeId);
-    if (uprv_strcmp("en", locale.getLanguage()) == 0) {
-         unitName.toLower();
+
+    void consumeTimeDetail(int32_t relUnitIndex,
+                           const char *key, ResourceValue &value, UErrorCode &errorCode) {
+        ResourceTable unitTypesTable = value.getTable(errorCode);
+        if (U_FAILURE(errorCode)) { return; }
+
+          for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) {
+            if (value.getType() == URES_STRING) {
+                int32_t pluralIndex = StandardPlural::indexOrNegativeFromString(key);
+                if (pluralIndex >= 0) {
+                    SimpleFormatter **patterns =
+                        outputData.relativeUnitsFormatters[style][relUnitIndex]
+                        [pastFutureIndex];
+                    // Only set if not already established.
+                    if (patterns[pluralIndex] == NULL) {
+                        patterns[pluralIndex] = new SimpleFormatter(
+                            value.getUnicodeString(errorCode), 0, 1, errorCode);
+                        if (patterns[pluralIndex] == NULL) {
+                            errorCode = U_MEMORY_ALLOCATION_ERROR;
+                        }
+                    }
+                }
+            }
+        }
     }
-    // end hack
-    ures_getByKeyWithFallback(
-            topLevel.getAlias(), "relative", topLevel.getAlias(), &status);
-    if (U_FAILURE(status)) {
-        return;
+
+    void consumeTableRelativeTime(const char *key, ResourceValue &value, UErrorCode &errorCode) {
+        ResourceTable relativeTimeTable = value.getTable(errorCode);
+        if (U_FAILURE(errorCode)) { return; }
+
+        int32_t relUnitIndex = relUnitFromGeneric(genericUnit);
+        if (relUnitIndex < 0) {
+            return;
+        }
+        for (int32_t i = 0; relativeTimeTable.getKeyAndValue(i, key, value); ++i) {
+            if (uprv_strcmp(key, "past") == 0) {
+                pastFutureIndex = 0;
+            } else if (uprv_strcmp(key, "future") == 0) {
+                pastFutureIndex = 1;
+            } else {
+                // Unknown key.
+                continue;
+            }
+            consumeTimeDetail(relUnitIndex, key, value, errorCode);
+        }
     }
-    initAbsoluteUnit(
-            topLevel.getAlias(),
-            unitName,
-            absoluteUnit,
-            status);
-}
 
-static void readDaysOfWeek(
-        const UResourceBundle *resource,
-        const char *path,
-        UnicodeString *daysOfWeek,
-        UErrorCode &status) {
-    LocalUResourceBundlePointer topLevel(
-            ures_getByKeyWithFallback(resource, path, NULL, &status));
-    if (U_FAILURE(status)) {
-        return;
+    void consumeAlias(const char *key, const ResourceValue &value, UErrorCode &errorCode) {
+
+        UDateRelativeDateTimeFormatterStyle sourceStyle = styleFromString(key);
+        const UnicodeString valueStr = value.getAliasUnicodeString(errorCode);
+        if (U_FAILURE(errorCode)) { return; }
+
+        UDateRelativeDateTimeFormatterStyle targetStyle =
+            styleFromAliasUnicodeString(valueStr);
+
+        if (sourceStyle == targetStyle) {
+            errorCode = U_INVALID_FORMAT_ERROR;
+            return;
+        }
+        if (outputData.fallBackCache[sourceStyle] != -1 &&
+            outputData.fallBackCache[sourceStyle] != targetStyle) {
+            errorCode = U_INVALID_FORMAT_ERROR;
+            return;
+        }
+        outputData.fallBackCache[sourceStyle] = targetStyle;
     }
-    int32_t size = ures_getSize(topLevel.getAlias());
-    if (size != 7) {
-        status = U_INTERNAL_PROGRAM_ERROR;
-        return;
+
+    void consumeTimeUnit(const char *key, ResourceValue &value, UErrorCode &errorCode) {
+        ResourceTable unitTypesTable = value.getTable(errorCode);
+        if (U_FAILURE(errorCode)) { return; }
+
+        for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) {
+            // Handle display name.
+            if (uprv_strcmp(key, "dn") == 0 && value.getType() == URES_STRING) {
+                handlePlainDirection(value, errorCode);
+            }
+            if (value.getType() == URES_TABLE) {
+                if (uprv_strcmp(key, "relative") == 0) {
+                    consumeTableRelative(key, value, errorCode);
+                } else if (uprv_strcmp(key, "relativeTime") == 0) {
+                    consumeTableRelativeTime(key, value, errorCode);
+                }
+            }
+        }
     }
-    for (int32_t i = 0; i < size; ++i) {
-        if (!getStringByIndex(topLevel.getAlias(), i, daysOfWeek[i], status)) {
-            return;
+
+    virtual void put(const char *key, ResourceValue &value,
+                     UBool /*noFallback*/, UErrorCode &errorCode) {
+        // Main entry point to sink
+        ResourceTable table = value.getTable(errorCode);
+        if (U_FAILURE(errorCode)) { return; }
+        for (int32_t i = 0; table.getKeyAndValue(i, key, value); ++i) {
+            if (value.getType() == URES_ALIAS) {
+                consumeAlias(key, value, errorCode);
+            } else {
+                style = styleFromString(key);
+                int32_t unitSize = uprv_strlen(key) - styleSuffixLength(style);
+                genericUnit = unitOrNegativeFromString(key, unitSize);
+                if (style >= 0 && genericUnit != INVALID_UNIT) {
+                    consumeTimeUnit(key, value, errorCode);
+                }
+            }
         }
     }
-}
 
-static void addWeekDay(
-        const UResourceBundle *resource,
-        const char *path,
-        const UnicodeString *daysOfWeek,
-        UDateAbsoluteUnit absoluteUnit,
-        UnicodeString absoluteUnits[][UDAT_DIRECTION_COUNT],
-        UErrorCode &status) {
-    LocalUResourceBundlePointer topLevel(
-            ures_getByKeyWithFallback(resource, path, NULL, &status));
-    if (U_FAILURE(status)) {
-        return;
+};
+
+// Virtual destructors must be defined out of line.
+RelDateTimeFmtDataSink::~RelDateTimeFmtDataSink() {}
+} // namespace
+
+static const DateFormatSymbols::DtWidthType styleToDateFormatSymbolWidth[UDAT_STYLE_COUNT] = {
+  DateFormatSymbols::WIDE, DateFormatSymbols::SHORT, DateFormatSymbols::NARROW
+};
+
+// Get days of weeks from the DateFormatSymbols class.
+static void loadWeekdayNames(UnicodeString absoluteUnits[UDAT_STYLE_COUNT]
+                                 [UDAT_ABSOLUTE_UNIT_COUNT][UDAT_DIRECTION_COUNT],
+                             const char* localeId,
+                             UErrorCode& status) {
+    Locale locale(localeId);
+    DateFormatSymbols dfSym(locale, status);
+    for (int32_t style = 0; style < UDAT_STYLE_COUNT; ++style) {
+        DateFormatSymbols::DtWidthType dtfmtWidth = styleToDateFormatSymbolWidth[style];
+        int32_t count;
+        const UnicodeString* weekdayNames =
+            dfSym.getWeekdays(count, DateFormatSymbols::STANDALONE, dtfmtWidth);
+        for (int32_t dayIndex = UDAT_ABSOLUTE_SUNDAY;
+                dayIndex <= UDAT_ABSOLUTE_SATURDAY; ++ dayIndex) {
+            int32_t dateSymbolIndex = (dayIndex - UDAT_ABSOLUTE_SUNDAY) + UCAL_SUNDAY;
+            absoluteUnits[style][dayIndex][UDAT_DIRECTION_PLAIN].fastCopyFrom(
+                weekdayNames[dateSymbolIndex]);
+        }
     }
-    initAbsoluteUnit(
-            topLevel.getAlias(),
-            daysOfWeek[absoluteUnit - UDAT_ABSOLUTE_SUNDAY],
-            absoluteUnits[absoluteUnit],
-            status);
 }
 
 static UBool loadUnitData(
         const UResourceBundle *resource,
         RelativeDateTimeCacheData &cacheData,
+        const char* localeId,
         UErrorCode &status) {
-    addTimeUnit(
-            resource,
-            "fields/day",
-            cacheData.relativeUnits[UDAT_RELATIVE_DAYS],
-            cacheData.absoluteUnits[UDAT_ABSOLUTE_DAY],
-            status);
-    addTimeUnit(
-            resource,
-            "fields/week",
-            cacheData.relativeUnits[UDAT_RELATIVE_WEEKS],
-            cacheData.absoluteUnits[UDAT_ABSOLUTE_WEEK],
-            status);
-    addTimeUnit(
-            resource,
-            "fields/month",
-            cacheData.relativeUnits[UDAT_RELATIVE_MONTHS],
-            cacheData.absoluteUnits[UDAT_ABSOLUTE_MONTH],
-            status);
-    addTimeUnit(
-            resource,
-            "fields/year",
-            cacheData.relativeUnits[UDAT_RELATIVE_YEARS],
-            cacheData.absoluteUnits[UDAT_ABSOLUTE_YEAR],
-            status);
-    initRelativeUnit(
-            resource,
-            "fields/second",
-            cacheData.relativeUnits[UDAT_RELATIVE_SECONDS],
-            status);
-    initRelativeUnit(
-            resource,
-            "fields/minute",
-            cacheData.relativeUnits[UDAT_RELATIVE_MINUTES],
-            status);
-    initRelativeUnit(
-            resource,
-            "fields/hour",
-            cacheData.relativeUnits[UDAT_RELATIVE_HOURS],
-            status);
-    getStringWithFallback(
-            resource,
-            "fields/second/relative/0",
-            cacheData.absoluteUnits[UDAT_ABSOLUTE_NOW][UDAT_DIRECTION_PLAIN],
-            status);
-    UnicodeString daysOfWeek[7];
-    readDaysOfWeek(
-            resource,
-            "calendar/gregorian/dayNames/stand-alone/wide",
-            daysOfWeek,
-            status);
-    addWeekDay(
-            resource,
-            "fields/mon/relative",
-            daysOfWeek,
-            UDAT_ABSOLUTE_MONDAY,
-            cacheData.absoluteUnits,
-            status);
-    addWeekDay(
-            resource,
-            "fields/tue/relative",
-            daysOfWeek,
-            UDAT_ABSOLUTE_TUESDAY,
-            cacheData.absoluteUnits,
-            status);
-    addWeekDay(
-            resource,
-            "fields/wed/relative",
-            daysOfWeek,
-            UDAT_ABSOLUTE_WEDNESDAY,
-            cacheData.absoluteUnits,
-            status);
-    addWeekDay(
-            resource,
-            "fields/thu/relative",
-            daysOfWeek,
-            UDAT_ABSOLUTE_THURSDAY,
-            cacheData.absoluteUnits,
-            status);
-    addWeekDay(
-            resource,
-            "fields/fri/relative",
-            daysOfWeek,
-            UDAT_ABSOLUTE_FRIDAY,
-            cacheData.absoluteUnits,
-            status);
-    addWeekDay(
-            resource,
-            "fields/sat/relative",
-            daysOfWeek,
-            UDAT_ABSOLUTE_SATURDAY,
-            cacheData.absoluteUnits,
-            status);
-    addWeekDay(
-            resource,
-            "fields/sun/relative",
-            daysOfWeek,
-            UDAT_ABSOLUTE_SUNDAY,
-            cacheData.absoluteUnits,
-            status);
+
+    RelDateTimeFmtDataSink sink(cacheData);
+
+    ures_getAllItemsWithFallback(resource, "fields", sink, status);
+
+    // Get the weekday names from DateFormatSymbols.
+    loadWeekdayNames(cacheData.absoluteUnits, localeId, status);
     return U_SUCCESS(status);
 }
 
@@ -460,7 +625,7 @@ static UBool getDateTimePattern(
     }
     int32_t size = ures_getSize(topLevel.getAlias());
     if (size <= 8) {
-        // Oops, size is to small to access the index that we want, fallback
+        // Oops, size is too small to access the index that we want, fallback
         // to a hard-coded value.
         result = UNICODE_STRING_SIMPLE("{1} {0}");
         return TRUE;
@@ -468,9 +633,9 @@ static UBool getDateTimePattern(
     return getStringByIndex(topLevel.getAlias(), 8, result, status);
 }
 
-// Creates RelativeDateTimeFormatter specific data for a given locale
-static SharedObject *U_CALLCONV createData(
-        const char *localeId, UErrorCode &status) {
+template<> U_I18N_API
+const RelativeDateTimeCacheData *LocaleCacheKey<RelativeDateTimeCacheData>::createObject(const void * /*unused*/, UErrorCode &status) const {
+    const char *localeId = fLoc.getName();
     LocalUResourceBundlePointer topLevel(ures_open(NULL, localeId, &status));
     if (U_FAILURE(status)) {
         return NULL;
@@ -484,6 +649,7 @@ static SharedObject *U_CALLCONV createData(
     if (!loadUnitData(
             topLevel.getAlias(),
             *result,
+            localeId,
             status)) {
         return NULL;
     }
@@ -492,87 +658,136 @@ static SharedObject *U_CALLCONV createData(
         return NULL;
     }
     result->adoptCombinedDateAndTime(
-            new MessageFormat(dateTimePattern, localeId, status));
+            new SimpleFormatter(dateTimePattern, 2, 2, status));
     if (U_FAILURE(status)) {
         return NULL;
     }
+    result->addRef();
     return result.orphan();
 }
 
-static void U_CALLCONV cacheInit(UErrorCode &status) {
-    U_ASSERT(gCache == NULL);
-    ucln_i18n_registerCleanup(UCLN_I18N_RELDATEFMT, reldatefmt_cleanup);
-    gCache = new SimpleLRUCache(100, &createData, status);
-    if (U_FAILURE(status)) {
-        delete gCache;
-        gCache = NULL;
-    }
-}
-
-static UBool getFromCache(
-        const char *locale,
-        const RelativeDateTimeCacheData *&ptr,
-        UErrorCode &status) {
-    umtx_initOnce(gCacheInitOnce, &cacheInit, status);
-    if (U_FAILURE(status)) {
-        return FALSE;
-    }
-    Mutex lock(&gCacheMutex);
-    gCache->get(locale, ptr, status);
-    return U_SUCCESS(status);
+RelativeDateTimeFormatter::RelativeDateTimeFormatter(UErrorCode& status) :
+        fCache(NULL),
+        fNumberFormat(NULL),
+        fPluralRules(NULL),
+        fStyle(UDAT_STYLE_LONG),
+        fContext(UDISPCTX_CAPITALIZATION_NONE),
+        fOptBreakIterator(NULL) {
+    init(NULL, NULL, status);
 }
 
-RelativeDateTimeFormatter::RelativeDateTimeFormatter(UErrorCode& status)
-        : cache(NULL), numberFormat(NULL), pluralRules(NULL) {
-    init(Locale::getDefault(), NULL, status);
+RelativeDateTimeFormatter::RelativeDateTimeFormatter(
+        const Locale& locale, UErrorCode& status) :
+        fCache(NULL),
+        fNumberFormat(NULL),
+        fPluralRules(NULL),
+        fStyle(UDAT_STYLE_LONG),
+        fContext(UDISPCTX_CAPITALIZATION_NONE),
+        fOptBreakIterator(NULL),
+        fLocale(locale) {
+    init(NULL, NULL, status);
 }
 
 RelativeDateTimeFormatter::RelativeDateTimeFormatter(
-        const Locale& locale, UErrorCode& status)
-        : cache(NULL), numberFormat(NULL), pluralRules(NULL) {
-    init(locale, NULL, status);
+        const Locale& locale, NumberFormat *nfToAdopt, UErrorCode& status) :
+        fCache(NULL),
+        fNumberFormat(NULL),
+        fPluralRules(NULL),
+        fStyle(UDAT_STYLE_LONG),
+        fContext(UDISPCTX_CAPITALIZATION_NONE),
+        fOptBreakIterator(NULL),
+        fLocale(locale) {
+    init(nfToAdopt, NULL, status);
 }
 
 RelativeDateTimeFormatter::RelativeDateTimeFormatter(
-        const Locale& locale, NumberFormat *nfToAdopt, UErrorCode& status)
-        : cache(NULL), numberFormat(NULL), pluralRules(NULL) {
-    init(locale, nfToAdopt, status);
+        const Locale& locale,
+        NumberFormat *nfToAdopt,
+        UDateRelativeDateTimeFormatterStyle styl,
+        UDisplayContext capitalizationContext,
+        UErrorCode& status) :
+        fCache(NULL),
+        fNumberFormat(NULL),
+        fPluralRules(NULL),
+        fStyle(styl),
+        fContext(capitalizationContext),
+        fOptBreakIterator(NULL),
+        fLocale(locale) {
+    if (U_FAILURE(status)) {
+        return;
+    }
+    if ((capitalizationContext >> 8) != UDISPCTX_TYPE_CAPITALIZATION) {
+        status = U_ILLEGAL_ARGUMENT_ERROR;
+        return;
+    }
+    if (capitalizationContext == UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE) {
+        BreakIterator *bi = BreakIterator::createSentenceInstance(locale, status);
+        if (U_FAILURE(status)) {
+            return;
+        }
+        init(nfToAdopt, bi, status);
+    } else {
+        init(nfToAdopt, NULL, status);
+    }
 }
 
 RelativeDateTimeFormatter::RelativeDateTimeFormatter(
         const RelativeDateTimeFormatter& other)
-        : cache(other.cache),
-          numberFormat(other.numberFormat),
-          pluralRules(other.pluralRules) {
-    cache->addRef();
-    numberFormat->addRef();
-    pluralRules->addRef();
+        : UObject(other),
+          fCache(other.fCache),
+          fNumberFormat(other.fNumberFormat),
+          fPluralRules(other.fPluralRules),
+          fStyle(other.fStyle),
+          fContext(other.fContext),
+          fOptBreakIterator(other.fOptBreakIterator),
+          fLocale(other.fLocale) {
+    fCache->addRef();
+    fNumberFormat->addRef();
+    fPluralRules->addRef();
+    if (fOptBreakIterator != NULL) {
+      fOptBreakIterator->addRef();
+    }
 }
 
 RelativeDateTimeFormatter& RelativeDateTimeFormatter::operator=(
         const RelativeDateTimeFormatter& other) {
     if (this != &other) {
-        SharedObject::copyPtr(other.cache, cache);
-        SharedObject::copyPtr(other.numberFormat, numberFormat);
-        SharedObject::copyPtr(other.pluralRules, pluralRules);
+        SharedObject::copyPtr(other.fCache, fCache);
+        SharedObject::copyPtr(other.fNumberFormat, fNumberFormat);
+        SharedObject::copyPtr(other.fPluralRules, fPluralRules);
+        SharedObject::copyPtr(other.fOptBreakIterator, fOptBreakIterator);
+        fStyle = other.fStyle;
+        fContext = other.fContext;
+        fLocale = other.fLocale;
     }
     return *this;
 }
 
 RelativeDateTimeFormatter::~RelativeDateTimeFormatter() {
-    if (cache != NULL) {
-        cache->removeRef();
+    if (fCache != NULL) {
+        fCache->removeRef();
     }
-    if (numberFormat != NULL) {
-        numberFormat->removeRef();
+    if (fNumberFormat != NULL) {
+        fNumberFormat->removeRef();
     }
-    if (pluralRules != NULL) {
-        pluralRules->removeRef();
+    if (fPluralRules != NULL) {
+        fPluralRules->removeRef();
+    }
+    if (fOptBreakIterator != NULL) {
+        fOptBreakIterator->removeRef();
     }
 }
 
 const NumberFormat& RelativeDateTimeFormatter::getNumberFormat() const {
-    return **numberFormat;
+    return **fNumberFormat;
+}
+
+UDisplayContext RelativeDateTimeFormatter::getCapitalizationContext() const {
+    return fContext;
+}
+
+UDateRelativeDateTimeFormatterStyle RelativeDateTimeFormatter::getFormatStyle() const {
+    return fStyle;
 }
 
 UnicodeString& RelativeDateTimeFormatter::format(
@@ -587,13 +802,59 @@ UnicodeString& RelativeDateTimeFormatter::format(
     }
     int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0;
     FieldPosition pos(FieldPosition::DONT_CARE);
-    return cache->relativeUnits[unit][bFuture].format(
-            quantity,
-            **numberFormat,
-            **pluralRules,
-            appendTo,
-            pos,
-            status);
+
+    UnicodeString result;
+    UnicodeString formattedNumber;
+
+    StandardPlural::Form pluralIndex = QuantityFormatter::selectPlural(
+        quantity, **fNumberFormat, **fPluralRules, formattedNumber, pos,
+        status);
+
+    const SimpleFormatter* formatter =
+        fCache->getRelativeUnitFormatter(fStyle, unit, bFuture, pluralIndex);
+    if (formatter == NULL) {
+        // TODO: WARN - look at quantity formatter's action with an error.
+        status = U_INVALID_FORMAT_ERROR;
+        return appendTo;
+    }
+    formatter->format(formattedNumber, result, status);
+    adjustForContext(result);
+    return appendTo.append(result);
+}
+
+UnicodeString& RelativeDateTimeFormatter::formatNumeric(
+        double offset, URelativeDateTimeUnit unit,
+        UnicodeString& appendTo, UErrorCode& status) const {
+    if (U_FAILURE(status)) {
+        return appendTo;
+    }
+    // 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.
+    // In the meantime do a quick bring-up by calling the old format method; this
+    // leaves some holes (even for data that is currently available, such as quarter).
+    // When the new CLDR data is available, update the data storage accordingly,
+    // rewrite this to use it directly, and rewrite the old format method to call this
+    // new one; that is covered by http://bugs.icu-project.org/trac/ticket/12171.
+    UDateRelativeUnit relunit = UDAT_RELATIVE_UNIT_COUNT;
+    switch (unit) {
+        case UDAT_REL_UNIT_YEAR:    relunit = UDAT_RELATIVE_YEARS; break;
+        case UDAT_REL_UNIT_MONTH:   relunit = UDAT_RELATIVE_MONTHS; break;
+        case UDAT_REL_UNIT_WEEK:    relunit = UDAT_RELATIVE_WEEKS; break;
+        case UDAT_REL_UNIT_DAY:     relunit = UDAT_RELATIVE_DAYS; break;
+        case UDAT_REL_UNIT_HOUR:    relunit = UDAT_RELATIVE_HOURS; break;
+        case UDAT_REL_UNIT_MINUTE:  relunit = UDAT_RELATIVE_MINUTES; break;
+        case UDAT_REL_UNIT_SECOND:  relunit = UDAT_RELATIVE_SECONDS; break;
+        default: // a unit that the above method does not handle
+            status = U_UNSUPPORTED_ERROR;
+            return appendTo;
+    }
+    UDateDirection direction = UDAT_DIRECTION_NEXT;
+    if (std::signbit(offset)) { // needed to handle -0.0
+        direction = UDAT_DIRECTION_LAST;
+        offset = -offset;
+    }
+    return format(offset, direction, relunit, appendTo, status);
 }
 
 UnicodeString& RelativeDateTimeFormatter::format(
@@ -606,41 +867,129 @@ UnicodeString& RelativeDateTimeFormatter::format(
         status = U_ILLEGAL_ARGUMENT_ERROR;
         return appendTo;
     }
-    return appendTo.append(cache->absoluteUnits[unit][direction]);
+
+    // Get string using fallback.
+    UnicodeString result;
+    result.fastCopyFrom(fCache->getAbsoluteUnitString(fStyle, unit, direction));
+    if (fOptBreakIterator != NULL) {
+        adjustForContext(result);
+    }
+    return appendTo.append(result);
+}
+
+UnicodeString& RelativeDateTimeFormatter::format(
+        double offset, URelativeDateTimeUnit unit,
+        UnicodeString& appendTo, UErrorCode& status) const {
+    if (U_FAILURE(status)) {
+        return appendTo;
+    }
+    // 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.
+    // In the meantime do a quick bring-up by calling the old format method; this
+    // leaves some holes (even for data that is currently available, such as quarter).
+    // When the new CLDR data is available, update the data storage accordingly,
+    // rewrite this to use it directly, and rewrite the old format method to call this
+    // new one; that is covered by http://bugs.icu-project.org/trac/ticket/12171.
+    UDateDirection direction = UDAT_DIRECTION_COUNT;
+    if (offset > -2.1 && offset < 2.1) {
+        // Allow a 1% epsilon, so offsets in -1.01..-0.99 map to LAST
+        double offsetx100 = offset * 100.0;
+        int32_t intoffset = (offsetx100 < 0)? (int32_t)(offsetx100-0.5) : (int32_t)(offsetx100+0.5);
+        switch (intoffset) {
+            case -200/*-2*/: direction = UDAT_DIRECTION_LAST_2; break;
+            case -100/*-1*/: direction = UDAT_DIRECTION_LAST; break;
+            case    0/* 0*/: direction = UDAT_DIRECTION_THIS; break;
+            case  100/* 1*/: direction = UDAT_DIRECTION_NEXT; break;
+            case  200/* 2*/: direction = UDAT_DIRECTION_NEXT_2; break;
+            default: break;
+       }
+    }
+    UDateAbsoluteUnit absunit = UDAT_ABSOLUTE_UNIT_COUNT;
+    switch (unit) {
+        case UDAT_REL_UNIT_YEAR:    absunit = UDAT_ABSOLUTE_YEAR; break;
+        case UDAT_REL_UNIT_MONTH:   absunit = UDAT_ABSOLUTE_MONTH; break;
+        case UDAT_REL_UNIT_WEEK:    absunit = UDAT_ABSOLUTE_WEEK; break;
+        case UDAT_REL_UNIT_DAY:     absunit = UDAT_ABSOLUTE_DAY; break;
+        case UDAT_REL_UNIT_SECOND:
+            if (direction == UDAT_DIRECTION_THIS) {
+                absunit = UDAT_ABSOLUTE_NOW;
+                direction = UDAT_DIRECTION_PLAIN;
+            }
+            break;
+        case UDAT_REL_UNIT_SUNDAY:  absunit = UDAT_ABSOLUTE_SUNDAY; break;
+        case UDAT_REL_UNIT_MONDAY:  absunit = UDAT_ABSOLUTE_MONDAY; break;
+        case UDAT_REL_UNIT_TUESDAY:  absunit = UDAT_ABSOLUTE_TUESDAY; break;
+        case UDAT_REL_UNIT_WEDNESDAY:  absunit = UDAT_ABSOLUTE_WEDNESDAY; break;
+        case UDAT_REL_UNIT_THURSDAY:  absunit = UDAT_ABSOLUTE_THURSDAY; break;
+        case UDAT_REL_UNIT_FRIDAY:  absunit = UDAT_ABSOLUTE_FRIDAY; break;
+        case UDAT_REL_UNIT_SATURDAY:  absunit = UDAT_ABSOLUTE_SATURDAY; break;
+        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 != NULL) {
+                UnicodeString result(unitFormatString);
+                adjustForContext(result);
+                return appendTo.append(result);
+            } else {
+                return appendTo.append(unitFormatString);
+            }
+        }
+    }
+    // otherwise fallback to formatNumeric
+    return formatNumeric(offset, unit, appendTo, status);
 }
 
 UnicodeString& RelativeDateTimeFormatter::combineDateAndTime(
-    const UnicodeString& relativeDateString, const UnicodeString& timeString,
-    UnicodeString& appendTo, UErrorCode& status) const {
-    Formattable args[2] = {timeString, relativeDateString};
-    FieldPosition fpos(0);
-    return cache->getCombinedDateAndTime()->format(
-            args, 2, appendTo, fpos, status);
+        const UnicodeString& relativeDateString, const UnicodeString& timeString,
+        UnicodeString& appendTo, UErrorCode& status) const {
+    return fCache->getCombinedDateAndTime()->format(
+            timeString, relativeDateString, appendTo, status);
+}
+
+void RelativeDateTimeFormatter::adjustForContext(UnicodeString &str) const {
+    if (fOptBreakIterator == NULL
+        || str.length() == 0 || !u_islower(str.char32At(0))) {
+        return;
+    }
+
+    // Must guarantee that one thread at a time accesses the shared break
+    // iterator.
+    Mutex lock(&gBrkIterMutex);
+    str.toTitle(
+            fOptBreakIterator->get(),
+            fLocale,
+            U_TITLECASE_NO_LOWERCASE | U_TITLECASE_NO_BREAK_ADJUSTMENT);
 }
 
 void RelativeDateTimeFormatter::init(
-        const Locale &locale, NumberFormat *nfToAdopt, UErrorCode &status) {
+        NumberFormat *nfToAdopt,
+        BreakIterator *biToAdopt,
+        UErrorCode &status) {
     LocalPointer<NumberFormat> nf(nfToAdopt);
-    if (!getFromCache(locale.getName(), cache, status)) {
+    LocalPointer<BreakIterator> bi(biToAdopt);
+    UnifiedCache::getByLocale(fLocale, fCache, status);
+    if (U_FAILURE(status)) {
         return;
     }
-    SharedObject::copyPtr(
-            PluralRules::createSharedInstance(
-                    locale, UPLURAL_TYPE_CARDINAL, status),
-            pluralRules);
+    const SharedPluralRules *pr = PluralRules::createSharedInstance(
+            fLocale, UPLURAL_TYPE_CARDINAL, status);
     if (U_FAILURE(status)) {
         return;
     }
-    pluralRules->removeRef();
+    SharedObject::copyPtr(pr, fPluralRules);
+    pr->removeRef();
     if (nf.isNull()) {
-       SharedObject::copyPtr(
-               NumberFormat::createSharedInstance(
-                       locale, UNUM_DECIMAL, status),
-               numberFormat);
+       const SharedNumberFormat *shared = NumberFormat::createSharedInstance(
+               fLocale, UNUM_DECIMAL, status);
         if (U_FAILURE(status)) {
             return;
         }
-        numberFormat->removeRef();
+        SharedObject::copyPtr(shared, fNumberFormat);
+        shared->removeRef();
     } else {
         SharedNumberFormat *shared = new SharedNumberFormat(nf.getAlias());
         if (shared == NULL) {
@@ -648,12 +997,135 @@ void RelativeDateTimeFormatter::init(
             return;
         }
         nf.orphan();
-        SharedObject::copyPtr(shared, numberFormat);
+        SharedObject::copyPtr(shared, fNumberFormat);
+    }
+    if (bi.isNull()) {
+        SharedObject::clearPtr(fOptBreakIterator);
+    } else {
+        SharedBreakIterator *shared = new SharedBreakIterator(bi.getAlias());
+        if (shared == NULL) {
+            status = U_MEMORY_ALLOCATION_ERROR;
+            return;
+        }
+        bi.orphan();
+        SharedObject::copyPtr(shared, fOptBreakIterator);
     }
 }
 
-
 U_NAMESPACE_END
 
-#endif /* !UCONFIG_NO_FORMATTING */
+// Plain C API
+
+U_NAMESPACE_USE
 
+U_CAPI URelativeDateTimeFormatter* U_EXPORT2
+ureldatefmt_open( const char*          locale,
+                  UNumberFormat*       nfToAdopt,
+                  UDateRelativeDateTimeFormatterStyle width,
+                  UDisplayContext      capitalizationContext,
+                  UErrorCode*          status )
+{
+    if (U_FAILURE(*status)) {
+        return NULL;
+    }
+    LocalPointer<RelativeDateTimeFormatter> formatter(new RelativeDateTimeFormatter(Locale(locale),
+                                                              (NumberFormat*)nfToAdopt, width,
+                                                              capitalizationContext, *status), *status);
+    if (U_FAILURE(*status)) {
+        return NULL;
+    }
+    return (URelativeDateTimeFormatter*)formatter.orphan();
+}
+
+U_CAPI void U_EXPORT2
+ureldatefmt_close(URelativeDateTimeFormatter *reldatefmt)
+{
+    delete (RelativeDateTimeFormatter*)reldatefmt;
+}
+
+U_CAPI int32_t U_EXPORT2
+ureldatefmt_formatNumeric( const URelativeDateTimeFormatter* reldatefmt,
+                    double                offset,
+                    URelativeDateTimeUnit unit,
+                    UChar*                result,
+                    int32_t               resultCapacity,
+                    UErrorCode*           status)
+{
+    if (U_FAILURE(*status)) {
+        return 0;
+    }
+    if (result == NULL ? resultCapacity != 0 : resultCapacity < 0) {
+        *status = U_ILLEGAL_ARGUMENT_ERROR;
+        return 0;
+    }
+    UnicodeString res;
+    if (result != NULL) {
+        // NULL destination for pure preflighting: empty dummy string
+        // otherwise, alias the destination buffer (copied from udat_format)
+        res.setTo(result, 0, resultCapacity);
+    }
+    ((RelativeDateTimeFormatter*)reldatefmt)->formatNumeric(offset, unit, res, *status);
+    if (U_FAILURE(*status)) {
+        return 0;
+    }
+    return res.extract(result, resultCapacity, *status);
+}
+
+U_CAPI int32_t U_EXPORT2
+ureldatefmt_format( const URelativeDateTimeFormatter* reldatefmt,
+                    double                offset,
+                    URelativeDateTimeUnit unit,
+                    UChar*                result,
+                    int32_t               resultCapacity,
+                    UErrorCode*           status)
+{
+    if (U_FAILURE(*status)) {
+        return 0;
+    }
+    if (result == NULL ? resultCapacity != 0 : resultCapacity < 0) {
+        *status = U_ILLEGAL_ARGUMENT_ERROR;
+        return 0;
+    }
+    UnicodeString res;
+    if (result != NULL) {
+        // NULL destination for pure preflighting: empty dummy string
+        // otherwise, alias the destination buffer (copied from udat_format)
+        res.setTo(result, 0, resultCapacity);
+    }
+    ((RelativeDateTimeFormatter*)reldatefmt)->format(offset, unit, res, *status);
+    if (U_FAILURE(*status)) {
+        return 0;
+    }
+    return res.extract(result, resultCapacity, *status);
+}
+
+U_CAPI int32_t U_EXPORT2
+ureldatefmt_combineDateAndTime( const URelativeDateTimeFormatter* reldatefmt,
+                    const UChar *     relativeDateString,
+                    int32_t           relativeDateStringLen,
+                    const UChar *     timeString,
+                    int32_t           timeStringLen,
+                    UChar*            result,
+                    int32_t           resultCapacity,
+                    UErrorCode*       status )
+{
+    if (U_FAILURE(*status)) {
+        return 0;
+    }
+    if (result == NULL ? resultCapacity != 0 : resultCapacity < 0 ||
+            (relativeDateString == NULL ? relativeDateStringLen != 0 : relativeDateStringLen < -1) ||
+            (timeString == NULL ? timeStringLen != 0 : timeStringLen < -1)) {
+        *status = U_ILLEGAL_ARGUMENT_ERROR;
+        return 0;
+    }
+    UnicodeString relDateStr((UBool)(relativeDateStringLen == -1), relativeDateString, relativeDateStringLen);
+    UnicodeString timeStr((UBool)(timeStringLen == -1), timeString, timeStringLen);
+    UnicodeString res(result, 0, resultCapacity);
+    ((RelativeDateTimeFormatter*)reldatefmt)->combineDateAndTime(relDateStr, timeStr, res, *status);
+    if (U_FAILURE(*status)) {
+        return 0;
+    }
+    return res.extract(result, resultCapacity, *status);
+}
+
+#endif /* !UCONFIG_NO_FORMATTING */