]> granicus.if.org Git - icu/commitdiff
ICU-8464 Add relative date formatting.
authorTravis Keep <keep94@gmail.com>
Fri, 22 Nov 2013 18:26:22 +0000 (18:26 +0000)
committerTravis Keep <keep94@gmail.com>
Fri, 22 Nov 2013 18:26:22 +0000 (18:26 +0000)
X-SVN-Rev: 34686

20 files changed:
.gitattributes
icu4c/source/common/Makefile.in
icu4c/source/common/common.vcxproj
icu4c/source/common/common.vcxproj.filters
icu4c/source/common/lrucache.cpp [new file with mode: 0644]
icu4c/source/common/lrucache.h [new file with mode: 0644]
icu4c/source/common/sharedptr.h [new file with mode: 0644]
icu4c/source/i18n/Makefile.in
icu4c/source/i18n/i18n.vcxproj
icu4c/source/i18n/i18n.vcxproj.filters
icu4c/source/i18n/reldatefmt.cpp [new file with mode: 0644]
icu4c/source/i18n/ucln_in.h
icu4c/source/i18n/unicode/reldatefmt.h [new file with mode: 0644]
icu4c/source/test/intltest/Makefile.in
icu4c/source/test/intltest/intltest.vcxproj
icu4c/source/test/intltest/intltest.vcxproj.filters
icu4c/source/test/intltest/itformat.cpp
icu4c/source/test/intltest/itutil.cpp
icu4c/source/test/intltest/lrucachetest.cpp [new file with mode: 0644]
icu4c/source/test/intltest/reldatefmttest.cpp [new file with mode: 0644]

index a99b97fce9319440f991649c92d1f197a1097df2..2c6bec14228b83230f594823df2b34d2eb9c7074 100644 (file)
@@ -53,6 +53,9 @@ icu4c/source/aclocal.m4 -text
 icu4c/source/allinone/icucheck.bat -text
 icu4c/source/common/common.vcxproj -text
 icu4c/source/common/common.vcxproj.filters -text
+icu4c/source/common/lrucache.cpp -text
+icu4c/source/common/lrucache.h -text
+icu4c/source/common/sharedptr.h -text
 icu4c/source/data/curr/pool.res -text
 icu4c/source/data/in/coll/invuca.icu -text
 icu4c/source/data/in/coll/ucadata.icu -text
@@ -77,6 +80,8 @@ icu4c/source/i18n/decimalformatpattern.cpp -text
 icu4c/source/i18n/decimalformatpattern.h -text
 icu4c/source/i18n/i18n.vcxproj -text
 icu4c/source/i18n/i18n.vcxproj.filters -text
+icu4c/source/i18n/reldatefmt.cpp -text
+icu4c/source/i18n/unicode/reldatefmt.h -text
 icu4c/source/io/io.vcxproj -text
 icu4c/source/io/io.vcxproj.filters -text
 icu4c/source/layout/layout.vcxproj -text
@@ -144,6 +149,8 @@ icu4c/source/test/cintltst/cintltst.vcxproj -text
 icu4c/source/test/cintltst/cintltst.vcxproj.filters -text
 icu4c/source/test/intltest/intltest.vcxproj -text
 icu4c/source/test/intltest/intltest.vcxproj.filters -text
+icu4c/source/test/intltest/lrucachetest.cpp -text
+icu4c/source/test/intltest/reldatefmttest.cpp -text
 icu4c/source/test/iotest/iotest.vcxproj -text
 icu4c/source/test/iotest/iotest.vcxproj.filters -text
 icu4c/source/test/letest/cletest.vcxproj -text
index ec7f23f268868bdd2629a2df4000ea9ed622b405..20381e1d139e45af8469a3c59003f5562af461fe 100644 (file)
@@ -104,7 +104,7 @@ rbbi.o rbbidata.o rbbinode.o rbbirb.o rbbiscan.o rbbisetb.o rbbistbl.o rbbitblb.
 serv.o servnotf.o servls.o servlk.o servlkf.o servrbf.o servslkf.o \
 uidna.o usprep.o uts46.o punycode.o \
 util.o util_props.o parsepos.o locbased.o cwchar.o wintz.o dtintrv.o ucnvsel.o propsvec.o \
-ulist.o uloc_tag.o icudataver.o icuplug.o listformatter.o
+ulist.o uloc_tag.o icudataver.o icuplug.o listformatter.o lrucache.o
 
 ## Header files to install
 HEADERS = $(srcdir)/unicode/*.h
index 1933b274070e55377cc78015f3026551987a64a6..43e7853838c6063f2767fd15cb05ddd04bae6c17 100644 (file)
     <ClCompile Include="locresdata.cpp" />\r
     <ClCompile Include="locutil.cpp">\r
     </ClCompile>\r
+    <ClCompile Include="lrucache.cpp" />\r
     <ClCompile Include="resbund.cpp">\r
     </ClCompile>\r
     <ClCompile Include="resbund_cnv.cpp" />\r
       <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">..\..\include\unicode\%(Filename)%(Extension);%(Outputs)</Outputs>\r
     </CustomBuild>\r
     <ClInclude Include="locutil.h" />\r
+    <ClInclude Include="lrucache.h" />\r
     <CustomBuild Include="unicode\resbund.h">\r
       <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">copy "%(FullPath)" ..\..\include\unicode\r
 </Command>\r
 </Command>\r
       <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">..\..\include\unicode\%(Filename)%(Extension);%(Outputs)</Outputs>\r
     </CustomBuild>\r
+    <ClInclude Include="sharedptr.h" />\r
     <CustomBuild Include="unicode\ucat.h">\r
       <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">copy "%(FullPath)" ..\..\include\unicode\r
 </Command>\r
index f8a5cefc840fdee42b614988fba3df4bfc7fd47b..37fe45f23c107bd11fe4b5503a7caff803f99da4 100644 (file)
     <ClCompile Include="ucol_swp.cpp">\r
       <Filter>collation</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="lrucache.cpp">\r
+      <Filter>collections</Filter>\r
+    </ClCompile>\r
     <ClCompile Include="propsvec.c">\r
       <Filter>collections</Filter>\r
     </ClCompile>\r
     <ClInclude Include="hash.h">\r
       <Filter>collections</Filter>\r
     </ClInclude>\r
+    <ClInclude Include="lrucache.h">\r
+      <Filter>collections</Filter>\r
+    </ClInclude>\r
     <ClInclude Include="propsvec.h">\r
       <Filter>collections</Filter>\r
     </ClInclude>\r
     <ClInclude Include="cmemory.h">\r
       <Filter>data &amp; memory</Filter>\r
     </ClInclude>\r
+    <ClInclude Include="sharedptr.h">\r
+      <Filter>data &amp; memory</Filter>\r
+    </ClInclude>\r
     <ClInclude Include="ucln.h">\r
       <Filter>data &amp; memory</Filter>\r
     </ClInclude>\r
diff --git a/icu4c/source/common/lrucache.cpp b/icu4c/source/common/lrucache.cpp
new file mode 100644 (file)
index 0000000..a958ea4
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+*******************************************************************************
+* Copyright (C) 2013, International Business Machines Corporation and         
+* others. All Rights Reserved.                                                
+*******************************************************************************
+*                                                                             
+* File LRUCACHE.CPP                                                             
+*******************************************************************************
+*/
+
+#include "lrucache.h"
+#include "mutex.h"
+#include "uhash.h"
+#include "cstring.h"
+
+U_NAMESPACE_BEGIN
+
+// Named CacheEntry2 to avoid conflict with CacheEntry in serv.cpp
+// Don't know how to make truly private class that the linker can't see.
+class CacheEntry2 : public UMemory {
+public:
+    CacheEntry2 *moreRecent;
+    CacheEntry2 *lessRecent;
+    char *localeId;
+    SharedPtr<UObject> cachedData;
+    UErrorCode status;  // This is the error if any from creating cachedData.
+
+    CacheEntry2();
+    ~CacheEntry2();
+
+    void unlink();
+    void uninit();
+    UBool init(const char *localeId, UObject *dataToAdopt, UErrorCode err);
+private:
+    CacheEntry2(const CacheEntry2& other);
+    CacheEntry2 &operator=(const CacheEntry2& other);
+};
+
+CacheEntry2::CacheEntry2() 
+    : moreRecent(NULL), lessRecent(NULL), localeId(NULL), cachedData(),
+      status(U_ZERO_ERROR) {
+}
+
+CacheEntry2::~CacheEntry2() {
+    uninit();
+}
+
+void CacheEntry2::unlink() {
+    if (moreRecent != NULL) {
+        moreRecent->lessRecent = lessRecent;
+    }
+    if (lessRecent != NULL) {
+        lessRecent->moreRecent = moreRecent;
+    }
+    moreRecent = NULL;
+    lessRecent = NULL;
+}
+
+void CacheEntry2::uninit() {
+    cachedData.clear();
+    status = U_ZERO_ERROR;
+    if (localeId != NULL) {
+        uprv_free(localeId);
+    }
+    localeId = NULL;
+}
+
+UBool CacheEntry2::init(const char *locId, UObject *dataToAdopt, UErrorCode err) {
+    uninit();
+    localeId = (char *) uprv_malloc(strlen(locId) + 1);
+    if (localeId == NULL) {
+        delete dataToAdopt;
+        return FALSE;
+    }
+    uprv_strcpy(localeId, locId);
+    if (!cachedData.adoptInstead(dataToAdopt)) {
+        status = U_MEMORY_ALLOCATION_ERROR;
+        return TRUE;
+    }
+    status = err;
+    return TRUE;
+}
+
+void LRUCache::moveToMostRecent(CacheEntry2 *entry) {
+    entry->unlink();
+    entry->moreRecent = mostRecentlyUsedMarker;
+    entry->lessRecent = mostRecentlyUsedMarker->lessRecent;
+    mostRecentlyUsedMarker->lessRecent->moreRecent = entry;
+    mostRecentlyUsedMarker->lessRecent = entry;
+}
+
+UObject *LRUCache::safeCreate(const char *localeId, UErrorCode &status) {
+    UObject *result = create(localeId, status);
+
+    // Safe guard to ensure that some error is reported for missing data in
+    // case subclass forgets to set status.
+    if (result == NULL && U_SUCCESS(status)) {
+        status = U_MEMORY_ALLOCATION_ERROR;
+        return NULL;
+    } 
+
+    // Safe guard to ensure that if subclass reports an error and returns
+    // data that we don't leak memory.
+    if (result != NULL && U_FAILURE(status)) {
+        delete result;
+        return NULL;
+    }
+    return result;
+}
+
+UBool LRUCache::init(const char *localeId, CacheEntry2 *entry) {
+    UErrorCode status = U_ZERO_ERROR;
+    UObject *result = safeCreate(localeId, status);
+    return entry->init(localeId, result, status);
+}
+
+UBool LRUCache::contains(const char *localeId) const {
+    return (uhash_get(localeIdToEntries, localeId) != NULL);
+}
+
+
+void LRUCache::_get(const char *localeId, SharedPtr<UObject>& ptr, UErrorCode &status) {
+    Mutex lock(mutex);
+    CacheEntry2 *entry = (CacheEntry2 *) uhash_get(localeIdToEntries, localeId);
+    if (entry != NULL) {
+        moveToMostRecent(entry);
+    } else {
+        // Its a cache miss.
+
+        if (uhash_count(localeIdToEntries) < maxSize) {
+            entry = new CacheEntry2;
+        } else {
+            entry = leastRecentlyUsedMarker->moreRecent;
+            uhash_remove(localeIdToEntries, entry->localeId);
+            entry->unlink();
+            entry->uninit();
+        }
+        // entry is an uninitialized, unlinked cache entry 
+        // or entry is null if memory could not be allocated.
+        if (entry != NULL) {
+            if (!init(localeId, entry)) {
+                delete entry;
+                entry = NULL;
+            }
+        }
+
+        // Entry is initialized, but unlinked or entry is null on
+        // memory allocation error.
+        if (entry != NULL) {
+            // Add to hashtable
+            uhash_put(localeIdToEntries, entry->localeId, entry, &status);
+            if (U_FAILURE(status)) {
+                delete entry;
+                entry = NULL;
+            }
+        }
+        if (entry != NULL) {
+            moveToMostRecent(entry);
+        }
+    }
+    if (entry == NULL) {
+        status = U_MEMORY_ALLOCATION_ERROR;
+        return;
+    }
+
+    // If we get here our data is cached.
+    if (U_FAILURE(entry->status)) {
+        status = entry->status;
+        return;
+    }
+    ptr = entry->cachedData;
+}
+
+LRUCache::LRUCache(int32_t size, UMutex *mtx, UErrorCode &status) :
+        mostRecentlyUsedMarker(NULL),
+        leastRecentlyUsedMarker(NULL),
+        localeIdToEntries(NULL),
+        maxSize(size),
+        mutex(mtx) {
+    if (U_FAILURE(status)) {
+        return;
+    }
+    mostRecentlyUsedMarker = new CacheEntry2;
+    leastRecentlyUsedMarker = new CacheEntry2;
+    if (mostRecentlyUsedMarker == NULL || leastRecentlyUsedMarker == NULL) {
+        delete mostRecentlyUsedMarker;
+        delete leastRecentlyUsedMarker;
+        mostRecentlyUsedMarker = leastRecentlyUsedMarker = NULL;
+        status = U_MEMORY_ALLOCATION_ERROR;
+        return;
+    }
+    mostRecentlyUsedMarker->moreRecent = NULL;
+    mostRecentlyUsedMarker->lessRecent = leastRecentlyUsedMarker;
+    leastRecentlyUsedMarker->moreRecent = mostRecentlyUsedMarker;
+    leastRecentlyUsedMarker->lessRecent = NULL;
+    localeIdToEntries = uhash_openSize(
+        uhash_hashChars,
+        uhash_compareChars,
+        NULL,
+        maxSize + maxSize / 5,
+        &status);
+    if (U_FAILURE(status)) {
+        return;
+    }
+}
+
+LRUCache::~LRUCache() {
+    uhash_close(localeIdToEntries);
+    for (CacheEntry2 *i = mostRecentlyUsedMarker; i != NULL;) {
+        CacheEntry2 *next = i->lessRecent;
+        delete i;
+        i = next;
+    }
+}
+
+UObject *SimpleLRUCache::create(const char *localeId, UErrorCode &status) {
+    return createFunc(localeId, status);
+}
+
+U_NAMESPACE_END
diff --git a/icu4c/source/common/lrucache.h b/icu4c/source/common/lrucache.h
new file mode 100644 (file)
index 0000000..efc86ef
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+*******************************************************************************
+* Copyright (C) 2013, International Business Machines Corporation and         
+* others. All Rights Reserved.                                                
+*******************************************************************************
+*                                                                             
+* File LRUCACHE.H                                                             
+*******************************************************************************
+*/
+
+#ifndef __LRU_CACHE_H__
+#define __LRU_CACHE_H__
+
+#include "unicode/uobject.h"
+#include "umutex.h"
+#include "sharedptr.h"
+
+struct UHashtable;
+
+U_NAMESPACE_BEGIN
+
+/**
+ * LRUCache keyed by locale ID.
+ */
+
+class CacheEntry2;
+
+class LRUCache : public UObject {
+  public:
+    template<typename T>
+    void get(const char *localeId, SharedPtr<T> &ptr, UErrorCode &status) {
+        SharedPtr<UObject> p;
+        _get(localeId, p, status);
+        if (U_FAILURE(status)) {
+            return;
+        }
+        ptr = p;
+    }
+    UBool contains(const char *localeId) const;
+    virtual ~LRUCache();
+  protected:
+    virtual UObject *create(const char *localeId, UErrorCode &status)=0;
+    LRUCache(int32_t maxSize, UMutex *mutex, UErrorCode &status);
+  private:
+    LRUCache();
+    LRUCache(const LRUCache &other);
+    LRUCache &operator=(const LRUCache &other);
+    UObject *safeCreate(const char *localeId, UErrorCode &status);
+    CacheEntry2 *mostRecentlyUsedMarker;
+    CacheEntry2 *leastRecentlyUsedMarker;
+    UHashtable *localeIdToEntries;
+    int32_t maxSize;
+    UMutex *mutex;
+
+    void moveToMostRecent(CacheEntry2 *cacheEntry);
+    UBool init(const char *localeId, CacheEntry2 *cacheEntry);
+    void _get(const char *localeId, SharedPtr<UObject> &ptr, UErrorCode &status);
+};
+
+typedef UObject *(*CreateFunc)(const char *localeId, UErrorCode &status);
+
+class SimpleLRUCache : public LRUCache {
+public:
+    SimpleLRUCache(
+        int32_t maxSize,
+        UMutex *mutex,
+        CreateFunc cf,
+        UErrorCode &status) :
+            LRUCache(maxSize, mutex, status), createFunc(cf) {
+    }
+    virtual ~SimpleLRUCache() {
+    }
+protected:
+    virtual UObject *create(const char *localeId, UErrorCode &status);
+private:
+    CreateFunc createFunc;
+};
+    
+U_NAMESPACE_END
+
+#endif
diff --git a/icu4c/source/common/sharedptr.h b/icu4c/source/common/sharedptr.h
new file mode 100644 (file)
index 0000000..1fdeecc
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+*******************************************************************************
+* Copyright (C) 2013, International Business Machines Corporation and         
+* others. All Rights Reserved.                                                
+*******************************************************************************
+*                                                                             
+* File SHAREDPTR.H                                                             
+*******************************************************************************
+*/
+
+#ifndef __SHARED_PTR_H__
+#define __SHARED_PTR_H__
+
+#include "unicode/uobject.h"
+#include "umutex.h"
+#include "uassert.h"
+
+U_NAMESPACE_BEGIN
+
+// Wrap u_atomic_int32_t in a UMemory so that we allocate them in the same
+// way we allocate all other ICU objects.
+struct _AtomicInt : public UMemory {
+    u_atomic_int32_t value;
+};
+
+/**
+ * SharedPtr are shared pointers that support copy-on-write sematics.
+ * SharedPtr makes the act of copying large objects cheap by deferring the
+ * cost of the copy to the first write operation after the copy.
+ *
+ * A SharedPtr<T> instance can refer to no object or an object of type T where
+ * T is a subclass of UObject. T must also have a clone() method that copies
+ * the object and returns a pointer to the copy. Copy and assignment of
+ * SharedPtr instances are cheap because they only involve copying or
+ * assigning the SharedPtr instance, not the T object which could be large.
+ * Although many SharedPtr<T> instances may refer to the same T object,
+ * clients can still assume that each SharedPtr<T> instance has its own
+ * private instance of T because each SharedPtr<T> instance offers only a
+ * const view of its T object through normal pointer operations. If a caller
+ * must change a T object through its SharedPtr<T>, it can do so by calling
+ * readWrite() on the SharedPtr instance. readWrite() ensures that the
+ * SharedPtr<T> really does have its own private T object by cloning it if
+ * it is shared by using its clone() method. SharedPtr<T> instances handle
+ * management by reference counting their T objects. T objects that are
+ * referenced by no SharedPtr<T> instances get deleted automatically.
+ */
+template<typename T>
+class SharedPtr {
+public:
+    /**
+     * Constructor. If there is a memory allocation error creating
+     * reference counter then this object will contain NULL, and adopted
+     * pointer will be freed. Note that when passing NULL or no argument to
+     * constructor, no memory allocation error can happen as NULL pointers
+     * are never reference counted.
+     */
+    explicit SharedPtr(T *adopted=NULL) : ptr(adopted), refPtr(NULL) {
+        if (ptr != NULL) {
+            refPtr = new _AtomicInt();
+            if (refPtr == NULL) {
+                delete ptr;
+                ptr = NULL;
+            } else {
+                umtx_storeRelease(refPtr->value, 1);
+            }
+        }
+    }
+
+    /**
+     * Non-templated copy costructor. Needed to keep compiler from
+     * creating its own.
+     */
+    SharedPtr(const SharedPtr<T> &other) :
+            ptr(other.ptr), refPtr(other.refPtr) {
+        if (refPtr != NULL) {
+            umtx_atomic_inc(&refPtr->value);
+        }
+    }
+
+    /**
+     * Templated copy constructor.
+     */
+    template<typename U>
+    SharedPtr(const SharedPtr<U> &other) :
+            ptr((T *) other.ptr), refPtr(other.refPtr) {
+        if (refPtr != NULL) {
+            umtx_atomic_inc(&refPtr->value);
+        }
+    }
+
+    /**
+     * Non-templated assignment operator. Needed to keep compiler
+     * from creating its own.
+     */
+    SharedPtr<T> &operator=(const SharedPtr<T> &other) {
+        if (ptr != other.ptr) {
+            SharedPtr<T> newValue(other);
+            swap(newValue);
+        }
+        return *this;
+    }
+
+    /**
+     * Templated assignment operator.
+     */
+    template<typename U>
+    SharedPtr<T> &operator=(const SharedPtr<U> &other) {
+        if (ptr != other.ptr) {
+            SharedPtr<T> newValue(other);
+            swap(newValue);
+        }
+        return *this;
+    }
+
+    /**
+     * Destructor.
+     */
+    ~SharedPtr() {
+        if (refPtr != NULL) {
+            if (umtx_atomic_dec(&refPtr->value) == 0) {
+                // Cast to UObject to avoid compiler warnings about incomplete
+                // type T.
+                delete (UObject *) ptr;
+                delete refPtr;
+            }
+        }
+    }
+
+    /**
+     * adoptInstead adopts a new pointer. On success, returns TRUE.
+     * On memory allocation error creating reference counter for adopted
+     * pointer, returns FALSE while leaving this instance unchanged.
+     */
+    bool adoptInstead(T *adopted) {
+        SharedPtr<T> newValue(adopted);
+        if (adopted != NULL && newValue.ptr == NULL) {
+            // We couldn't allocate ref counter.
+            return FALSE;
+        }
+        swap(newValue);
+        return TRUE;
+    }
+
+    /**
+     * clear makes this instance refer to no object.
+     */
+    void clear() {
+        adoptInstead(NULL);
+    }
+
+    /**
+     * count returns how many SharedPtr instances, including this one,
+     * refer to the T object. Used for testing. Clients need not use in
+     * practice.
+     */
+    int32_t count() const {
+        if (refPtr == NULL) {
+            return 0;
+        }
+        return umtx_loadAcquire(refPtr->value);
+    }
+
+    /**
+     * Swaps this instance with other. a.swap(b) is equivalent to the
+     * following though more efficient: temp = a; a = b; b = temp.
+     */
+    void swap(SharedPtr<T> &other) {
+        T *tempPtr = other.ptr;
+        _AtomicInt *tempRefPtr = other.refPtr;
+        other.ptr = ptr;
+        other.refPtr = refPtr;
+        ptr = tempPtr;
+        refPtr = tempRefPtr;
+    }
+
+    const T *operator->() const {
+        return ptr;
+    }
+
+    const T &operator*() const {
+        return *ptr;
+    }
+
+    bool operator==(const T *other) const {
+        return ptr == other;
+    }
+
+    bool operator!=(const T *other) const {
+        return ptr != other;
+    }
+
+    /**
+     * readOnly gives const access to this instance's T object. If this
+     * instance refers to no object, returns NULL.
+     */
+    const T *readOnly() const {
+        return ptr;
+    }
+
+    /**
+     * readWrite returns a writable pointer to its T object copying it first
+     * using its clone() method if it is shared.
+     * On memory allocation error or if this instance refers to no object,
+     * returns NULL leaving this instance unchanged.
+     */
+    T *readWrite() {
+        int32_t refCount = count();
+        if (refCount == 0 || refCount == 1) {
+            return ptr;
+        }
+        T *result = (T *) ptr->clone();
+        if (result == NULL) {
+            // Memory allocation error
+            return NULL;
+        }
+        if (!adoptInstead(result)) {
+            return NULL;
+        }
+        return ptr;
+    }
+private:
+    T *ptr;
+    _AtomicInt *refPtr;
+    // No heap allocation. Use only stack.
+    static void * U_EXPORT2 operator new(size_t size);
+    static void * U_EXPORT2 operator new[](size_t size);
+#if U_HAVE_PLACEMENT_NEW
+    static void * U_EXPORT2 operator new(size_t, void *ptr);
+#endif
+    template<typename U> friend class SharedPtr;
+};
+
+U_NAMESPACE_END
+
+#endif
index 9ce1d14783fb87ef8f49947d6f1d19c2befa3f16..d588692cededd82a3512419cc43471c72b9fce79 100644 (file)
@@ -87,7 +87,7 @@ uspoof.o uspoof_impl.o uspoof_build.o uspoof_conf.o uspoof_wsconf.o decfmtst.o s
 ztrans.o zrule.o vzone.o fphdlimp.o fpositer.o locdspnm.o \
 decNumber.o decContext.o alphaindex.o tznames.o tznames_impl.o tzgnames.o \
 tzfmt.o compactdecimalformat.o gender.o region.o scriptset.o identifier_info.o \
-uregion.o
+uregion.o reldatefmt.o
 
 ## Header files to install
 HEADERS = $(srcdir)/unicode/*.h
index 659ac1ecaae195a5f0ced2903020e8fc867674bb..dc696eb101fd18bb26e573bdd3336574c3ab56ea 100644 (file)
     <ClCompile Include="plurrule.cpp" />\r
     <ClCompile Include="rbnf.cpp" />\r
     <ClCompile Include="rbtz.cpp" />\r
+    <ClCompile Include="reldatefmt.cpp" />\r
     <ClCompile Include="reldtfmt.cpp" />\r
     <ClCompile Include="selfmt.cpp" />\r
     <ClCompile Include="simpletz.cpp" />\r
 </Command>\r
       <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\include\unicode\%(Filename)%(Extension);%(Outputs)</Outputs>\r
       <Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">copy "%(FullPath)" ..\..\include\unicode\r
+</Command>\r
+      <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">..\..\include\unicode\%(Filename)%(Extension);%(Outputs)</Outputs>\r
+    </CustomBuild>\r
+    <CustomBuild Include="unicode\reldatefmt.h">\r
+      <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">copy "%(FullPath)" ..\..\include\unicode\r
+</Command>\r
+      <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">..\..\include\unicode\%(Filename)%(Extension);%(Outputs)</Outputs>\r
+      <Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">copy "%(FullPath)" ..\..\include\unicode\r
+</Command>\r
+      <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">..\..\include\unicode\%(Filename)%(Extension);%(Outputs)</Outputs>\r
+      <Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">copy "%(FullPath)" ..\..\include\unicode\r
+</Command>\r
+      <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\include\unicode\%(Filename)%(Extension);%(Outputs)</Outputs>\r
+      <Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">copy "%(FullPath)" ..\..\include\unicode\r
 </Command>\r
       <Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">..\..\include\unicode\%(Filename)%(Extension);%(Outputs)</Outputs>\r
     </CustomBuild>\r
index e7783afad3811a6e4f951b562cd559f0539661f9..2f2cf0fc5029126f882c1d45c75e9cc1be828d09 100644 (file)
     <ClCompile Include="rbtz.cpp">\r
       <Filter>formatting</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="reldatefmt.cpp">\r
+      <Filter>formatting</Filter>\r
+    </ClCompile>\r
     <ClCompile Include="reldtfmt.cpp">\r
       <Filter>formatting</Filter>\r
     </ClCompile>\r
     <CustomBuild Include="unicode\rbtz.h">\r
       <Filter>formatting</Filter>\r
     </CustomBuild>\r
+    <ClInclude Include="unicode\reldatefmt.h">\r
+      <Filter>formatting</Filter>\r
+    </ClInclude>\r
     <CustomBuild Include="unicode\selfmt.h">\r
       <Filter>formatting</Filter>\r
     </CustomBuild>\r
diff --git a/icu4c/source/i18n/reldatefmt.cpp b/icu4c/source/i18n/reldatefmt.cpp
new file mode 100644 (file)
index 0000000..99b35a9
--- /dev/null
@@ -0,0 +1,702 @@
+/*
+*******************************************************************************
+* Copyright (C) 2013, International Business Machines Corporation and         
+* others. All Rights Reserved.                                                
+*******************************************************************************
+*                                                                             
+* File RELDATEFMT.CPP                                                             
+*******************************************************************************
+*/
+
+#include "unicode/reldatefmt.h"
+
+#include "unicode/localpointer.h"
+#include "unicode/plurrule.h"
+#include "unicode/msgfmt.h"
+#include "unicode/decimfmt.h"
+#include "unicode/numfmt.h"
+#include "lrucache.h"
+#include "uresimp.h"
+#include "unicode/ures.h"
+#include "cstring.h"
+#include "plurrule_impl.h"
+#include "ucln_in.h"
+
+#include "sharedptr.h"
+
+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
+
+U_NAMESPACE_BEGIN
+
+// other must always be first.
+static const char * const gPluralForms[] = {
+        "other", "zero", "one", "two", "few", "many", NULL};
+
+// Must be equal to number of plural forms just above.
+#define MAX_PLURAL_FORMS 6
+
+
+class QualitativeUnits : public UObject {
+public:
+    QualitativeUnits() { }
+    UnicodeString data[UDAT_ABSOLUTE_UNIT_COUNT][UDAT_DIRECTION_UNIT_COUNT];
+    virtual ~QualitativeUnits() {
+    }
+private:
+    QualitativeUnits(const QualitativeUnits &other);
+    QualitativeUnits &operator=(const QualitativeUnits& other);
+};
+
+class QuantitativeUnits : public UObject {
+public:
+    QuantitativeUnits() { }
+    UnicodeString data[UDAT_RELATIVE_UNIT_COUNT][2][MAX_PLURAL_FORMS];
+    virtual ~QuantitativeUnits() {
+    }
+private:
+    QuantitativeUnits(const QuantitativeUnits &other);
+    QuantitativeUnits &operator=(const QuantitativeUnits& other);
+};
+
+class RelativeDateTimeData : public UObject {
+public:
+    RelativeDateTimeData() {
+    }
+    SharedPtr<QualitativeUnits> qualitativeUnits;
+    SharedPtr<QuantitativeUnits> quantitativeUnits;
+    SharedPtr<MessageFormat> combinedDateAndTime;
+    SharedPtr<PluralRules> pluralRules;
+    SharedPtr<NumberFormat> numberFormat;
+    RelativeDateTimeData *clone() const {
+        return new RelativeDateTimeData(*this);
+    }
+    virtual ~RelativeDateTimeData() {
+    }
+private:
+    RelativeDateTimeData(const RelativeDateTimeData &other);
+    RelativeDateTimeData &operator=(const RelativeDateTimeData& other);
+};
+
+RelativeDateTimeData::RelativeDateTimeData(
+        const RelativeDateTimeData &other) :
+        qualitativeUnits(other.qualitativeUnits),
+        quantitativeUnits(other.quantitativeUnits),
+        combinedDateAndTime(other.combinedDateAndTime),
+        pluralRules(other.pluralRules),
+        numberFormat(other.numberFormat) {
+}
+
+static char *UnicodeString2Char(
+        const UnicodeString &source, char *buffer, int32_t bufCapacity) {
+    source.extract(0, source.length(), buffer, bufCapacity, US_INV);
+    return buffer;
+}
+
+static void 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;
+    }
+    result.setTo(TRUE, resStr, len);
+}
+
+static void getOptionalStringWithFallback(
+        const UResourceBundle *resource, 
+        const char *key,
+        UnicodeString &result,
+        UErrorCode &status) {
+    if (U_FAILURE(status)) {
+        return;
+    }
+    int32_t len = 0;
+    const UChar *resStr = ures_getStringByKey(
+        resource, key, &len, &status);
+    if (status == U_MISSING_RESOURCE_ERROR) {
+        result.remove();
+        status = U_ZERO_ERROR;
+        return;
+    }
+    if (U_FAILURE(status)) {
+        return;
+    }
+    result.setTo(TRUE, resStr, len);
+}
+
+static void 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;
+    }
+    result.setTo(TRUE, resStr, len);
+}
+
+static void getStringByIndex(
+        const UResourceBundle *resource, 
+        int32_t idx,
+        UnicodeString &result,
+        UErrorCode &status) {
+    int32_t len = 0;
+    const UChar *resStr = ures_getStringByIndex(
+            resource, idx, &len, &status);
+    if (U_FAILURE(status)) {
+        return;
+    }
+    result.setTo(TRUE, resStr, len);
+}
+
+static void addQualitativeUnit(
+            const UResourceBundle *resource,
+            UDateAbsoluteUnit absoluteUnit,
+            const UnicodeString &unitName,
+            QualitativeUnits &qualitativeUnits,
+            UErrorCode &status) {
+    getStringWithFallback(
+            resource, 
+            "-1",
+            qualitativeUnits.data[absoluteUnit][UDAT_DIRECTION_LAST],
+            status);
+    getStringWithFallback(
+            resource, 
+            "0",
+            qualitativeUnits.data[absoluteUnit][UDAT_DIRECTION_THIS],
+            status);
+    getStringWithFallback(
+            resource, 
+            "1",
+            qualitativeUnits.data[absoluteUnit][UDAT_DIRECTION_NEXT],
+            status);
+    getOptionalStringWithFallback(
+            resource,
+            "-2",
+            qualitativeUnits.data[absoluteUnit][UDAT_DIRECTION_LAST_2],
+            status);
+    getOptionalStringWithFallback(
+            resource,
+            "2",
+            qualitativeUnits.data[absoluteUnit][UDAT_DIRECTION_NEXT_2],
+            status);
+    qualitativeUnits.data[absoluteUnit][UDAT_DIRECTION_PLAIN] = unitName;
+}
+
+static int32_t getPluralIndex(const char *pluralForm) {
+    for (int32_t i = 0; gPluralForms[i] != NULL; ++i) {
+        if (uprv_strcmp(pluralForm, gPluralForms[i]) == 0) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+static void addTimeUnit(
+        const UResourceBundle *resource,
+        UDateRelativeUnit relativeUnit,
+        int32_t pastOrFuture,
+        QuantitativeUnits &quantitativeUnits,
+        UErrorCode &status) {
+    if (U_FAILURE(status)) {
+        return;
+    }
+    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;
+        }
+        int32_t pluralIndex = getPluralIndex(
+                ures_getKey(pluralBundle.getAlias()));
+        if (pluralIndex != -1) {
+            getString(
+                    pluralBundle.getAlias(),
+                    quantitativeUnits.data[relativeUnit][pastOrFuture][pluralIndex],
+                    status);
+            if (U_FAILURE(status)) {
+                return;
+            }
+        }
+    }
+}
+
+static void addTimeUnit(
+        const UResourceBundle *resource,
+        UDateRelativeUnit relativeUnit,
+        QuantitativeUnits &quantitativeUnits,
+        UErrorCode &status) {
+    LocalUResourceBundlePointer topLevel(
+            ures_getByKeyWithFallback(resource, "relativeTime", NULL, &status));
+    if (U_FAILURE(status)) {
+        return;
+    }
+    LocalUResourceBundlePointer futureBundle(ures_getByKeyWithFallback(
+            topLevel.getAlias(), "future", NULL, &status));
+    if (U_FAILURE(status)) {
+        return;
+    }
+    addTimeUnit(
+            futureBundle.getAlias(),
+            relativeUnit,
+            1,
+            quantitativeUnits,
+            status);
+    LocalUResourceBundlePointer pastBundle(ures_getByKeyWithFallback(
+            topLevel.getAlias(), "past", NULL, &status));
+    if (U_FAILURE(status)) {
+        return;
+    }
+    addTimeUnit(
+            pastBundle.getAlias(),
+            relativeUnit,
+            0,
+            quantitativeUnits,
+            status);
+}
+
+static void addTimeUnit(
+        const UResourceBundle *resource,
+        const char *path,
+        UDateRelativeUnit relativeUnit,
+        QuantitativeUnits &quantitativeUnits,
+        UErrorCode &status) {
+    LocalUResourceBundlePointer topLevel(
+            ures_getByKeyWithFallback(resource, path, NULL, &status));
+    if (U_FAILURE(status)) {
+        return;
+    }
+    addTimeUnit(topLevel.getAlias(), relativeUnit, quantitativeUnits, status);
+}
+
+static void addTimeUnit(
+        const UResourceBundle *resource,
+        const char *path,
+        UDateRelativeUnit relativeUnit,
+        UDateAbsoluteUnit absoluteUnit,
+        QuantitativeUnits &quantitativeUnits,
+        QualitativeUnits &qualitativeUnits,
+        UErrorCode &status) {
+    LocalUResourceBundlePointer topLevel(
+            ures_getByKeyWithFallback(resource, path, NULL, &status));
+    if (U_FAILURE(status)) {
+        return;
+    }
+    addTimeUnit(topLevel.getAlias(), relativeUnit, quantitativeUnits, status);
+    UnicodeString unitName;
+    getStringWithFallback(topLevel.getAlias(), "dn", unitName, status);
+    if (U_FAILURE(status)) {
+        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;
+    }
+    Locale locale(localeId);
+    if (uprv_strcmp("en", locale.getLanguage()) == 0) {
+         unitName.toLower();
+    }
+    // end hack
+    ures_getByKeyWithFallback(
+            topLevel.getAlias(), "relative", topLevel.getAlias(), &status);
+    if (U_FAILURE(status)) {
+        return;
+    }
+    addQualitativeUnit(
+            topLevel.getAlias(),
+            absoluteUnit,
+            unitName,
+            qualitativeUnits,
+            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;
+    }
+    int32_t size = ures_getSize(topLevel.getAlias());
+    if (size != 7) {
+        status = U_INTERNAL_PROGRAM_ERROR;
+        return;
+    }
+    for (int32_t i = 0; i < size; ++i) {
+        getStringByIndex(topLevel.getAlias(), i, daysOfWeek[i], status);
+        if (U_FAILURE(status)) {
+            return;
+        }
+    }
+}
+
+static void addWeekDay(
+        const UResourceBundle *resource,
+        const char *path,
+        const UnicodeString *daysOfWeek,
+        UDateAbsoluteUnit absoluteUnit,
+        QualitativeUnits &qualitativeUnits,
+        UErrorCode &status) {
+    LocalUResourceBundlePointer topLevel(
+            ures_getByKeyWithFallback(resource, path, NULL, &status));
+    if (U_FAILURE(status)) {
+        return;
+    }
+    addQualitativeUnit(
+            topLevel.getAlias(),
+            absoluteUnit,
+            daysOfWeek[absoluteUnit - UDAT_ABSOLUTE_SUNDAY],
+            qualitativeUnits,
+            status);
+}
+
+static void load(
+        const UResourceBundle *resource,
+        QualitativeUnits &qualitativeUnits,
+        QuantitativeUnits &quantitativeUnits,
+        UErrorCode &status) {
+    addTimeUnit(
+            resource,
+            "fields/day",
+            UDAT_RELATIVE_DAYS,
+            UDAT_ABSOLUTE_DAY,
+            quantitativeUnits,
+            qualitativeUnits,
+            status);
+    addTimeUnit(
+            resource,
+            "fields/week",
+            UDAT_RELATIVE_WEEKS,
+            UDAT_ABSOLUTE_WEEK,
+            quantitativeUnits,
+            qualitativeUnits,
+            status);
+    addTimeUnit(
+            resource,
+            "fields/month",
+            UDAT_RELATIVE_MONTHS,
+            UDAT_ABSOLUTE_MONTH,
+            quantitativeUnits,
+            qualitativeUnits,
+            status);
+    addTimeUnit(
+            resource,
+            "fields/year",
+            UDAT_RELATIVE_YEARS,
+            UDAT_ABSOLUTE_YEAR,
+            quantitativeUnits,
+            qualitativeUnits,
+            status);
+    addTimeUnit(
+            resource,
+            "fields/second",
+            UDAT_RELATIVE_SECONDS,
+            quantitativeUnits,
+            status);
+    addTimeUnit(
+            resource,
+            "fields/minute",
+            UDAT_RELATIVE_MINUTES,
+            quantitativeUnits,
+            status);
+    addTimeUnit(
+            resource,
+            "fields/hour",
+            UDAT_RELATIVE_HOURS,
+            quantitativeUnits,
+            status);
+    getStringWithFallback(
+            resource,
+            "fields/second/relative/0",
+            qualitativeUnits.data[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,
+            qualitativeUnits,
+            status);
+    addWeekDay(
+            resource,
+            "fields/tue/relative",
+            daysOfWeek,
+            UDAT_ABSOLUTE_TUESDAY,
+            qualitativeUnits,
+            status);
+    addWeekDay(
+            resource,
+            "fields/wed/relative",
+            daysOfWeek,
+            UDAT_ABSOLUTE_WEDNESDAY,
+            qualitativeUnits,
+            status);
+    addWeekDay(
+            resource,
+            "fields/thu/relative",
+            daysOfWeek,
+            UDAT_ABSOLUTE_THURSDAY,
+            qualitativeUnits,
+            status);
+    addWeekDay(
+            resource,
+            "fields/fri/relative",
+            daysOfWeek,
+            UDAT_ABSOLUTE_FRIDAY,
+            qualitativeUnits,
+            status);
+    addWeekDay(
+            resource,
+            "fields/sat/relative",
+            daysOfWeek,
+            UDAT_ABSOLUTE_SATURDAY,
+            qualitativeUnits,
+            status);
+    addWeekDay(
+            resource,
+            "fields/sun/relative",
+            daysOfWeek,
+            UDAT_ABSOLUTE_SUNDAY,
+            qualitativeUnits,
+            status);
+}
+
+static void getDateTimePattern(
+        const UResourceBundle *resource,
+        UnicodeString &result,
+        UErrorCode &status) {
+    UnicodeString defaultCalendarName;
+    getStringWithFallback(
+            resource,
+            "calendar/default",
+            defaultCalendarName,
+            status);
+    if (U_FAILURE(status)) {
+        return;
+    }
+    char calendarNameBuffer[128];
+    char pathBuffer[256];
+    sprintf(
+            pathBuffer,
+            "calendar/%s/DateTimePatterns",
+            UnicodeString2Char(
+                    defaultCalendarName,
+                    calendarNameBuffer,
+                    128));
+    LocalUResourceBundlePointer topLevel(
+            ures_getByKeyWithFallback(resource, pathBuffer, NULL, &status));
+    if (U_FAILURE(status)) {
+        return;
+    }
+    int32_t size = ures_getSize(topLevel.getAlias());
+    if (size < 9) {
+        // Oops, size is to small to access the index that we want, fallback
+        // to a hard-coded value.
+        result = UnicodeString("{1} {0}");
+        return;
+    }
+    getStringByIndex(topLevel.getAlias(), 8, result, status);
+}
+
+static UObject *U_CALLCONV createData(const char *localeId, UErrorCode &status) {
+    LocalUResourceBundlePointer topLevel(ures_open(NULL, localeId, &status));
+    if (U_FAILURE(status)) {
+        return NULL;
+    }
+    LocalPointer<RelativeDateTimeData> result(new RelativeDateTimeData());
+    LocalPointer<QualitativeUnits> qualitativeUnits(new QualitativeUnits());
+    LocalPointer<QuantitativeUnits> quantitativeUnits(new QuantitativeUnits());
+    if (qualitativeUnits.getAlias() == NULL || quantitativeUnits.getAlias() == NULL) {
+        status = U_MEMORY_ALLOCATION_ERROR;
+        return NULL;
+    }
+    load(topLevel.getAlias(), *qualitativeUnits, *quantitativeUnits, status);
+    if (U_FAILURE(status)) {
+        return NULL;
+    }
+    if (!result->qualitativeUnits.adoptInstead(qualitativeUnits.orphan())) {
+        status = U_MEMORY_ALLOCATION_ERROR;
+        return NULL;
+    }
+    if (!result->quantitativeUnits.adoptInstead(quantitativeUnits.orphan())) {
+        status = U_MEMORY_ALLOCATION_ERROR;
+        return NULL;
+    }
+        
+    
+    UnicodeString dateTimePattern;
+    getDateTimePattern(topLevel.getAlias(), dateTimePattern, status);
+    if (U_FAILURE(status)) {
+        return NULL;
+    }
+    LocalPointer<MessageFormat> mf(new MessageFormat(dateTimePattern, localeId, status));
+    if (U_FAILURE(status)) {
+        return NULL;
+    }
+    if (!result->combinedDateAndTime.adoptInstead(mf.orphan())) {
+        status = U_MEMORY_ALLOCATION_ERROR;
+        return NULL;
+    }
+    LocalPointer<PluralRules> pr(PluralRules::forLocale(localeId, status));
+    if (U_FAILURE(status)) {
+        return NULL;
+    }
+    if (!result->pluralRules.adoptInstead(pr.orphan())) {
+        status = U_MEMORY_ALLOCATION_ERROR;
+        return NULL;
+    }
+    LocalPointer<NumberFormat> nf(
+            NumberFormat::createInstance(localeId, status));
+    if (U_FAILURE(status)) {
+        return NULL;
+    }
+    if (!result->numberFormat.adoptInstead(nf.orphan())) {
+        status = U_MEMORY_ALLOCATION_ERROR;
+        return NULL;
+    }
+    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, &gCacheMutex, &createData, status);
+    if (U_FAILURE(status)) {
+        delete gCache;
+        gCache = NULL;
+    }
+}
+
+static void getFromCache(const char *locale, SharedPtr<RelativeDateTimeData>& ptr, UErrorCode &status) {
+    umtx_initOnce(gCacheInitOnce, &cacheInit, status);
+    if (U_FAILURE(status)) {
+        return;
+    }
+    gCache->get(locale, ptr, status);
+}
+
+RelativeDateTimeFormatter::RelativeDateTimeFormatter(UErrorCode& status) {
+  getFromCache(Locale::getDefault().getName(), ptr, status);
+}
+
+RelativeDateTimeFormatter::RelativeDateTimeFormatter(const Locale& locale, UErrorCode& status) {
+    getFromCache(locale.getName(), ptr, status);
+}
+
+
+RelativeDateTimeFormatter::RelativeDateTimeFormatter(const RelativeDateTimeFormatter& other) : ptr(other.ptr) {
+}
+
+RelativeDateTimeFormatter& RelativeDateTimeFormatter::operator=(const RelativeDateTimeFormatter& other) {
+    if (this != &other) {
+        ptr = other.ptr;
+    }
+    return *this;
+}
+
+RelativeDateTimeFormatter::~RelativeDateTimeFormatter() {
+}
+
+
+UnicodeString& RelativeDateTimeFormatter::format(
+        double quantity, UDateDirection direction, UDateRelativeUnit unit,
+        UnicodeString& appendTo, UErrorCode& status) const {
+    if (U_FAILURE(status)) {
+        return appendTo;
+    }
+    if (direction != UDAT_DIRECTION_LAST && direction != UDAT_DIRECTION_NEXT) {
+        status = U_ILLEGAL_ARGUMENT_ERROR;
+        return appendTo;
+    }
+    FixedDecimal dec(quantity);
+    const DecimalFormat *decFmt = dynamic_cast<const DecimalFormat *>(
+            ptr->numberFormat.readOnly());
+    if (decFmt != NULL) {
+        dec = decFmt->getFixedDecimal(quantity, status);
+    }
+    if (U_FAILURE(status)) {
+        return appendTo;
+    }
+    char buffer[256];
+    int32_t pluralIndex = getPluralIndex(
+            UnicodeString2Char(ptr->pluralRules->select(dec), buffer, 256));
+    if (pluralIndex == -1) {
+        pluralIndex = 0;
+    }
+    int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0;
+    const UnicodeString *pattern = &ptr->quantitativeUnits->data[unit][bFuture][pluralIndex];
+    if (pattern->isEmpty()) {
+        pattern = &ptr->quantitativeUnits->data[unit][bFuture][0];
+    }
+    if (pattern->isEmpty()) {
+        return appendTo;
+    }
+    UnicodeString result(*pattern);
+    UnicodeString formattedNumber;
+    result.findAndReplace(UnicodeString("{0}"), ptr->numberFormat->format(quantity, formattedNumber));
+    return appendTo.append(result);
+}
+
+UnicodeString& RelativeDateTimeFormatter::format(
+        UDateDirection direction, UDateAbsoluteUnit unit,
+        UnicodeString& appendTo, UErrorCode& status) const {
+    if (U_FAILURE(status)) {
+        return appendTo;
+    }
+    if (unit == UDAT_ABSOLUTE_NOW && direction != UDAT_DIRECTION_PLAIN) {
+        status = U_ILLEGAL_ARGUMENT_ERROR;
+        return appendTo;
+    }
+    return appendTo.append(ptr->qualitativeUnits->data[unit][direction]);
+}
+
+UnicodeString& RelativeDateTimeFormatter::combineDateAndTime(
+    const UnicodeString& relativeDateString, const UnicodeString& timeString,
+    UnicodeString& appendTo, UErrorCode& status) const {
+    Formattable formattable[2];
+    formattable[0].setString(timeString);
+    formattable[1].setString(relativeDateString);
+    FieldPosition fpos(0);
+    return ptr->combinedDateAndTime->format(formattable, 2, appendTo, fpos, status);
+}
+
+void RelativeDateTimeFormatter::setNumberFormat(const NumberFormat& nf) {
+    RelativeDateTimeData *wptr = ptr.readWrite();
+    NumberFormat *newNf = (NumberFormat *) nf.clone();
+    if (newNf != NULL && wptr != NULL) {
+        wptr->numberFormat.adoptInstead(newNf);
+    }
+}
+
+U_NAMESPACE_END
+
index 400b2727713845d61ff042c6cc197e52759424d1..b42761089951653b7e2dc3b33fda07186953cd83 100644 (file)
@@ -26,6 +26,7 @@ as the functions are suppose to be called.
 It's usually best to have child dependencies called first. */
 typedef enum ECleanupI18NType {
     UCLN_I18N_START = -1,
+    UCLN_I18N_RELDATEFMT,
     UCLN_I18N_IDENTIFIER_INFO,
     UCLN_I18N_SPOOF,
     UCLN_I18N_TRANSLITERATOR,
diff --git a/icu4c/source/i18n/unicode/reldatefmt.h b/icu4c/source/i18n/unicode/reldatefmt.h
new file mode 100644 (file)
index 0000000..e4c50f7
--- /dev/null
@@ -0,0 +1,380 @@
+/*
+********************************************************************************
+* Copyright (C) 2013, International Business Machines Corporation and
+* others.
+* All Rights Reserved.
+********************************************************************************
+*
+* File RELDATEFMT.H
+********************************************************************************
+*/
+
+#ifndef __RELDATEFMT_H
+#define __RELDATEFMT_H
+
+#include "unicode/utypes.h"
+#include "unicode/locid.h"
+#include "sharedptr.h"
+
+
+/**
+ * Represents the unit for formatting a relative date. e.g "in 5 days"
+ * or "in 3 months"
+ * @draft ICU 53
+ */
+typedef enum UDateRelativeUnit {
+
+    /**
+     * Seconds
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_RELATIVE_SECONDS,
+
+    /**
+     * Minutes
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_RELATIVE_MINUTES,
+
+    /**
+     * Hours
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_RELATIVE_HOURS,
+
+    /**
+     * Days
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_RELATIVE_DAYS,
+
+    /**
+     * Weeks
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_RELATIVE_WEEKS,
+
+    /**
+     * Months
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_RELATIVE_MONTHS, 
+
+    /**
+     * Years
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_RELATIVE_YEARS,
+
+    /**
+     * Count of items in this enum.
+     * @draft ICU 53
+     */
+    UDAT_RELATIVE_UNIT_COUNT
+} UDateRelativeUnit;
+
+/**
+ * Represents an absolute unit.
+ * @draft ICU 53
+ */
+typedef enum UDateAbsoluteUnit {
+
+    // Days of week have to remain together and in order from Sunday to
+    // Saturday.
+    /**
+     * Sunday
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_ABSOLUTE_SUNDAY,
+
+    /**
+     * Monday
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_ABSOLUTE_MONDAY,
+
+    /**
+     * Tuesday
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_ABSOLUTE_TUESDAY,
+
+    /**
+     * Wednesday
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_ABSOLUTE_WEDNESDAY,
+
+    /**
+     * Thursday
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_ABSOLUTE_THURSDAY,
+
+    /**
+     * Friday
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_ABSOLUTE_FRIDAY,
+
+    /**
+     * Saturday
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_ABSOLUTE_SATURDAY,
+
+    /**
+     * Day
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_ABSOLUTE_DAY,
+
+    /**
+     * Week
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_ABSOLUTE_WEEK,
+
+    /**
+     * Month
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_ABSOLUTE_MONTH,
+
+    /**
+     * Year
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_ABSOLUTE_YEAR,
+
+    /**
+     * Now
+     * @draft ICU 53
+     */
+    UDAT_ABSOLUTE_NOW,
+
+    /**
+     * Count of items in this enum.
+     * @draft ICU 53
+     */
+    UDAT_ABSOLUTE_UNIT_COUNT
+} UDateAbsoluteUnit;
+
+/**
+ * Represents a direction for an absolute unit e.g "Next Tuesday"
+ * or "Last Tuesday"
+ * @draft ICU 53
+ */
+typedef enum UDateDirection {
+
+    /**
+     * Two before. Not fully supported in every locale.
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_DIRECTION_LAST_2,
+
+    /**
+     * Last
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_DIRECTION_LAST,
+
+    /**
+     * This
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_DIRECTION_THIS,
+
+    /**
+     * Next
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_DIRECTION_NEXT,
+
+    /**
+     * Two after. Not fully supported in every locale.
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_DIRECTION_NEXT_2,
+
+    /**
+     * Plain, which means the absence of a qualifier.
+ Â Â Â Â * @draft ICU 53
+ Â Â Â Â */
+    UDAT_DIRECTION_PLAIN,
+
+    /**
+     * Count of items in this enum.
+     * @draft ICU 53
+     */
+    UDAT_DIRECTION_UNIT_COUNT
+} UDateDirection;
+
+
+U_NAMESPACE_BEGIN 
+
+class RelativeDateTimeData;
+class NumberFormat;
+
+/**
+ * Formats simple relative dates. There are two types of relative dates that
+ * it handles:
+ * <ul>
+ *   <li>relative dates with a quantity e.g "in 5 days"</li>
+ *   <li>relative dates without a quantity e.g "next Tuesday"</li>
+ * </ul>
+ * <p>
+ * This API is very basic and is intended to be a building block for more
+ * fancy APIs. The caller tells it exactly what to display in a locale
+ * independent way. While this class automatically provides the correct plural
+ * forms, the grammatical form is otherwise as neutral as possible. It is the
+ * caller's responsibility to handle cut-off logic such as deciding between
+ * displaying "in 7 days" or "in 1 week." This API supports relative dates
+ * involving one single unit. This API does not support relative dates
+ * involving compound units.
+ * e.g "in 5 days and 4 hours" nor does it support parsing.
+ * This class is NOT thread-safe.
+ * <p>
+ * Here are some examples of use:
+ * <blockquote>
+ * <pre>
+ * UErrorCode status = U_ZERO_ERROR;
+ * UnicodeString appendTo;
+ * RelativeDateTimeFormatter fmt(status);
+ * // Appends "in 1 day"
+ * fmt.format(
+ *     1, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_DAYS, appendTo, status);
+ * // Appends "in 3 days"
+ * fmt.format(
+ *     3, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_DAYS, appendTo, status);
+ * // Appends "3.2 years ago"
+ * fmt.format(
+ *     3.2, UDAT_DIRECTION_LAST, UDAT_RELATIVE_YEARS, appendTo, status);
+ * // Appends "last Sunday"
+ * fmt.format(UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_SUNDAY, appendTo, status);
+ * // Appends "this Sunday"
+ * fmt.format(UDAT_DIRECTION_THIS, UDAT_ABSOLUTE_SUNDAY, appendTo, status);
+ * // Appends "next Sunday"
+ * fmt.format(UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_SUNDAY, appendTo, status);
+ * // Appends "Sunday"
+ * fmt.format(UDAT_DIRECTION_PLAIN, UDAT_ABSOLUTE_SUNDAY, appendTo, status);
+ * 
+ * // Appends "yesterday"
+ * fmt.format(UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_DAY, appendTo, status);
+ * // Appends "today"
+ * fmt.format(UDAT_DIRECTION_THIS, UDAT_ABSOLUTE_DAY, appendTo, status);
+ * // Appends "tomorrow"
+ * fmt.format(UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_DAY, appendTo, status);
+ * // Appends "now"
+ * fmt.format(UDAT_DIRECTION_PLAIN, UDAT_ABSOLUTE_NOW, appendTo, status);
+ *
+ * </pre>
+ * </blockquote>
+ * <p>
+ * In the future, we may add more forms, such as abbreviated/short forms
+ * (3 secs ago), and relative day periods ("yesterday afternoon"), etc.
+ *
+ * The RelativeDateTimeFormatter class is not intended for public subclassing.
+ * 
+ * @draft ICU 53
+ */
+
+
+class U_I18N_API RelativeDateTimeFormatter : public UObject {
+public:
+
+    /**
+     * Create RelativeDateTimeFormatter with default locale.
+     * @draft ICU 53
+     */
+    RelativeDateTimeFormatter(UErrorCode& status);
+
+
+    /**
+     * Create RelativeDateTimeFormatter with given locale.
+     * @draft ICU 53
+     */
+    RelativeDateTimeFormatter(const Locale& locale, UErrorCode& status);
+
+    /**
+     * Copy constructor.
+     * @draft ICU 53
+     */
+    RelativeDateTimeFormatter(const RelativeDateTimeFormatter& other);
+
+    /**
+     * Assignment operator.
+     * @draft ICU 53
+     */
+    RelativeDateTimeFormatter& operator=(const RelativeDateTimeFormatter& other);
+
+    /**
+     * Destructor.
+     * @draft ICU 53
+     */
+    virtual ~RelativeDateTimeFormatter();
+
+    /**
+     * Formats a relative date with a quantity such as "in 5 days" or
+     * "3 months ago"
+     * @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 appendTo The string to which the formatted result will be
+     *  appended
+     * @param status ICU error code returned here.
+     * @return appendTo
+     * @draft ICU 53
+     */
+    UnicodeString& format(double quantity, UDateDirection direction, UDateRelativeUnit unit, UnicodeString& appendTo, UErrorCode& status) const;
+
+    /**
+     * Formats a relative date without a quantity.
+     * @param direction NEXT, LAST, THIS, etc.
+     * @param unit e.g SATURDAY, DAY, MONTH
+     * @param appendTo The string to which the formatted result will be
+     *  appended. If the value of direction is documented as not being fully
+     *  supported in all locales then this method leaves appendTo unchanged if
+     *  no format string is available.
+     * @param status ICU error code returned here.
+     * @return appendTo
+     * @draft ICU 53
+     */
+    UnicodeString& format(UDateDirection direction, UDateAbsoluteUnit unit, UnicodeString& appendTo, 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.
+     *
+     * @param relativeDateString the relative date, e.g 'yesterday'
+     * @param timeString the time e.g '3:45'
+     * @param appendTo concatenated date and time appended here
+     * @param status ICU error code returned here.
+     * @return appendTo
+     * @draft ICU 53
+     */
+    UnicodeString& combineDateAndTime(
+        const UnicodeString& relativeDateString, const UnicodeString& timeString,
+        UnicodeString& appendTo, UErrorCode& status) const;
+
+    /**
+     * Specify which NumberFormat object this object should use for
+     * formatting numbers. By default this object uses the default
+     * NumberFormat object for this object's locale.
+     * @param nf the NumberFormat object to use.
+     * @see #format(double, Direction, RelativeUnit)
+     * @draft ICU 53
+     */
+    void setNumberFormat(const NumberFormat& nf);
+private:
+    RelativeDateTimeFormatter();
+    SharedPtr<icu::RelativeDateTimeData> ptr;
+};
+
+U_NAMESPACE_END
+
+#endif
index b523efee2443634c2a9826c90bbdc58fbc00df3c..bc4e19108d0cd4b4b29fa4f7b199b618ce2d7d44 100644 (file)
@@ -55,7 +55,8 @@ itrbnf.o itrbnfrt.o itrbnfp.o ucaconf.o icusvtst.o \
 uobjtest.o idnaref.o idnaconf.o nptrans.o punyref.o testidn.o testidna.o uts46test.o \
 incaltst.o calcasts.o v32test.o uvectest.o textfile.o tokiter.o utxttest.o \
 windttst.o winnmtst.o winutil.o csdetest.o tzrulets.o tzoffloc.o tzfmttst.o ssearch.o dtifmtts.o \
-tufmtts.o itspoof.o simplethread.o bidiconf.o locnmtst.o dcfmtest.o alphaindextst.o listformattertest.o genderinfotest.o compactdecimalformattest.o regiontst.o
+tufmtts.o itspoof.o simplethread.o bidiconf.o locnmtst.o dcfmtest.o alphaindextst.o listformattertest.o genderinfotest.o compactdecimalformattest.o regiontst.o \
+reldatefmttest.o lrucachetest.o
 
 DEPS = $(OBJECTS:.o=.d)
 
index 8a6698ed97047f0ca0a77a997eae6695d9ca8e95..d87db6e18757f43fd0510658793c6e3cb2f98c5a 100644 (file)
     <ClCompile Include="itrbnfp.cpp" />\r
     <ClCompile Include="itrbnfrt.cpp" />\r
     <ClCompile Include="locnmtst.cpp" />\r
+    <ClCompile Include="lrucachetest.cpp" />\r
     <ClCompile Include="miscdtfm.cpp" />\r
     <ClCompile Include="msfmrgts.cpp" />\r
     <ClCompile Include="nmfmapts.cpp" />\r
     <ClCompile Include="plurfmts.cpp" />\r
     <ClCompile Include="plurults.cpp" />\r
     <ClCompile Include="pptest.cpp" />\r
+    <ClCompile Include="reldatefmttest.cpp" />\r
     <ClCompile Include="sdtfmtts.cpp" />\r
     <ClCompile Include="selfmts.cpp" />\r
     <ClCompile Include="tchcfmt.cpp" />\r
index aa9c7e79b9f61ca68bcf0b2dd7f26a80c1279124..b7bf661707ae6641d65913317aed37fa8f5ea827 100644 (file)
     <ClCompile Include="ucaconf.cpp">\r
       <Filter>collation</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="lrucachetest.cpp">\r
+      <Filter>collections</Filter>\r
+    </ClCompile>\r
     <ClCompile Include="uvectest.cpp">\r
       <Filter>collections</Filter>\r
     </ClCompile>\r
     <ClCompile Include="pptest.cpp">\r
       <Filter>formatting</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="reldatefmttest.cpp">\r
+      <Filter>formatting</Filter>\r
+    </ClCompile>\r
     <ClCompile Include="sdtfmtts.cpp">\r
       <Filter>formatting</Filter>\r
     </ClCompile>\r
       <Filter>formatting</Filter>\r
     </ClInclude>\r
   </ItemGroup>\r
-</Project>
\ No newline at end of file
+</Project>\r
index d1609e7c099fa8a2bfafd3a6511a1ee51514f786..84f25dea08b6b39a3ebca951ef301bae6349bea0 100644 (file)
@@ -61,6 +61,7 @@
 
 extern IntlTest *createCompactDecimalFormatTest();
 extern IntlTest *createGenderInfoTest();
+extern IntlTest *createRelativeDateTimeFormatterTest();
 
 #define TESTCLASS(id, TestClass)          \
     case id:                              \
@@ -157,6 +158,15 @@ void IntlTestFormat::runIndexedTest( int32_t index, UBool exec, const char* &nam
           }
           break;
         TESTCLASS(45,RegionTest);
+        case 46:
+          name = "RelativeDateTimeFormatterTest";
+          if (exec) {
+            logln("RelativeDateTimeFormatterTest test---");
+            logln((UnicodeString)"");
+            LocalPointer<IntlTest> test(createRelativeDateTimeFormatterTest());
+            callTest(*test, par);
+          }
+          break;
         default: name = ""; break; //needed to end loop
     }
     if (exec) {
index 2f726101ce86bc5067c6bc53e8e4ed934c7f6026..537ea097684ec6d89fc9ff65b4c8625dce3e0468 100644 (file)
@@ -1,6 +1,6 @@
 /********************************************************************
  * COPYRIGHT: 
- * Copyright (c) 1997-2012, International Business Machines Corporation and
+ * Copyright (c) 1997-2013, International Business Machines Corporation and
  * others. All Rights Reserved.
  ********************************************************************/
 
@@ -33,6 +33,7 @@ extern IntlTest *createBytesTrieTest();
 static IntlTest *createLocalPointerTest();
 extern IntlTest *createUCharsTrieTest();
 static IntlTest *createEnumSetTest();
+extern IntlTest *createLRUCacheTest();
 
 #define CASE(id, test) case id:                               \
                           name = #test;                       \
@@ -95,6 +96,14 @@ void IntlTestUtilities::runIndexedTest( int32_t index, UBool exec, const char* &
                 callTest(*test, par);
             }
             break;
+        case 20:
+            name = "LRUCacheTest";
+            if (exec) {
+                logln("TestSuite LRUCacheTest---"); logln();
+                LocalPointer<IntlTest> test(createLRUCacheTest());
+                callTest(*test, par);
+            }
+            break;
         default: name = ""; break; //needed to end loop
     }
 }
diff --git a/icu4c/source/test/intltest/lrucachetest.cpp b/icu4c/source/test/intltest/lrucachetest.cpp
new file mode 100644 (file)
index 0000000..8125a4c
--- /dev/null
@@ -0,0 +1,274 @@
+/*
+*******************************************************************************
+* Copyright (C) 2013, International Business Machines Corporation and         *
+* others. All Rights Reserved.                                                *
+*******************************************************************************
+*
+* File LRUCACHETEST.CPP
+*
+********************************************************************************
+*/
+#include "cstring.h"
+#include "intltest.h"
+#include "lrucache.h"
+#include "umutex.h"
+
+static UMutex gMutex = U_MUTEX_INITIALIZER;
+
+class CopyOnWriteForTesting : public UObject {
+public:
+    CopyOnWriteForTesting() : localeNamePtr(), formatStrPtr(), length(0) {
+    }
+    UObject *clone() const {
+        return new CopyOnWriteForTesting(*this);
+    }
+    virtual ~CopyOnWriteForTesting() {
+    }
+    SharedPtr<UnicodeString> localeNamePtr;
+    SharedPtr<UnicodeString> formatStrPtr;
+    int32_t length;
+private:
+    CopyOnWriteForTesting(const CopyOnWriteForTesting &other) :
+        localeNamePtr(other.localeNamePtr),
+        formatStrPtr(other.formatStrPtr),
+        length(other.length) {
+    }
+    CopyOnWriteForTesting &operator=(const CopyOnWriteForTesting &rhs);
+};
+
+class LRUCacheForTesting : public LRUCache {
+public:
+    LRUCacheForTesting(
+        int32_t maxSize, UMutex *mutex,
+        const UnicodeString &dfs, UErrorCode &status);
+    virtual ~LRUCacheForTesting() {
+    }
+protected:
+    virtual UObject *create(const char *localeId, UErrorCode &status);
+private:
+    SharedPtr<UnicodeString> defaultFormatStr;
+};
+
+LRUCacheForTesting::LRUCacheForTesting(
+        int32_t maxSize, UMutex *mutex,
+        const UnicodeString &dfs, UErrorCode &status) :
+    LRUCache(maxSize, mutex, status), defaultFormatStr() {
+    if (U_FAILURE(status)) {
+        return;
+    }
+    defaultFormatStr.adoptInstead(new UnicodeString(dfs));
+}
+
+UObject *LRUCacheForTesting::create(const char *localeId, UErrorCode &status) {
+    if (uprv_strcmp(localeId, "error") == 0) {
+        status = U_ILLEGAL_ARGUMENT_ERROR;
+        return NULL;
+    }
+    CopyOnWriteForTesting *result = new CopyOnWriteForTesting;
+    result->localeNamePtr.adoptInstead(new UnicodeString(localeId));
+    result->formatStrPtr = defaultFormatStr;
+    result->length = 5;
+    return result;
+}
+
+class LRUCacheTest : public IntlTest {
+public:
+    LRUCacheTest() {
+    }
+    void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par=0);
+private:
+    void TestSharedPointer();
+    void TestErrorCallingConstructor();
+    void TestLRUCache();
+    void TestLRUCacheError();
+    void verifySharedPointer(
+            const SharedPtr<CopyOnWriteForTesting>& ptr,
+            const UnicodeString& name,
+            const UnicodeString& format);
+    void verifyString(
+            const UnicodeString &expected, const UnicodeString &actual);
+    void verifyPtr(const void *expected, const void *actual);
+    void verifyReferences(
+            const SharedPtr<CopyOnWriteForTesting>& ptr,
+            int32_t count, int32_t nameCount, int32_t formatCount);
+};
+
+void LRUCacheTest::runIndexedTest(int32_t index, UBool exec, const char* &name, char* /*par*/) {
+  TESTCASE_AUTO_BEGIN;
+  TESTCASE_AUTO(TestSharedPointer);
+  TESTCASE_AUTO(TestErrorCallingConstructor);
+  TESTCASE_AUTO(TestLRUCache);
+  TESTCASE_AUTO(TestLRUCacheError);
+  TESTCASE_AUTO_END;
+}
+
+void LRUCacheTest::TestSharedPointer() {
+    UErrorCode status = U_ZERO_ERROR;
+    LRUCacheForTesting cache(3, &gMutex, "little", status);
+    SharedPtr<CopyOnWriteForTesting> ptr;
+    cache.get("boo", ptr, status);
+    verifySharedPointer(ptr, "boo", "little");
+    SharedPtr<CopyOnWriteForTesting> ptrCopy = ptr;
+    verifyPtr(ptr.readOnly(), ptrCopy.readOnly());
+    {
+        SharedPtr<CopyOnWriteForTesting> ptrCopy2(ptrCopy);
+        verifyReferences(ptr, 4, 1, 2);
+    }
+    
+    // Test identity assignment
+    ptr = ptr;
+
+    verifyReferences(ptr, 3, 1, 2);
+    verifyReferences(ptrCopy, 3, 1, 2);
+    *ptrCopy.readWrite()->localeNamePtr.readWrite() = UnicodeString("hi there");
+    *ptrCopy.readWrite()->formatStrPtr.readWrite() = UnicodeString("see you");
+    verifyReferences(ptr, 2, 1, 2);
+    verifyReferences(ptrCopy, 1, 1, 1);
+    verifySharedPointer(ptr, "boo", "little");
+    verifySharedPointer(ptrCopy, "hi there", "see you");
+}
+
+void LRUCacheTest::TestErrorCallingConstructor() {
+    UErrorCode status = U_MEMORY_ALLOCATION_ERROR;
+    LRUCacheForTesting cache(3, &gMutex, "little", status);
+} 
+
+void LRUCacheTest::TestLRUCache() {
+    UErrorCode status = U_ZERO_ERROR;
+    LRUCacheForTesting cache(3, &gMutex, "little", status);
+    SharedPtr<CopyOnWriteForTesting> ptr1;
+    SharedPtr<CopyOnWriteForTesting> ptr2;
+    SharedPtr<CopyOnWriteForTesting> ptr3;
+    SharedPtr<CopyOnWriteForTesting> ptr4;
+    SharedPtr<CopyOnWriteForTesting> ptr5;
+    cache.get("foo", ptr1, status);
+    cache.get("bar", ptr2, status);
+    cache.get("baz", ptr3, status);
+    verifySharedPointer(ptr1, "foo", "little");
+    verifySharedPointer(ptr2, "bar", "little");
+    verifySharedPointer(ptr3, "baz", "little");
+
+    // Cache holds a reference to returned data which explains the 2s
+    // Note the '4'. each cached data has a reference to "little" and the
+    // cache itself also has a reference to "little"
+    verifyReferences(ptr1, 2, 1, 4);
+    verifyReferences(ptr2, 2, 1, 4);
+    verifyReferences(ptr3, 2, 1, 4);
+    
+    // (Most recent) "baz", "bar", "foo" (Least Recent) 
+    // Cache is now full but thanks to shared pointers we can still evict.
+    cache.get("full", ptr4, status);
+    verifySharedPointer(ptr4, "full", "little");
+
+    verifyReferences(ptr4, 2, 1, 5);
+
+    // (Most Recent) "full" "baz", "bar" (Least Recent)
+    cache.get("baz", ptr5, status);
+    verifySharedPointer(ptr5, "baz", "little");
+    // ptr5, ptr3, and cache have baz data
+    verifyReferences(ptr5, 3, 1, 5);
+
+    // This should delete foo data since it got evicted from cache.
+    ptr1.clear();
+    // Reference count for little drops to 4 because foo data was deleted.
+    verifyReferences(ptr5, 3, 1, 4);
+
+    // (Most Recent) "baz" "full" "bar" (Least Recent)
+    cache.get("baz", ptr5, status);
+    verifySharedPointer(ptr5, "baz", "little");
+    verifyReferences(ptr5, 3, 1, 4);
+
+    // (Most Recent) "baz", "full", "bar" (Least Recent)
+    // ptr3, ptr5 -> "baz" ptr4 -> "full" ptr2 -> "bar"
+    if (!cache.contains("baz") || !cache.contains("full") || !cache.contains("bar") || cache.contains("foo")) {
+        errln("Unexpected keys in cache.");
+    }
+    cache.get("new1", ptr5, status);
+    verifySharedPointer(ptr5, "new1", "little");
+    verifyReferences(ptr5, 2, 1, 5);
+
+    // Since bar was evicted, clearing its pointer should delete its data.
+    // Notice that the reference count to 'little' dropped from 5 to 4.
+    ptr2.clear();
+    verifyReferences(ptr5, 2, 1, 4);
+    if (cache.contains("bar") || !cache.contains("full")) {
+        errln("Unexpected 'bar' in cache.");
+    }
+
+    // (Most Recent) "new1", "baz", "full" (Least Recent)
+    // ptr3 -> "baz" ptr4 -> "full" ptr5 -> "new1"
+    cache.get("new2", ptr5, status);
+    verifySharedPointer(ptr5, "new2", "little");
+    verifyReferences(ptr5, 2, 1, 5);
+
+    // since "full" was evicted, clearing its pointer should delete its data.
+    ptr4.clear();
+    verifyReferences(ptr5, 2, 1, 4);
+    if (cache.contains("full") || !cache.contains("baz")) {
+        errln("Unexpected 'full' in cache.");
+    }
+
+    // (Most Recent) "new2", "new1", "baz" (Least Recent)
+    // ptr3 -> "baz" ptr5 -> "new2"
+    cache.get("new3", ptr5, status);
+    verifySharedPointer(ptr5, "new3", "little");
+    verifyReferences(ptr5, 2, 1, 5);
+
+    // since "baz" was evicted, clearing its pointer should delete its data.
+    ptr3.clear();
+    verifyReferences(ptr5, 2, 1, 4);
+    if (cache.contains("baz") || !cache.contains("new3")) {
+        errln("Unexpected 'baz' in cache.");
+    }
+}
+
+void LRUCacheTest::TestLRUCacheError() {
+    UErrorCode status = U_ZERO_ERROR;
+    LRUCacheForTesting cache(3, &gMutex, "little", status);
+    SharedPtr<CopyOnWriteForTesting> ptr1;
+    cache.get("error", ptr1, status);
+    if (status != U_ILLEGAL_ARGUMENT_ERROR) {
+        errln("Expected an error.");
+    }
+}
+
+void LRUCacheTest::verifySharedPointer(
+        const SharedPtr<CopyOnWriteForTesting>& ptr,
+        const UnicodeString& name,
+        const UnicodeString& format) {
+    const UnicodeString *strPtr = ptr->localeNamePtr.readOnly();
+    verifyString(name, *strPtr);
+    strPtr = ptr->formatStrPtr.readOnly();
+    verifyString(format, *strPtr);
+}
+
+void LRUCacheTest::verifyString(const UnicodeString &expected, const UnicodeString &actual) {
+    if (expected != actual) {
+        errln(UnicodeString("Expected '") + expected + "', got '"+ actual+"'");
+    }
+}
+
+void LRUCacheTest::verifyPtr(const void *expected, const void *actual) {
+    if (expected != actual) {
+       errln("Pointer mismatch.");
+    }
+}
+
+void LRUCacheTest::verifyReferences(const SharedPtr<CopyOnWriteForTesting>& ptr, int32_t count, int32_t nameCount, int32_t formatCount) {
+    int32_t actual = ptr.count();
+    if (count != actual) {
+        errln("Main reference count wrong: Expected %d, got %d", count, actual);
+    }
+    actual = ptr->localeNamePtr.count();
+    if (nameCount != actual) {
+        errln("name reference count wrong: Expected %d, got %d", nameCount, actual);
+    }
+    actual = ptr->formatStrPtr.count();
+    if (formatCount != actual) {
+        errln("format reference count wrong: Expected %d, got %d", formatCount, actual);
+    }
+}
+
+extern IntlTest *createLRUCacheTest() {
+    return new LRUCacheTest();
+}
diff --git a/icu4c/source/test/intltest/reldatefmttest.cpp b/icu4c/source/test/intltest/reldatefmttest.cpp
new file mode 100644 (file)
index 0000000..39ae56b
--- /dev/null
@@ -0,0 +1,549 @@
+/*
+*******************************************************************************
+* Copyright (C) 2013, International Business Machines Corporation and         *
+* others. All Rights Reserved.                                                *
+*******************************************************************************
+*
+* File RELDATEFMTTEST.CPP
+*
+*******************************************************************************
+*/
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "intltest.h"
+
+#if !UCONFIG_NO_FORMATTING
+
+#include "unicode/localpointer.h"
+#include "unicode/numfmt.h"
+#include "unicode/reldatefmt.h"
+
+#define LENGTHOF(array) (int32_t)(sizeof(array) / sizeof((array)[0]))
+
+static const char *DirectionStr(UDateDirection direction);
+static const char *RelativeUnitStr(UDateRelativeUnit unit);
+static const char *AbsoluteUnitStr(UDateAbsoluteUnit unit);
+
+typedef struct WithQuantityExpected {
+    double value;
+    UDateDirection direction;
+    UDateRelativeUnit unit;
+    const char *expected;
+} WithQuantityExpected;
+
+typedef struct WithoutQuantityExpected {
+    UDateDirection direction;
+    UDateAbsoluteUnit unit;
+    const char *expected;
+} WithoutQuantityExpected;
+
+static WithQuantityExpected kEnglish[] = {
+        {0.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_SECONDS, "in 0 seconds"},
+        {0.5, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_SECONDS, "in 0.5 seconds"},
+        {1.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_SECONDS, "in 1 second"},
+        {2.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_SECONDS, "in 2 seconds"},
+        {0.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MINUTES, "in 0 minutes"},
+        {0.5, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MINUTES, "in 0.5 minutes"},
+        {1.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MINUTES, "in 1 minute"},
+        {2.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MINUTES, "in 2 minutes"},
+        {0.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_HOURS, "in 0 hours"},
+        {0.5, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_HOURS, "in 0.5 hours"},
+        {1.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_HOURS, "in 1 hour"},
+        {2.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_HOURS, "in 2 hours"},
+        {0.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_DAYS, "in 0 days"},
+        {0.5, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_DAYS, "in 0.5 days"},
+        {1.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_DAYS, "in 1 day"},
+        {2.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_DAYS, "in 2 days"},
+        {0.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_WEEKS, "in 0 weeks"},
+        {0.5, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_WEEKS, "in 0.5 weeks"},
+        {1.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_WEEKS, "in 1 week"},
+        {2.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_WEEKS, "in 2 weeks"},
+        {0.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MONTHS, "in 0 months"},
+        {0.5, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MONTHS, "in 0.5 months"},
+        {1.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MONTHS, "in 1 month"},
+        {2.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MONTHS, "in 2 months"},
+        {0.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_YEARS, "in 0 years"},
+        {0.5, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_YEARS, "in 0.5 years"},
+        {1.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_YEARS, "in 1 year"},
+        {2.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_YEARS, "in 2 years"},
+                
+        {0.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_SECONDS, "0 seconds ago"},
+        {0.5, UDAT_DIRECTION_LAST, UDAT_RELATIVE_SECONDS, "0.5 seconds ago"},
+        {1.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_SECONDS, "1 second ago"},
+        {2.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_SECONDS, "2 seconds ago"},
+        {0.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_MINUTES, "0 minutes ago"},
+        {0.5, UDAT_DIRECTION_LAST, UDAT_RELATIVE_MINUTES, "0.5 minutes ago"},
+        {1.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_MINUTES, "1 minute ago"},
+        {2.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_MINUTES, "2 minutes ago"},
+        {0.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_HOURS, "0 hours ago"},
+        {0.5, UDAT_DIRECTION_LAST, UDAT_RELATIVE_HOURS, "0.5 hours ago"},
+        {1.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_HOURS, "1 hour ago"},
+        {2.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_HOURS, "2 hours ago"},
+        {0.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_DAYS, "0 days ago"},
+        {0.5, UDAT_DIRECTION_LAST, UDAT_RELATIVE_DAYS, "0.5 days ago"},
+        {1.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_DAYS, "1 day ago"},
+        {2.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_DAYS, "2 days ago"},
+        {0.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_WEEKS, "0 weeks ago"},
+        {0.5, UDAT_DIRECTION_LAST, UDAT_RELATIVE_WEEKS, "0.5 weeks ago"},
+        {1.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_WEEKS, "1 week ago"},
+        {2.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_WEEKS, "2 weeks ago"},
+        {0.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_MONTHS, "0 months ago"},
+        {0.5, UDAT_DIRECTION_LAST, UDAT_RELATIVE_MONTHS, "0.5 months ago"},
+        {1.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_MONTHS, "1 month ago"},
+        {2.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_MONTHS, "2 months ago"},
+        {0.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_YEARS, "0 years ago"},
+        {0.5, UDAT_DIRECTION_LAST, UDAT_RELATIVE_YEARS, "0.5 years ago"},
+        {1.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_YEARS, "1 year ago"},
+        {2.0, UDAT_DIRECTION_LAST, UDAT_RELATIVE_YEARS, "2 years ago"} 
+};
+
+static WithQuantityExpected kEnglishDecimal[] = {
+        {0.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_SECONDS, "in 0.0 seconds"},
+        {0.5, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_SECONDS, "in 0.5 seconds"},
+        {1.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_SECONDS, "in 1.0 seconds"},
+        {2.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_SECONDS, "in 2.0 seconds"}
+};
+
+static WithQuantityExpected kSerbian[] = {
+        {0.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MONTHS, "\\u0437\\u0430 0 \\u043c\\u0435\\u0441\\u0435\\u0446\\u0438"},
+        {1.2, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MONTHS, "\\u0437\\u0430 1,2 \\u043c\\u0435\\u0441\\u0435\\u0446\\u0430"},
+        {21.0, UDAT_DIRECTION_NEXT, UDAT_RELATIVE_MONTHS, "\\u0437\\u0430 21 \\u043c\\u0435\\u0441\\u0435\\u0446"}
+};
+
+static WithoutQuantityExpected kEnglishNoQuantity[] = {
+        {UDAT_DIRECTION_NEXT_2, UDAT_ABSOLUTE_DAY, ""},
+                
+        {UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_DAY, "tomorrow"},
+        {UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_WEEK, "next week"},
+        {UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_MONTH, "next month"},
+        {UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_YEAR, "next year"},
+        {UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_MONDAY, "next Monday"},
+        {UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_TUESDAY, "next Tuesday"},
+        {UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_WEDNESDAY, "next Wednesday"},
+        {UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_THURSDAY, "next Thursday"},
+        {UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_FRIDAY, "next Friday"},
+        {UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_SATURDAY, "next Saturday"},
+        {UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_SUNDAY, "next Sunday"},
+        
+        {UDAT_DIRECTION_LAST_2, UDAT_ABSOLUTE_DAY, ""},
+        
+        {UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_DAY, "yesterday"},
+        {UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_WEEK, "last week"},
+        {UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_MONTH, "last month"},
+        {UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_YEAR, "last year"},
+        {UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_MONDAY, "last Monday"},
+        {UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_TUESDAY, "last Tuesday"},
+        {UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_WEDNESDAY, "last Wednesday"},
+        {UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_THURSDAY, "last Thursday"},
+        {UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_FRIDAY, "last Friday"},
+        {UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_SATURDAY, "last Saturday"},
+        {UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_SUNDAY, "last Sunday"},
+         
+        {UDAT_DIRECTION_THIS, UDAT_ABSOLUTE_DAY, "today"},
+        {UDAT_DIRECTION_THIS, UDAT_ABSOLUTE_WEEK, "this week"},
+        {UDAT_DIRECTION_THIS, UDAT_ABSOLUTE_MONTH, "this month"},
+        {UDAT_DIRECTION_THIS, UDAT_ABSOLUTE_YEAR, "this year"},
+        {UDAT_DIRECTION_THIS, UDAT_ABSOLUTE_MONDAY, "this Monday"},
+        {UDAT_DIRECTION_THIS, UDAT_ABSOLUTE_TUESDAY, "this Tuesday"},
+        {UDAT_DIRECTION_THIS, UDAT_ABSOLUTE_WEDNESDAY, "this Wednesday"},
+        {UDAT_DIRECTION_THIS, UDAT_ABSOLUTE_THURSDAY, "this Thursday"},
+        {UDAT_DIRECTION_THIS, UDAT_ABSOLUTE_FRIDAY, "this Friday"},
+        {UDAT_DIRECTION_THIS, UDAT_ABSOLUTE_SATURDAY, "this Saturday"},
+        {UDAT_DIRECTION_THIS, UDAT_ABSOLUTE_SUNDAY, "this Sunday"},
+        
+        {UDAT_DIRECTION_PLAIN, UDAT_ABSOLUTE_DAY, "day"},
+        {UDAT_DIRECTION_PLAIN, UDAT_ABSOLUTE_WEEK, "week"},
+        {UDAT_DIRECTION_PLAIN, UDAT_ABSOLUTE_MONTH, "month"},
+        {UDAT_DIRECTION_PLAIN, UDAT_ABSOLUTE_YEAR, "year"},
+        {UDAT_DIRECTION_PLAIN, UDAT_ABSOLUTE_MONDAY, "Monday"},
+        {UDAT_DIRECTION_PLAIN, UDAT_ABSOLUTE_TUESDAY, "Tuesday"},
+        {UDAT_DIRECTION_PLAIN, UDAT_ABSOLUTE_WEDNESDAY, "Wednesday"},
+        {UDAT_DIRECTION_PLAIN, UDAT_ABSOLUTE_THURSDAY, "Thursday"},
+        {UDAT_DIRECTION_PLAIN, UDAT_ABSOLUTE_FRIDAY, "Friday"},
+        {UDAT_DIRECTION_PLAIN, UDAT_ABSOLUTE_SATURDAY, "Saturday"},
+        {UDAT_DIRECTION_PLAIN, UDAT_ABSOLUTE_SUNDAY, "Sunday"},
+        
+        {UDAT_DIRECTION_PLAIN, UDAT_ABSOLUTE_NOW, "now"}
+};
+
+static WithoutQuantityExpected kSpanishNoQuantity[] = {
+        {UDAT_DIRECTION_NEXT_2, UDAT_ABSOLUTE_DAY, "pasado ma\\u00F1ana"},
+        {UDAT_DIRECTION_LAST_2, UDAT_ABSOLUTE_DAY, "antes de ayer"}
+};
+
+class RelativeDateTimeFormatterTest : public IntlTest {
+public:
+    RelativeDateTimeFormatterTest() {
+    }
+
+    void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par=0);
+private:
+    void TestEnglish();
+    void TestSerbian();
+    void TestEnglishNoQuantity();
+    void TestSpanishNoQuantity();
+    void TestFormatWithQuantityIllegalArgument();
+    void TestFormatWithoutQuantityIllegalArgument();
+    void TestSetNumberFormat();
+    void TestCombineDateAndTime();
+    void RunTest(
+            const Locale& locale,
+            const WithQuantityExpected* expectedResults,
+            int32_t expectedResultLength);
+    void RunTest(
+            const Locale& locale,
+            const WithoutQuantityExpected* expectedResults,
+            int32_t expectedResultLength);
+    void RunTest(
+            const RelativeDateTimeFormatter& fmt,
+            const WithQuantityExpected* expectedResults,
+            int32_t expectedResultLength,
+            const char *description);
+    void RunTest(
+            const RelativeDateTimeFormatter& fmt,
+            const WithoutQuantityExpected* expectedResults,
+            int32_t expectedResultLength,
+            const char *description);
+    void CheckExpectedResult(
+            const RelativeDateTimeFormatter& fmt,
+            const WithQuantityExpected& expectedResult,
+            const char* description);
+    void CheckExpectedResult(
+            const RelativeDateTimeFormatter& fmt,
+            const WithoutQuantityExpected& expectedResult,
+            const char* description);
+    void VerifyIllegalArgument(
+            const RelativeDateTimeFormatter& fmt,
+            UDateDirection direction,
+            UDateRelativeUnit unit);
+    void VerifyIllegalArgument(
+            const RelativeDateTimeFormatter& fmt,
+            UDateDirection direction,
+            UDateAbsoluteUnit unit);
+};
+
+void RelativeDateTimeFormatterTest::runIndexedTest(
+        int32_t index, UBool exec, const char *&name, char *) {
+    if (exec) {
+        logln("TestSuite RelativeDateTimeFormatterTest: ");
+    }
+    TESTCASE_AUTO_BEGIN;
+    TESTCASE_AUTO(TestEnglish);
+    TESTCASE_AUTO(TestSerbian);
+    TESTCASE_AUTO(TestEnglishNoQuantity);
+    TESTCASE_AUTO(TestSpanishNoQuantity);
+    TESTCASE_AUTO(TestFormatWithQuantityIllegalArgument);
+    TESTCASE_AUTO(TestFormatWithoutQuantityIllegalArgument);
+    TESTCASE_AUTO(TestSetNumberFormat);
+    TESTCASE_AUTO(TestCombineDateAndTime);
+    TESTCASE_AUTO_END;
+}
+
+void RelativeDateTimeFormatterTest::TestEnglish() {
+    RunTest("en", kEnglish, LENGTHOF(kEnglish));
+}
+
+void RelativeDateTimeFormatterTest::TestSerbian() {
+    RunTest("sr", kSerbian, LENGTHOF(kSerbian));
+}
+
+void RelativeDateTimeFormatterTest::TestEnglishNoQuantity() {
+    RunTest("en", kEnglishNoQuantity, LENGTHOF(kEnglishNoQuantity));
+}
+
+void RelativeDateTimeFormatterTest::TestSpanishNoQuantity() {
+    RunTest("es", kSpanishNoQuantity, LENGTHOF(kSpanishNoQuantity));
+}
+
+void RelativeDateTimeFormatterTest::TestFormatWithQuantityIllegalArgument() {
+    UErrorCode status = U_ZERO_ERROR;
+    RelativeDateTimeFormatter fmt("en", status);
+    if (U_FAILURE(status)) {
+        dataerrln("Failure creating format object - %s", u_errorName(status));
+        return;
+    }
+    VerifyIllegalArgument(fmt, UDAT_DIRECTION_PLAIN, UDAT_RELATIVE_DAYS);
+    VerifyIllegalArgument(fmt, UDAT_DIRECTION_THIS, UDAT_RELATIVE_DAYS);
+}
+
+void RelativeDateTimeFormatterTest::TestFormatWithoutQuantityIllegalArgument() {
+    UErrorCode status = U_ZERO_ERROR;
+    RelativeDateTimeFormatter fmt("en", status);
+    if (U_FAILURE(status)) {
+        dataerrln("Failure creating format object - %s", u_errorName(status));
+        return;
+    }
+    VerifyIllegalArgument(fmt, UDAT_DIRECTION_LAST, UDAT_ABSOLUTE_NOW);
+    VerifyIllegalArgument(fmt, UDAT_DIRECTION_NEXT, UDAT_ABSOLUTE_NOW);
+    VerifyIllegalArgument(fmt, UDAT_DIRECTION_THIS, UDAT_ABSOLUTE_NOW);
+}
+
+void RelativeDateTimeFormatterTest::TestSetNumberFormat() {
+    UErrorCode status = U_ZERO_ERROR;
+    RelativeDateTimeFormatter fmt("en", status);
+    if (U_FAILURE(status)) {
+        dataerrln("Failure creating format object - %s", u_errorName(status));
+        return;
+    }
+    LocalPointer<NumberFormat> numberFormat(NumberFormat::createInstance("en", status));
+    numberFormat->setMinimumFractionDigits(1);
+    numberFormat->setMaximumFractionDigits(1);
+    fmt.setNumberFormat(*numberFormat);
+
+    // Prove that we made a defensive copy.
+    numberFormat->setMinimumFractionDigits(3);
+    numberFormat->setMaximumFractionDigits(3);
+
+    RunTest(fmt, kEnglishDecimal, LENGTHOF(kEnglishDecimal), "en decimal digits");
+}
+
+void RelativeDateTimeFormatterTest::TestCombineDateAndTime() {
+    UErrorCode status = U_ZERO_ERROR;
+    RelativeDateTimeFormatter fmt("en", status);
+    if (U_FAILURE(status)) {
+        dataerrln("Failure creating format object - %s", u_errorName(status));
+        return;
+    }
+    UnicodeString actual;
+    fmt.combineDateAndTime(
+        UnicodeString("yesterday"),
+        UnicodeString("3:50"),
+        actual,
+        status);
+    UnicodeString expected("yesterday, 3:50");
+    if (expected != actual) {
+        errln("Expected "+expected+", got "+actual);
+    }
+}
+    
+
+void RelativeDateTimeFormatterTest::RunTest(
+        const Locale& locale,
+        const WithQuantityExpected* expectedResults,
+        int32_t expectedResultLength) {
+    UErrorCode status = U_ZERO_ERROR;
+    RelativeDateTimeFormatter fmt(locale, status);
+    if (U_FAILURE(status)) {
+        dataerrln("Unable to create format object - %s", u_errorName(status));
+        return;
+   }
+    RunTest(fmt, expectedResults, expectedResultLength, locale.getName());
+}
+
+void RelativeDateTimeFormatterTest::RunTest(
+        const Locale& locale,
+        const WithoutQuantityExpected* expectedResults,
+        int32_t expectedResultLength) {
+    UErrorCode status = U_ZERO_ERROR;
+    RelativeDateTimeFormatter fmt(locale, status);
+    if (U_FAILURE(status)) {
+        dataerrln("Unable to create format object - %s", u_errorName(status));
+        return;
+    }
+    RunTest(fmt, expectedResults, expectedResultLength, locale.getName());
+}
+
+void RelativeDateTimeFormatterTest::RunTest(
+        const RelativeDateTimeFormatter& fmt,
+        const WithQuantityExpected* expectedResults,
+        int32_t expectedResultLength,
+        const char *description) {
+    for (int32_t i = 0; i < expectedResultLength; ++i) {
+        CheckExpectedResult(fmt, expectedResults[i], description);
+    }
+}
+
+void RelativeDateTimeFormatterTest::RunTest(
+        const RelativeDateTimeFormatter& fmt,
+        const WithoutQuantityExpected* expectedResults,
+        int32_t expectedResultLength,
+        const char *description) {
+    for (int32_t i = 0; i < expectedResultLength; ++i) {
+        CheckExpectedResult(fmt, expectedResults[i], description);
+    }
+}
+
+void RelativeDateTimeFormatterTest::CheckExpectedResult(
+        const RelativeDateTimeFormatter& fmt,
+        const WithQuantityExpected& expectedResult,
+        const char* description) {
+    UErrorCode status = U_ZERO_ERROR;
+    UnicodeString actual;
+    fmt.format(expectedResult.value, expectedResult.direction, expectedResult.unit, actual, status);
+    UnicodeString expected(expectedResult.expected, -1, US_INV);
+    expected = expected.unescape();
+    char buffer[256];
+    sprintf(
+            buffer,
+            "%s, %f, %s, %s",
+            description,
+            expectedResult.value,
+            DirectionStr(expectedResult.direction),
+            RelativeUnitStr(expectedResult.unit));
+    if (actual != expected) {
+        errln(UnicodeString("Fail: Expected: ") + expected
+                + ", Got: " + actual
+                + ", For: " + buffer);
+    }
+}
+
+void RelativeDateTimeFormatterTest::CheckExpectedResult(
+        const RelativeDateTimeFormatter& fmt,
+        const WithoutQuantityExpected& expectedResult,
+        const char* description) {
+    UErrorCode status = U_ZERO_ERROR;
+    UnicodeString actual;
+    fmt.format(expectedResult.direction, expectedResult.unit, actual, status);
+    UnicodeString expected(expectedResult.expected, -1, US_INV);
+    expected = expected.unescape();
+    char buffer[256];
+    sprintf(
+            buffer,
+            "%s, %s, %s",
+            description,
+            DirectionStr(expectedResult.direction),
+            AbsoluteUnitStr(expectedResult.unit));
+    if (actual != expected) {
+        errln(UnicodeString("Fail: Expected: ") + expected
+                + ", Got: " + actual
+                + ", For: " + buffer);
+    }
+}
+
+void RelativeDateTimeFormatterTest::VerifyIllegalArgument(
+        const RelativeDateTimeFormatter& fmt,
+        UDateDirection direction,
+        UDateRelativeUnit unit) {
+    UnicodeString appendTo;
+    UErrorCode status = U_ZERO_ERROR;
+    fmt.format(1.0, direction, unit, appendTo, status);
+    if (status != U_ILLEGAL_ARGUMENT_ERROR) {
+        errln("Expected U_ILLEGAL_ARGUMENT_ERROR, got %s", u_errorName(status));
+    }
+}
+
+void RelativeDateTimeFormatterTest::VerifyIllegalArgument(
+        const RelativeDateTimeFormatter& fmt,
+        UDateDirection direction,
+        UDateAbsoluteUnit unit) {
+    UnicodeString appendTo;
+    UErrorCode status = U_ZERO_ERROR;
+    fmt.format(direction, unit, appendTo, status);
+    if (status != U_ILLEGAL_ARGUMENT_ERROR) {
+        errln("Expected U_ILLEGAL_ARGUMENT_ERROR, got %s", u_errorName(status));
+    }
+}
+
+static const char *kLast2 = "Last_2";
+static const char *kLast = "Last";
+static const char *kThis = "This";
+static const char *kNext = "Next";
+static const char *kNext2 = "Next_2";
+static const char *kPlain = "Plain";
+
+static const char *kSeconds = "Seconds";
+static const char *kMinutes = "Minutes";
+static const char *kHours = "Hours";
+static const char *kDays = "Days";
+static const char *kWeeks = "Weeks";
+static const char *kMonths = "Months";
+static const char *kYears = "Years";
+
+static const char *kSunday = "Sunday";
+static const char *kMonday = "Monday";
+static const char *kTuesday = "Tuesday";
+static const char *kWednesday = "Wednesday";
+static const char *kThursday = "Thursday";
+static const char *kFriday = "Friday";
+static const char *kSaturday = "Saturday";
+static const char *kDay = "Day";
+static const char *kWeek = "Week";
+static const char *kMonth = "Month";
+static const char *kYear = "Year";
+static const char *kNow = "Now";
+
+static const char *kUndefined = "Undefined";
+
+static const char *DirectionStr(
+        UDateDirection direction) {
+    switch (direction) {
+        case UDAT_DIRECTION_LAST_2:
+            return kLast2;
+        case UDAT_DIRECTION_LAST:
+            return kLast;
+        case UDAT_DIRECTION_THIS:
+            return kThis;
+        case UDAT_DIRECTION_NEXT:
+            return kNext;
+        case UDAT_DIRECTION_NEXT_2:
+            return kNext2;
+        case UDAT_DIRECTION_PLAIN:
+            return kPlain;
+        default:
+            return kUndefined;
+    }
+    return kUndefined;
+}
+
+static const char *RelativeUnitStr(
+        UDateRelativeUnit unit) {
+    switch (unit) {
+        case UDAT_RELATIVE_SECONDS:
+            return kSeconds;
+        case UDAT_RELATIVE_MINUTES:
+            return kMinutes;
+        case UDAT_RELATIVE_HOURS:
+            return kHours;
+        case UDAT_RELATIVE_DAYS:
+            return kDays;
+        case UDAT_RELATIVE_WEEKS:
+            return kWeeks;
+        case UDAT_RELATIVE_MONTHS:
+            return kMonths;
+        case UDAT_RELATIVE_YEARS:
+            return kYears;
+        default:
+            return kUndefined;
+    }
+    return kUndefined;
+}
+
+static const char *AbsoluteUnitStr(
+        UDateAbsoluteUnit unit) {
+    switch (unit) {
+        case UDAT_ABSOLUTE_SUNDAY:
+            return kSunday;
+        case UDAT_ABSOLUTE_MONDAY:
+            return kMonday;
+        case UDAT_ABSOLUTE_TUESDAY:
+            return kTuesday;
+        case UDAT_ABSOLUTE_WEDNESDAY:
+            return kWednesday;
+        case UDAT_ABSOLUTE_THURSDAY:
+            return kThursday;
+        case UDAT_ABSOLUTE_FRIDAY:
+            return kFriday;
+        case UDAT_ABSOLUTE_SATURDAY:
+            return kSaturday;
+        case UDAT_ABSOLUTE_DAY:
+            return kDay;
+        case UDAT_ABSOLUTE_WEEK:
+            return kWeek;
+        case UDAT_ABSOLUTE_MONTH:
+            return kMonth;
+        case UDAT_ABSOLUTE_YEAR:
+            return kYear;
+        case UDAT_ABSOLUTE_NOW:
+            return kNow;
+        default:
+            return kUndefined;
+    }
+    return kUndefined;
+}
+
+extern IntlTest *createRelativeDateTimeFormatterTest() {
+    return new RelativeDateTimeFormatterTest();
+}
+
+#endif