Merge "Make ReferenceQueueTest#test_removeJ more robust."
diff --git a/benchmarks/src/benchmarks/regression/RelativeDateTimeFormatterBenchmark.java b/benchmarks/src/benchmarks/regression/RelativeDateTimeFormatterBenchmark.java
new file mode 100644
index 0000000..30670b4
--- /dev/null
+++ b/benchmarks/src/benchmarks/regression/RelativeDateTimeFormatterBenchmark.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package benchmarks.regression;
+
+import com.google.caliper.SimpleBenchmark;
+
+import java.util.Locale;
+import java.util.TimeZone;
+
+import static libcore.icu.RelativeDateTimeFormatter.getRelativeDateTimeString;
+import static libcore.icu.RelativeDateTimeFormatter.getRelativeTimeSpanString;
+import static libcore.icu.RelativeDateTimeFormatter.FORMAT_ABBREV_RELATIVE;
+
+public class RelativeDateTimeFormatterBenchmark extends SimpleBenchmark {
+  public void timeRelativeDateTimeFormatter_getRelativeTimeSpanString(int reps) throws Exception {
+    Locale l = Locale.US;
+    TimeZone utc = TimeZone.getTimeZone("Europe/London");
+    int flags = 0;
+
+    for (int rep = 0; rep < reps; ++rep) {
+      getRelativeTimeSpanString(l, utc, 0L, 0L, 0L, flags);
+    }
+  }
+
+  public void timeRelativeDateTimeFormatter_getRelativeTimeSpanString_ABBREV(int reps) throws Exception {
+    Locale l = Locale.US;
+    TimeZone utc = TimeZone.getTimeZone("UTC");
+    int flags = FORMAT_ABBREV_RELATIVE;
+
+    for (int rep = 0; rep < reps; ++rep) {
+      getRelativeTimeSpanString(l, utc, 0L, 0L, 0L, flags);
+    }
+  }
+
+  public void timeRelativeDateTimeFormatter_getRelativeDateTimeString(int reps) throws Exception {
+    Locale l = Locale.US;
+    TimeZone utc = TimeZone.getTimeZone("UTC");
+    int flags = 0;
+
+    for (int rep = 0; rep < reps; ++rep) {
+      getRelativeDateTimeString(l, utc, 0L, 0L, 0L, 0L, flags);
+    }
+  }
+
+  public void timeRelativeDateTimeFormatter_getRelativeDateTimeString_ABBREV(int reps) throws Exception {
+    Locale l = Locale.US;
+    TimeZone utc = TimeZone.getTimeZone("America/Los_Angeles");
+    int flags = FORMAT_ABBREV_RELATIVE;
+
+    for (int rep = 0; rep < reps; ++rep) {
+      getRelativeDateTimeString(l, utc, 0L, 0L, 0L, 0L, flags);
+    }
+  }
+}
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/ChoiceFormatTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/ChoiceFormatTest.java
index e4d6bd8..d52e586 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/ChoiceFormatTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/ChoiceFormatTest.java
@@ -21,6 +21,7 @@
 import java.text.FieldPosition;
 import java.text.MessageFormat;
 import java.text.ParsePosition;
+import java.util.Locale;
 
 import junit.framework.TestCase;
 
@@ -466,4 +467,23 @@
 		assertEquals("GREATER_THAN_ONE", fmt.format(999999999D));
 		assertEquals("GREATER_THAN_ONE", fmt.format(Double.POSITIVE_INFINITY));
 	}
+
+    // http://b/19149384
+    public void testToPatternWithInfinities() {
+        final ChoiceFormat fmt = new ChoiceFormat(
+                "-\u221E<are negative|0<are fractions|1#is one|1.0<is 1+|\u221E<are many.");
+        assertEquals("-\u221E<are negative|0.0<are fractions|1.0#is one|1.0<is 1+|\u221E<are many.",
+                fmt.toPattern());
+    }
+
+    // http://b/19011159
+    public void testEscapedPatternWithConsecutiveQuotes() {
+        ChoiceFormat format = new ChoiceFormat("0#1'2''3'''4''''.");
+        String formatted = format.format(0);
+        assertEquals("12'3'4''.", formatted);
+
+        format = new ChoiceFormat("0#1'2''3'''''4''''.");
+        formatted = format.format(0);
+        assertEquals("12'3''4''.", formatted);
+    }
 }
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/MessageFormatTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/MessageFormatTest.java
index 171f247..0920714 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/MessageFormatTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/text/MessageFormatTest.java
@@ -950,4 +950,12 @@
     String res = MessageFormat.format("bgcolor=\"{10}\"", messageArgs);
     assertEquals(res, "bgcolor=\"example10\"");
   }
+
+  // http://b/19011159
+  public void test19011159() {
+    final String pattern = "ab{0,choice,0#1'2''3'''4''''.}yz";
+    final MessageFormat format = new MessageFormat(pattern, Locale.ENGLISH);
+    final Object[] zero0 = new Object[] { 0 };
+    assertEquals("ab12'3'4''.yz", format.format(zero0));
+  }
 }
diff --git a/luni/src/main/java/java/text/ChoiceFormat.java b/luni/src/main/java/java/text/ChoiceFormat.java
index 014b8c7..3d3cb3a 100644
--- a/luni/src/main/java/java/text/ChoiceFormat.java
+++ b/luni/src/main/java/java/text/ChoiceFormat.java
@@ -303,37 +303,16 @@
     }
 
     /**
-     * Returns the double value which is closest to the specified double but
-     * larger.
-     *
-     * @param value
-     *            a double value.
-     * @return the next larger double value.
+     * Equivalent to {@link Math#nextUp(double)}.
      */
     public static final double nextDouble(double value) {
-        if (value == Double.POSITIVE_INFINITY) {
-            return value;
-        }
-        long bits;
-        // Handle -0.0
-        if (value == 0) {
-            bits = 0;
-        } else {
-            bits = Double.doubleToLongBits(value);
-        }
-        return Double.longBitsToDouble(value < 0 ? bits - 1 : bits + 1);
+        return Math.nextUp(value);
     }
 
     /**
-     * Returns the double value which is closest to the specified double but
-     * either larger or smaller as specified.
-     *
-     * @param value
-     *            a double value.
-     * @param increment
-     *            {@code true} to get the next larger value, {@code false} to
-     *            get the previous smaller value.
-     * @return the next larger or smaller double value.
+     * Equivalent to {@link Math#nextUp(double)} if {@code increment == true}, and
+     * {@link Math#nextAfter(double, double)} with {@code direction == Double.NEGATIVE_INFINITY}
+     * otherwise.
      */
     public static double nextDouble(double value, boolean increment) {
         return increment ? nextDouble(value) : previousDouble(value);
@@ -385,25 +364,11 @@
     }
 
     /**
-     * Returns the double value which is closest to the specified double but
-     * smaller.
-     *
-     * @param value
-     *            a double value.
-     * @return the next smaller double value.
+     * Equivalent to {@link Math#nextAfter(double, double)} with
+     * {@code direction == Double.NEGATIVE_INFINITY}.
      */
     public static final double previousDouble(double value) {
-        if (value == Double.NEGATIVE_INFINITY) {
-            return value;
-        }
-        long bits;
-        // Handle 0.0
-        if (value == 0) {
-            bits = 0x8000000000000000L;
-        } else {
-            bits = Double.doubleToLongBits(value);
-        }
-        return Double.longBitsToDouble(value <= 0 ? bits + 1 : bits - 1);
+        return Math.nextAfter(value, Double.NEGATIVE_INFINITY);
     }
 
     /**
@@ -453,9 +418,30 @@
             if (i != 0) {
                 buffer.append('|');
             }
-            String previous = String.valueOf(previousDouble(choiceLimits[i]));
-            String limit = String.valueOf(choiceLimits[i]);
-            if (previous.length() < limit.length()) {
+
+            final String previous = String.valueOf(previousDouble(choiceLimits[i]));
+            final String limit = String.valueOf(choiceLimits[i]);
+
+            // Hack to make the output of toPattern parseable by another ChoiceFormat.
+            // String.valueOf() will emit "Infinity", which isn't parseable by our NumberFormat
+            // instances.
+            //
+            // Ideally, we'd just use NumberFormat.format() to emit output (to be symmetric with
+            // our usage of NumberFormat.parse()) but it's hard set the right number of significant
+            // digits in order to output a format string that's equivalent to the original input.
+            if (Double.isInfinite(choiceLimits[i]) ||
+                    Double.isInfinite(previousDouble(choiceLimits[i]))) {
+                if (choiceLimits[i] < 0) {
+                    buffer.append("-\u221E");
+                    buffer.append('<');
+                } else {
+                    buffer.append('\u221E');
+                    buffer.append('<');
+                }
+            } else if (previous.length() < limit.length()) {
+                // What the... i don't even.... sigh. This is trying to figure out whether the
+                // element was a "<" or a "#". The idea being that users will specify "reasonable"
+                // quantities and calling nextDouble will result in a "longer" number in most cases.
                 buffer.append(previous);
                 buffer.append('<');
             } else {
diff --git a/luni/src/main/java/java/text/Format.java b/luni/src/main/java/java/text/Format.java
index 58671fa..c4dc5f0 100644
--- a/luni/src/main/java/java/text/Format.java
+++ b/luni/src/main/java/java/text/Format.java
@@ -177,23 +177,26 @@
     static boolean upTo(String string, ParsePosition position,
             StringBuffer buffer, char stop) {
         int index = position.getIndex(), length = string.length();
-        boolean lastQuote = false, quote = false;
+
+        int numConsecutiveQuotes = 0;
+        boolean quote = false;
         while (index < length) {
             char ch = string.charAt(index++);
             if (ch == '\'') {
-                if (lastQuote) {
+                ++numConsecutiveQuotes;
+                if (numConsecutiveQuotes != 0 && numConsecutiveQuotes % 2 == 0) {
                     buffer.append('\'');
                 }
                 quote = !quote;
-                lastQuote = true;
             } else if (ch == stop && !quote) {
                 position.setIndex(index);
                 return true;
             } else {
-                lastQuote = false;
+                numConsecutiveQuotes = 0;
                 buffer.append(ch);
             }
         }
+
         position.setIndex(index);
         return false;
     }
diff --git a/luni/src/main/java/java/text/MessageFormat.java b/luni/src/main/java/java/text/MessageFormat.java
index cf306a7..f48cebd 100644
--- a/luni/src/main/java/java/text/MessageFormat.java
+++ b/luni/src/main/java/java/text/MessageFormat.java
@@ -580,6 +580,12 @@
             }
             if (format instanceof ChoiceFormat) {
                 String result = format.format(arg);
+                // Escape quotes in the result because the ChoiceFormat would've already
+                // dealt with them for us. In other words, any quotes that are present in the
+                // result are due to escaped quotes in the original input. We should preserve
+                // them in the output instead of having them processed again with the message
+                // format we're creating below.
+                result = result.replace("'", "''");
                 MessageFormat mf = new MessageFormat(result);
                 mf.setLocale(locale);
                 mf.format(objects, buffer, passedField);
diff --git a/luni/src/main/java/libcore/icu/DateIntervalFormat.java b/luni/src/main/java/libcore/icu/DateIntervalFormat.java
index 3855654..fbef89a 100644
--- a/luni/src/main/java/libcore/icu/DateIntervalFormat.java
+++ b/luni/src/main/java/libcore/icu/DateIntervalFormat.java
@@ -224,10 +224,20 @@
     return c.get(Calendar.YEAR) == now.get(Calendar.YEAR);
   }
 
-  private static int dayDistance(Calendar c1, Calendar c2) {
+  // Return the date difference for the two times in a given timezone.
+  public static int dayDistance(TimeZone tz, long startTime, long endTime) {
+    return julianDay(tz, endTime) - julianDay(tz, startTime);
+  }
+
+  public static int dayDistance(Calendar c1, Calendar c2) {
     return julianDay(c2) - julianDay(c1);
   }
 
+  private static int julianDay(TimeZone tz, long time) {
+    long utcMs = time + tz.getOffset(time);
+    return (int) (utcMs / DAY_IN_MS) + EPOCH_JULIAN_DAY;
+  }
+
   private static int julianDay(Calendar c) {
     long utcMs = c.getTimeInMillis() + c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET);
     return (int) (utcMs / DAY_IN_MS) + EPOCH_JULIAN_DAY;
diff --git a/luni/src/main/java/libcore/icu/RelativeDateTimeFormatter.java b/luni/src/main/java/libcore/icu/RelativeDateTimeFormatter.java
new file mode 100644
index 0000000..0e715b6
--- /dev/null
+++ b/luni/src/main/java/libcore/icu/RelativeDateTimeFormatter.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.icu;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import libcore.util.BasicLruCache;
+import libcore.icu.DateIntervalFormat;
+
+/**
+ * Exposes icu4c's RelativeDateTimeFormatter.
+ */
+public final class RelativeDateTimeFormatter {
+
+  // Values from public API in DateUtils to be used in this class. They must
+  // match the ones in DateUtils.java.
+  public static final int FORMAT_SHOW_TIME = 0x00001;
+  public static final int FORMAT_SHOW_YEAR = 0x00004;
+  public static final int FORMAT_SHOW_DATE = 0x00010;
+  public static final int FORMAT_ABBREV_MONTH = 0x10000;
+  public static final int FORMAT_NUMERIC_DATE = 0x20000;
+  public static final int FORMAT_ABBREV_RELATIVE = 0x40000;
+  public static final int FORMAT_ABBREV_ALL = 0x80000;
+
+  public static final long SECOND_IN_MILLIS = 1000;
+  public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
+  public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
+  public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
+  public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7;
+  // YEAR_IN_MILLIS considers 364 days as a year. However, since this
+  // constant comes from public API in DateUtils, it cannot be fixed here.
+  public static final long YEAR_IN_MILLIS = WEEK_IN_MILLIS * 52;
+
+  // Values from icu4c UDateRelativeUnit enum in unicode/reldatefmt.h.
+  // The following U* constants must agree with the ones in icu4c.
+  private static final int UDAT_RELATIVE_SECONDS = 0;
+  private static final int UDAT_RELATIVE_MINUTES = 1;
+  private static final int UDAT_RELATIVE_HOURS = 2;
+  private static final int UDAT_RELATIVE_DAYS = 3;
+  private static final int UDAT_RELATIVE_WEEKS = 4;
+  private static final int UDAT_RELATIVE_MONTHS = 5;
+  private static final int UDAT_RELATIVE_YEARS = 6;
+
+  // Values from icu4c UDateAbsoluteUnit enum in unicode/reldatefmt.h.
+  private static final int UDAT_ABSOLUTE_DAY = 7;
+
+  // Values from icu4c UDisplayContext enum in unicode/udisplaycontext.h.
+  private static final int UDISPCTX_CAPITALIZATION_NONE = 1 << 8;
+  private static final int UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE = (1 << 8) + 2;
+
+  // Values from icu4c UDateDirection enum in unicode/reldatefmt.h.
+  private static final int UDAT_DIRECTION_LAST_2 = 0;
+  private static final int UDAT_DIRECTION_LAST = 1;
+  private static final int UDAT_DIRECTION_THIS = 2;
+  private static final int UDAT_DIRECTION_NEXT = 3;
+  private static final int UDAT_DIRECTION_NEXT_2 = 4;
+  private static final int UDAT_DIRECTION_PLAIN = 5;
+
+  // Values from icu4c UDateRelativeDateTimeFormatterStyle enum in
+  // unicode/reldatefmt.h.
+  private static final int UDAT_STYLE_LONG = 0;
+  private static final int UDAT_STYLE_SHORT = 1;
+  private static final int UDAT_STYLE_NARROW = 2;
+
+  private static final FormatterCache CACHED_FORMATTERS = new FormatterCache();
+  private static final int EPOCH_JULIAN_DAY = 2440588;
+
+  static class FormatterCache extends BasicLruCache<String, Long> {
+    FormatterCache() {
+      super(8);
+    }
+
+    protected void entryEvicted(String key, Long value) {
+      destroyRelativeDateTimeFormatter(value);
+    }
+  };
+
+  private RelativeDateTimeFormatter() {
+  }
+
+  /**
+   * This is the internal API that implements the functionality of
+   * DateUtils.getRelativeTimeSpanString(long, long, long, int), which is to
+   * return a string describing 'time' as a time relative to 'now' such as
+   * '5 minutes ago', or 'in 2 days'. More examples can be found in DateUtils'
+   * doc.
+   *
+   * In the implementation below, it selects the appropriate time unit based on
+   * the elapsed time between time' and 'now', e.g. minutes, days and etc.
+   * Callers may also specify the desired minimum resolution to show in the
+   * result. For example, '45 minutes ago' will become '0 hours ago' when
+   * minResolution is HOUR_IN_MILLIS. Once getting the quantity and unit to
+   * display, it calls icu4c's RelativeDateTimeFormatter to format the actual
+   * string according to the given locale.
+   *
+   * Note that when minResolution is set to DAY_IN_MILLIS, it returns the
+   * result depending on the actual date difference. For example, it will
+   * return 'Yesterday' even if 'time' was less than 24 hours ago but falling
+   * onto a different calendar day.
+   *
+   * It takes two additional parameters of Locale and TimeZone than the
+   * DateUtils' API. Caller must specify the locale and timezone.
+   * FORMAT_ABBREV_RELATIVE or FORMAT_ABBREV_ALL can be set in 'flags' to get
+   * the abbreviated forms when available. When 'time' equals to 'now', it
+   * always // returns a string like '0 seconds/minutes/... ago' according to
+   * minResolution.
+   */
+  public static String getRelativeTimeSpanString(Locale locale, TimeZone tz, long time,
+      long now, long minResolution, int flags) {
+    if (locale == null) {
+      throw new NullPointerException("locale == null");
+    }
+    if (tz == null) {
+      throw new NullPointerException("tz == null");
+    }
+    long duration = Math.abs(now - time);
+    boolean past = (now >= time);
+
+    // Use UDAT_STYLE_SHORT or UDAT_STYLE_LONG.
+    int style;
+    if ((flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0) {
+        style = UDAT_STYLE_SHORT;
+    } else {
+        style = UDAT_STYLE_LONG;
+    }
+
+    // We are currently using the _NONE and _FOR_BEGINNING_OF_SENTENCE for the
+    // capitalization. We use _NONE for relative time strings, and the latter
+    // to capitalize the first letter of strings that don't contain
+    // quantities, such as "Yesterday", "Today" and etc. This is for backward
+    // compatibility (see b/14493853).
+    int capitalizationContext = UDISPCTX_CAPITALIZATION_NONE;
+
+    // Use UDAT_DIRECTION_LAST or UDAT_DIRECTION_NEXT.
+    int direction;
+    if (past) {
+        direction = UDAT_DIRECTION_LAST;
+    } else {
+        direction = UDAT_DIRECTION_NEXT;
+    }
+
+    // 'relative' defaults to true as we are generating relative time span
+    // string. It will be set to false when we try to display strings without
+    // a quantity, such as 'Yesterday', etc.
+    boolean relative = true;
+    int count;
+    int unit;
+
+    if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) {
+      count = (int)(duration / SECOND_IN_MILLIS);
+      unit = UDAT_RELATIVE_SECONDS;
+    } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) {
+      count = (int)(duration / MINUTE_IN_MILLIS);
+      unit = UDAT_RELATIVE_MINUTES;
+    } else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) {
+      // Even if 'time' actually happened yesterday, we don't format it as
+      // "Yesterday" in this case. Unless the duration is longer than a day,
+      // or minResolution is specified as DAY_IN_MILLIS by user.
+      count = (int)(duration / HOUR_IN_MILLIS);
+      unit = UDAT_RELATIVE_HOURS;
+    } else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) {
+      count = Math.abs(DateIntervalFormat.dayDistance(tz, time, now));
+      unit = UDAT_RELATIVE_DAYS;
+
+      if (count == 2) {
+        // Some locales have special terms for "2 days ago". Return them if
+        // available. Note that we cannot set up direction and unit here and
+        // make it fall through to use the call near the end of the function,
+        // because for locales that don't have special terms for "2 days ago",
+        // icu4c returns an empty string instead of falling back to strings
+        // like "2 days ago".
+        capitalizationContext = UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE;
+        String str;
+        if (past) {
+          synchronized (CACHED_FORMATTERS) {
+            str = formatWithAbsoluteUnit(getFormatter(locale.toString(), style,
+                                                      capitalizationContext),
+                                         UDAT_DIRECTION_LAST_2, UDAT_ABSOLUTE_DAY);
+          }
+        } else {
+          synchronized (CACHED_FORMATTERS) {
+            str = formatWithAbsoluteUnit(getFormatter(locale.toString(), style,
+                                                      capitalizationContext),
+                                         UDAT_DIRECTION_NEXT_2, UDAT_ABSOLUTE_DAY);
+          }
+        }
+        if (!str.isEmpty()) {
+          return str;
+        }
+        // Fall back to show something like "2 days ago". Reset the
+        // capitalization setting.
+        capitalizationContext = UDISPCTX_CAPITALIZATION_NONE;
+      } else if (count == 1) {
+        // Show "Yesterday / Tomorrow" instead of "1 day ago / in 1 day".
+        unit = UDAT_ABSOLUTE_DAY;
+        relative = false;
+      } else if (count == 0) {
+        // Show "Today" if time and now are on the same day.
+        unit = UDAT_ABSOLUTE_DAY;
+        direction = UDAT_DIRECTION_THIS;
+        relative = false;
+      }
+    } else if (minResolution == WEEK_IN_MILLIS) {
+      count = (int)(duration / WEEK_IN_MILLIS);
+      unit = UDAT_RELATIVE_WEEKS;
+    } else {
+      // The duration is longer than a week and minResolution is not
+      // WEEK_IN_MILLIS. Return the absolute date instead of relative time.
+      return DateIntervalFormat.formatDateRange(locale, tz, time, time, flags);
+    }
+
+    if (relative) {
+      synchronized (CACHED_FORMATTERS) {
+        return formatWithRelativeUnit(getFormatter(locale.toString(), style,
+                                                   capitalizationContext),
+                                      count, direction, unit);
+      }
+    } else {
+      capitalizationContext = UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE;
+      synchronized (CACHED_FORMATTERS) {
+        return formatWithAbsoluteUnit(getFormatter(locale.toString(), style,
+                                                   capitalizationContext),
+                                      direction, unit);
+      }
+    }
+  }
+
+  /**
+   * This is the internal API that implements
+   * DateUtils.getRelativeDateTimeString(long, long, long, long, int), which is
+   * to return a string describing 'time' as a time relative to 'now', formatted
+   * like '[relative time/date], [time]'. More examples can be found in
+   * DateUtils' doc.
+   *
+   * The function is similar to getRelativeTimeSpanString, but it always
+   * appends the absolute time to the relative time string to return
+   * '[relative time/date clause], [absolute time clause]'. It also takes an
+   * extra parameter transitionResolution to determine the format of the date
+   * clause. When the elapsed time is less than the transition resolution, it
+   * displays the relative time string. Otherwise, it gives the absolute
+   * numeric date string as the date clause. With the date and time clauses, it
+   * relies on icu4c's RelativeDateTimeFormatter::combineDateAndTime() to
+   * concatenate the two.
+   *
+   * It takes two additional parameters of Locale and TimeZone than the
+   * DateUtils' API. Caller must specify the locale and timezone.
+   * FORMAT_ABBREV_RELATIVE or FORMAT_ABBREV_ALL can be set in 'flags' to get
+   * the abbreviated forms when they are available.
+   *
+   * Bug 5252772: Since the absolute time will always be part of the result,
+   * minResolution will be set to at least DAY_IN_MILLIS to correctly indicate
+   * the date difference. For example, when it's 1:30 AM, it will return
+   * 'Yesterday, 11:30 PM' for getRelativeDateTimeString(null, null,
+   * now - 2 hours, now, HOUR_IN_MILLIS, DAY_IN_MILLIS, 0), instead of '2
+   * hours ago, 11:30 PM' even with minResolution being HOUR_IN_MILLIS.
+   */
+  public static String getRelativeDateTimeString(Locale locale, TimeZone tz, long time,
+      long now, long minResolution, long transitionResolution, int flags) {
+
+    if (locale == null) {
+      throw new NullPointerException("locale == null");
+    }
+    if (tz == null) {
+      throw new NullPointerException("tz == null");
+    }
+
+    // Get the time clause first.
+    String timeClause = DateIntervalFormat.formatDateRange(locale, tz, time, time,
+                                                           FORMAT_SHOW_TIME);
+
+    long duration = Math.abs(now - time);
+    // It doesn't make much sense to have results like: "1 week ago, 10:50 AM".
+    if (transitionResolution > WEEK_IN_MILLIS) {
+        transitionResolution = WEEK_IN_MILLIS;
+    }
+    // Use UDAT_STYLE_SHORT or UDAT_STYLE_LONG.
+    int style;
+    if ((flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0) {
+        style = UDAT_STYLE_SHORT;
+    } else {
+        style = UDAT_STYLE_LONG;
+    }
+
+    // icu4c also has other options available to control the capitalization. We
+    // are currently using the _NONE option only.
+    int capitalizationContext = UDISPCTX_CAPITALIZATION_NONE;
+
+    Calendar timeCalendar = new GregorianCalendar(false);
+    timeCalendar.setTimeZone(tz);
+    timeCalendar.setTimeInMillis(time);
+    Calendar nowCalendar = new GregorianCalendar(false);
+    nowCalendar.setTimeZone(tz);
+    nowCalendar.setTimeInMillis(now);
+
+    int days = Math.abs(DateIntervalFormat.dayDistance(timeCalendar, nowCalendar));
+
+    // Now get the date clause, either in relative format or the actual date.
+    String dateClause;
+    if (duration < transitionResolution) {
+      // This is to fix bug 5252772. If there is any date difference, we should
+      // promote the minResolution to DAY_IN_MILLIS so that it can display the
+      // date instead of "x hours/minutes ago, [time]".
+      if (days > 0 && minResolution < DAY_IN_MILLIS) {
+         minResolution = DAY_IN_MILLIS;
+      }
+      dateClause = getRelativeTimeSpanString(locale, tz, time, now, minResolution, flags);
+    } else {
+      // We always use fixed flags to format the date clause. User-supplied
+      // flags are ignored.
+      if (days == 0) {
+        // Same day
+        flags = FORMAT_SHOW_TIME;
+      } else if (timeCalendar.get(Calendar.YEAR) != nowCalendar.get(Calendar.YEAR)) {
+        // Different years
+        flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE;
+      } else {
+        // Default
+        flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
+      }
+
+      dateClause = DateIntervalFormat.formatDateRange(locale, tz, time, time, flags);
+    }
+
+    // Combine the two clauses, such as '5 days ago, 10:50 AM'.
+    synchronized (CACHED_FORMATTERS) {
+      return combineDateAndTime(getFormatter(locale.toString(), style, capitalizationContext),
+                                dateClause, timeClause);
+    }
+  }
+
+  /**
+   * getFormatter() caches the RelativeDateTimeFormatter instances based on
+   * the combination of localeName, sytle and capitalizationContext. It
+   * should always be used along with the action of the formatter in a
+   * synchronized block, because otherwise the formatter returned by
+   * getFormatter() may have been evicted by the time of the call to
+   * formatter->action().
+   */
+  private static long getFormatter(String localeName, int style, int capitalizationContext) {
+    String key = localeName + "\t" + style + "\t" + capitalizationContext;
+    Long formatter = CACHED_FORMATTERS.get(key);
+    if (formatter == null) {
+      formatter = createRelativeDateTimeFormatter(localeName, style, capitalizationContext);
+      CACHED_FORMATTERS.put(key, formatter);
+    }
+    return formatter;
+  }
+
+  private static native long createRelativeDateTimeFormatter(String localeName, int style, int capitalizationContext);
+  private static native void destroyRelativeDateTimeFormatter(long address);
+  private static native String formatWithRelativeUnit(long address, int quantity, int direction, int unit);
+  private static native String formatWithAbsoluteUnit(long address, int direction, int unit);
+  private static native String combineDateAndTime(long address, String relativeDateString, String timeString);
+}
diff --git a/luni/src/main/native/Register.cpp b/luni/src/main/native/Register.cpp
index 6a2c939..d8c9d5c 100644
--- a/luni/src/main/native/Register.cpp
+++ b/luni/src/main/native/Register.cpp
@@ -67,6 +67,7 @@
     REGISTER(register_libcore_icu_NativeIDN);
     REGISTER(register_libcore_icu_NativeNormalizer);
     REGISTER(register_libcore_icu_NativePluralRules);
+    REGISTER(register_libcore_icu_RelativeDateTimeFormatter);
     REGISTER(register_libcore_icu_TimeZoneNames);
     REGISTER(register_libcore_icu_Transliterator);
     REGISTER(register_libcore_io_AsynchronousCloseMonitor);
diff --git a/luni/src/main/native/libcore_icu_RelativeDateTimeFormatter.cpp b/luni/src/main/native/libcore_icu_RelativeDateTimeFormatter.cpp
new file mode 100644
index 0000000..bba2b0e
--- /dev/null
+++ b/luni/src/main/native/libcore_icu_RelativeDateTimeFormatter.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "RelativeDateTimeFormatter"
+
+#include "IcuUtilities.h"
+#include "JniConstants.h"
+#include "ScopedIcuLocale.h"
+#include "ScopedJavaUnicodeString.h"
+#include "cutils/log.h"
+#include "unicode/reldatefmt.h"
+
+static jlong RelativeDateTimeFormatter_createRelativeDateTimeFormatter(JNIEnv* env, jclass,
+    jstring javaLocaleName, jint style, jint capitalizationContext) {
+  ScopedIcuLocale icuLocale(env, javaLocaleName);
+  if (!icuLocale.valid()) {
+    return 0;
+  }
+
+  UErrorCode status = U_ZERO_ERROR;
+  RelativeDateTimeFormatter* formatter = new RelativeDateTimeFormatter(
+      icuLocale.locale(), nullptr, static_cast<UDateRelativeDateTimeFormatterStyle>(style),
+      static_cast<UDisplayContext>(capitalizationContext), status);
+  if (maybeThrowIcuException(env, "RelativeDateTimeFormatter::RelativeDateTimeFormatter", status)) {
+    return 0;
+  }
+
+  return reinterpret_cast<uintptr_t>(formatter);
+}
+
+static void RelativeDateTimeFormatter_destroyRelativeDateTimeFormatter(JNIEnv*, jclass,
+    jlong formatterAddress) {
+  delete reinterpret_cast<RelativeDateTimeFormatter*>(static_cast<uintptr_t>(formatterAddress));
+}
+
+static jstring RelativeDateTimeFormatter_formatWithRelativeUnit(JNIEnv* env, jclass,
+    jlong formatterAddress, jint quantity, jint direction, jint unit) {
+  RelativeDateTimeFormatter* formatter(reinterpret_cast<RelativeDateTimeFormatter*>(formatterAddress));
+  UnicodeString s;
+  UErrorCode status = U_ZERO_ERROR;
+  // RelativeDateTimeFormatter::format() takes a double-type quantity.
+  formatter->format(static_cast<double>(quantity), static_cast<UDateDirection>(direction),
+                    static_cast<UDateRelativeUnit>(unit), s, status);
+  if (maybeThrowIcuException(env, "RelativeDateTimeFormatter::format", status)) {
+    return nullptr;
+  }
+
+  return env->NewString(s.getBuffer(), s.length());
+}
+
+static jstring RelativeDateTimeFormatter_formatWithAbsoluteUnit(JNIEnv* env, jclass,
+    jlong formatterAddress, jint direction, jint unit) {
+  RelativeDateTimeFormatter* formatter(reinterpret_cast<RelativeDateTimeFormatter*>(formatterAddress));
+  UnicodeString s;
+  UErrorCode status = U_ZERO_ERROR;
+  formatter->format(static_cast<UDateDirection>(direction), static_cast<UDateAbsoluteUnit>(unit), s, status);
+  if (maybeThrowIcuException(env, "RelativeDateTimeFormatter::format", status)) {
+    return nullptr;
+  }
+
+  return env->NewString(s.getBuffer(), s.length());
+}
+
+static jstring RelativeDateTimeFormatter_combineDateAndTime(JNIEnv* env, jclass,
+    jlong formatterAddress, jstring relativeDateString0, jstring timeString0) {
+  RelativeDateTimeFormatter* formatter(reinterpret_cast<RelativeDateTimeFormatter*>(formatterAddress));
+  ScopedJavaUnicodeString relativeDateString(env, relativeDateString0);
+  if (!relativeDateString.valid()) {
+    return 0;
+  }
+
+  ScopedJavaUnicodeString timeString(env, timeString0);
+  if (!timeString.valid()) {
+    return 0;
+  }
+  UnicodeString s;
+  UErrorCode status = U_ZERO_ERROR;
+  formatter->combineDateAndTime(relativeDateString.unicodeString(), timeString.unicodeString(), s, status);
+  if (maybeThrowIcuException(env, "RelativeDateTimeFormatter::combineDateAndTime", status)) {
+    return nullptr;
+  }
+
+  return env->NewString(s.getBuffer(), s.length());
+}
+
+static JNINativeMethod gMethods[] = {
+  NATIVE_METHOD(RelativeDateTimeFormatter, createRelativeDateTimeFormatter, "(Ljava/lang/String;II)J"),
+  NATIVE_METHOD(RelativeDateTimeFormatter, destroyRelativeDateTimeFormatter, "(J)V"),
+  NATIVE_METHOD(RelativeDateTimeFormatter, formatWithRelativeUnit, "(JIII)Ljava/lang/String;"),
+  NATIVE_METHOD(RelativeDateTimeFormatter, formatWithAbsoluteUnit, "(JII)Ljava/lang/String;"),
+  NATIVE_METHOD(RelativeDateTimeFormatter, combineDateAndTime, "(JLjava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
+};
+
+void register_libcore_icu_RelativeDateTimeFormatter(JNIEnv* env) {
+  jniRegisterNativeMethods(env, "libcore/icu/RelativeDateTimeFormatter", gMethods, NELEM(gMethods));
+}
diff --git a/luni/src/main/native/sub.mk b/luni/src/main/native/sub.mk
index ebd0f59..da866a6 100644
--- a/luni/src/main/native/sub.mk
+++ b/luni/src/main/native/sub.mk
@@ -47,6 +47,7 @@
     libcore_icu_NativeIDN.cpp \
     libcore_icu_NativeNormalizer.cpp \
     libcore_icu_NativePluralRules.cpp \
+    libcore_icu_RelativeDateTimeFormatter.cpp \
     libcore_icu_TimeZoneNames.cpp \
     libcore_icu_Transliterator.cpp \
     libcore_io_AsynchronousCloseMonitor.cpp \
diff --git a/luni/src/test/java/libcore/icu/RelativeDateTimeFormatterTest.java b/luni/src/test/java/libcore/icu/RelativeDateTimeFormatterTest.java
new file mode 100644
index 0000000..07166bf
--- /dev/null
+++ b/luni/src/test/java/libcore/icu/RelativeDateTimeFormatterTest.java
@@ -0,0 +1,611 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.icu;
+
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import static libcore.icu.RelativeDateTimeFormatter.getRelativeDateTimeString;
+import static libcore.icu.RelativeDateTimeFormatter.getRelativeTimeSpanString;
+import static libcore.icu.RelativeDateTimeFormatter.FORMAT_ABBREV_ALL;
+import static libcore.icu.RelativeDateTimeFormatter.FORMAT_ABBREV_RELATIVE;
+import static libcore.icu.RelativeDateTimeFormatter.FORMAT_NUMERIC_DATE;
+import static libcore.icu.RelativeDateTimeFormatter.SECOND_IN_MILLIS;
+import static libcore.icu.RelativeDateTimeFormatter.MINUTE_IN_MILLIS;
+import static libcore.icu.RelativeDateTimeFormatter.HOUR_IN_MILLIS;
+import static libcore.icu.RelativeDateTimeFormatter.DAY_IN_MILLIS;
+import static libcore.icu.RelativeDateTimeFormatter.WEEK_IN_MILLIS;
+import static libcore.icu.RelativeDateTimeFormatter.YEAR_IN_MILLIS;
+
+public class RelativeDateTimeFormatterTest extends junit.framework.TestCase {
+
+  // Tests adopted from CTS tests for DateUtils.getRelativeTimeSpanString.
+  public void test_getRelativeTimeSpanStringCTS() throws Exception {
+    Locale en_US = new Locale("en", "US");
+    TimeZone tz = TimeZone.getTimeZone("GMT");
+    Calendar cal = Calendar.getInstance(tz, en_US);
+    // Feb 5, 2015 at 10:50 GMT
+    cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0);
+    final long baseTime = cal.getTimeInMillis();
+
+    assertEquals("0 minutes ago",
+                 getRelativeTimeSpanString(en_US, tz, baseTime - SECOND_IN_MILLIS, baseTime,
+                                           MINUTE_IN_MILLIS, 0));
+    assertEquals("in 0 minutes",
+                 getRelativeTimeSpanString(en_US, tz, baseTime + SECOND_IN_MILLIS, baseTime,
+                                           MINUTE_IN_MILLIS, 0));
+
+    assertEquals("1 minute ago",
+                 getRelativeTimeSpanString(en_US, tz, 0, MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 0));
+    assertEquals("in 1 minute",
+                 getRelativeTimeSpanString(en_US, tz, MINUTE_IN_MILLIS, 0, MINUTE_IN_MILLIS, 0));
+
+    assertEquals("42 minutes ago",
+      getRelativeTimeSpanString(en_US, tz, baseTime - 42 * MINUTE_IN_MILLIS, baseTime,
+                                MINUTE_IN_MILLIS, 0));
+    assertEquals("in 42 minutes",
+      getRelativeTimeSpanString(en_US, tz, baseTime + 42 * MINUTE_IN_MILLIS, baseTime,
+                                MINUTE_IN_MILLIS, 0));
+
+    final long TWO_HOURS_IN_MS = 2 * HOUR_IN_MILLIS;
+    assertEquals("2 hours ago",
+                 getRelativeTimeSpanString(en_US, tz, baseTime - TWO_HOURS_IN_MS, baseTime,
+                                           MINUTE_IN_MILLIS, FORMAT_NUMERIC_DATE));
+    assertEquals("in 2 hours",
+                 getRelativeTimeSpanString(en_US, tz, baseTime + TWO_HOURS_IN_MS, baseTime,
+                                           MINUTE_IN_MILLIS, FORMAT_NUMERIC_DATE));
+
+    assertEquals("in 42 min.",
+                 getRelativeTimeSpanString(en_US, tz, baseTime + (42 * MINUTE_IN_MILLIS), baseTime,
+                                           MINUTE_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+
+    assertEquals("Tomorrow",
+                 getRelativeTimeSpanString(en_US, tz, DAY_IN_MILLIS, 0, DAY_IN_MILLIS, 0));
+    assertEquals("in 2 days",
+                 getRelativeTimeSpanString(en_US, tz, 2 * DAY_IN_MILLIS, 0, DAY_IN_MILLIS, 0));
+    assertEquals("Yesterday",
+                 getRelativeTimeSpanString(en_US, tz, 0, DAY_IN_MILLIS, DAY_IN_MILLIS, 0));
+    assertEquals("2 days ago",
+                 getRelativeTimeSpanString(en_US, tz, 0, 2 * DAY_IN_MILLIS, DAY_IN_MILLIS, 0));
+
+    final long DAY_DURATION = 5 * 24 * 60 * 60 * 1000;
+    assertEquals("5 days ago",
+                 getRelativeTimeSpanString(en_US, tz, baseTime - DAY_DURATION, baseTime,
+                                           DAY_IN_MILLIS, 0));
+  }
+
+  private void test_getRelativeTimeSpanString_helper(long delta, long minResolution, int flags,
+                                                     String expectedInPast,
+                                                     String expectedInFuture) throws Exception {
+    Locale en_US = new Locale("en", "US");
+    TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
+    Calendar cal = Calendar.getInstance(tz, en_US);
+    // Feb 5, 2015 at 10:50 PST
+    cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0);
+    final long base = cal.getTimeInMillis();
+
+    assertEquals(expectedInPast,
+                 getRelativeTimeSpanString(en_US, tz, base - delta, base, minResolution, flags));
+    assertEquals(expectedInFuture,
+                 getRelativeTimeSpanString(en_US, tz, base + delta, base, minResolution, flags));
+  }
+
+  private void test_getRelativeTimeSpanString_helper(long delta, long minResolution,
+                                                     String expectedInPast,
+                                                     String expectedInFuture) throws Exception {
+    test_getRelativeTimeSpanString_helper(delta, minResolution, 0, expectedInPast, expectedInFuture);
+  }
+
+  public void test_getRelativeTimeSpanString() throws Exception {
+
+    test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, 0, "0 seconds ago", "0 seconds ago");
+    test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, "1 minute ago", "in 1 minute");
+    test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, "1 minute ago", "in 1 minute");
+    test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, 0, "5 days ago", "in 5 days");
+
+    test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "0 seconds ago",
+                                          "0 seconds ago");
+    test_getRelativeTimeSpanString_helper(1 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "1 second ago",
+                                          "in 1 second");
+    test_getRelativeTimeSpanString_helper(2 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "2 seconds ago",
+                                          "in 2 seconds");
+    test_getRelativeTimeSpanString_helper(25 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "25 seconds ago",
+                                          "in 25 seconds");
+    test_getRelativeTimeSpanString_helper(75 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "1 minute ago",
+                                          "in 1 minute");
+    test_getRelativeTimeSpanString_helper(5000 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "1 hour ago",
+                                          "in 1 hour");
+
+    test_getRelativeTimeSpanString_helper(0 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "0 minutes ago",
+                                          "0 minutes ago");
+    test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "1 minute ago",
+                                          "in 1 minute");
+    test_getRelativeTimeSpanString_helper(2 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "2 minutes ago",
+                                          "in 2 minutes");
+    test_getRelativeTimeSpanString_helper(25 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "25 minutes ago",
+                                          "in 25 minutes");
+    test_getRelativeTimeSpanString_helper(75 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "1 hour ago",
+                                          "in 1 hour");
+    test_getRelativeTimeSpanString_helper(720 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "12 hours ago",
+                                          "in 12 hours");
+
+    test_getRelativeTimeSpanString_helper(0 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "0 hours ago",
+                                          "0 hours ago");
+    test_getRelativeTimeSpanString_helper(1 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "1 hour ago",
+                                          "in 1 hour");
+    test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "2 hours ago",
+                                          "in 2 hours");
+    test_getRelativeTimeSpanString_helper(5 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "5 hours ago",
+                                          "in 5 hours");
+    test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "20 hours ago",
+                                          "in 20 hours");
+
+    test_getRelativeTimeSpanString_helper(0 * DAY_IN_MILLIS, DAY_IN_MILLIS, "Today", "Today");
+    test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday",
+                                          "Tomorrow");
+    test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday",
+                                          "Tomorrow");
+    test_getRelativeTimeSpanString_helper(2 * DAY_IN_MILLIS, DAY_IN_MILLIS, "2 days ago",
+                                          "in 2 days");
+    test_getRelativeTimeSpanString_helper(25 * DAY_IN_MILLIS, DAY_IN_MILLIS, "January 11",
+                                          "March 2");
+
+    test_getRelativeTimeSpanString_helper(0 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "0 weeks ago",
+                                          "0 weeks ago");
+    test_getRelativeTimeSpanString_helper(1 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "1 week ago",
+                                          "in 1 week");
+    test_getRelativeTimeSpanString_helper(2 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "2 weeks ago",
+                                          "in 2 weeks");
+    test_getRelativeTimeSpanString_helper(25 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "25 weeks ago",
+                                          "in 25 weeks");
+
+    // duration >= minResolution
+    test_getRelativeTimeSpanString_helper(30 * SECOND_IN_MILLIS, 0, "30 seconds ago",
+                                          "in 30 seconds");
+    test_getRelativeTimeSpanString_helper(30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS,
+                                          "30 minutes ago", "in 30 minutes");
+    test_getRelativeTimeSpanString_helper(30 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, "Yesterday",
+                                          "Tomorrow");
+    test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, "5 days ago",
+                                          "in 5 days");
+    test_getRelativeTimeSpanString_helper(30 * WEEK_IN_MILLIS, MINUTE_IN_MILLIS, "July 10, 2014",
+                                          "September 3");
+    test_getRelativeTimeSpanString_helper(5 * 365 * DAY_IN_MILLIS, MINUTE_IN_MILLIS,
+                                          "February 6, 2010", "February 4, 2020");
+
+    test_getRelativeTimeSpanString_helper(60 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, "1 minute ago",
+                                          "in 1 minute");
+    test_getRelativeTimeSpanString_helper(120 * SECOND_IN_MILLIS - 1, MINUTE_IN_MILLIS,
+                                          "1 minute ago", "in 1 minute");
+    test_getRelativeTimeSpanString_helper(60 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, "1 hour ago",
+                                          "in 1 hour");
+    test_getRelativeTimeSpanString_helper(120 * MINUTE_IN_MILLIS - 1, HOUR_IN_MILLIS, "1 hour ago",
+                                          "in 1 hour");
+    test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Today", "Today");
+    test_getRelativeTimeSpanString_helper(12 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday",
+                                          "Today");
+    test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday",
+                                          "Tomorrow");
+    test_getRelativeTimeSpanString_helper(48 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "2 days ago",
+                                          "in 2 days");
+    test_getRelativeTimeSpanString_helper(45 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "2 days ago",
+                                          "in 2 days");
+    test_getRelativeTimeSpanString_helper(7 * DAY_IN_MILLIS, WEEK_IN_MILLIS, "1 week ago",
+                                          "in 1 week");
+    test_getRelativeTimeSpanString_helper(14 * DAY_IN_MILLIS - 1, WEEK_IN_MILLIS, "1 week ago",
+                                          "in 1 week");
+
+    // duration < minResolution
+    test_getRelativeTimeSpanString_helper(59 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, "0 minutes ago",
+                                          "in 0 minutes");
+    test_getRelativeTimeSpanString_helper(59 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, "0 hours ago",
+                                          "in 0 hours");
+    test_getRelativeTimeSpanString_helper(HOUR_IN_MILLIS - 1, HOUR_IN_MILLIS, "0 hours ago",
+                                          "in 0 hours");
+    test_getRelativeTimeSpanString_helper(DAY_IN_MILLIS - 1, DAY_IN_MILLIS, "Yesterday",
+                                          "Tomorrow");
+    test_getRelativeTimeSpanString_helper(20 * SECOND_IN_MILLIS, WEEK_IN_MILLIS, "0 weeks ago",
+                                          "in 0 weeks");
+    test_getRelativeTimeSpanString_helper(WEEK_IN_MILLIS - 1, WEEK_IN_MILLIS, "0 weeks ago",
+                                          "in 0 weeks");
+  }
+
+  public void test_getRelativeTimeSpanStringAbbrev() throws Exception {
+    int flags = FORMAT_ABBREV_RELATIVE;
+
+    test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, 0, flags, "0 sec. ago",
+                                          "0 sec. ago");
+    test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, flags, "1 min. ago",
+                                          "in 1 min.");
+    test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, 0, flags, "5 days ago", "in 5 days");
+
+    test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags,
+                                          "0 sec. ago", "0 sec. ago");
+    test_getRelativeTimeSpanString_helper(1 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags,
+                                          "1 sec. ago", "in 1 sec.");
+    test_getRelativeTimeSpanString_helper(2 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags,
+                                          "2 sec. ago", "in 2 sec.");
+    test_getRelativeTimeSpanString_helper(25 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags,
+                                          "25 sec. ago", "in 25 sec.");
+    test_getRelativeTimeSpanString_helper(75 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags,
+                                          "1 min. ago", "in 1 min.");
+    test_getRelativeTimeSpanString_helper(5000 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags,
+                                          "1 hr. ago", "in 1 hr.");
+
+    test_getRelativeTimeSpanString_helper(0 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+                                          "0 min. ago", "0 min. ago");
+    test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+                                          "1 min. ago", "in 1 min.");
+    test_getRelativeTimeSpanString_helper(2 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+                                          "2 min. ago", "in 2 min.");
+    test_getRelativeTimeSpanString_helper(25 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+                                          "25 min. ago", "in 25 min.");
+    test_getRelativeTimeSpanString_helper(75 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+                                          "1 hr. ago", "in 1 hr.");
+    test_getRelativeTimeSpanString_helper(720 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+                                          "12 hr. ago", "in 12 hr.");
+
+    test_getRelativeTimeSpanString_helper(0 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags,
+                                          "0 hr. ago", "0 hr. ago");
+    test_getRelativeTimeSpanString_helper(1 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags,
+                                          "1 hr. ago", "in 1 hr.");
+    test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags,
+                                          "2 hr. ago", "in 2 hr.");
+    test_getRelativeTimeSpanString_helper(5 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags,
+                                          "5 hr. ago", "in 5 hr.");
+    test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags,
+                                          "20 hr. ago", "in 20 hr.");
+
+    test_getRelativeTimeSpanString_helper(0 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags, "Today",
+                                          "Today");
+    test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags,
+                                          "Yesterday", "Tomorrow");
+    test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags,
+                                          "Yesterday", "Tomorrow");
+    test_getRelativeTimeSpanString_helper(2 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags,
+                                          "2 days ago", "in 2 days");
+    test_getRelativeTimeSpanString_helper(25 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags,
+                                          "January 11", "March 2");
+
+    test_getRelativeTimeSpanString_helper(0 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags,
+                                          "0 wk. ago", "0 wk. ago");
+    test_getRelativeTimeSpanString_helper(1 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags,
+                                          "1 wk. ago", "in 1 wk.");
+    test_getRelativeTimeSpanString_helper(2 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags,
+                                          "2 wk. ago", "in 2 wk.");
+    test_getRelativeTimeSpanString_helper(25 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags,
+                                          "25 wk. ago", "in 25 wk.");
+
+    // duration >= minResolution
+    test_getRelativeTimeSpanString_helper(30 * SECOND_IN_MILLIS, 0, flags, "30 sec. ago",
+                                          "in 30 sec.");
+    test_getRelativeTimeSpanString_helper(30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+                                          "30 min. ago", "in 30 min.");
+    test_getRelativeTimeSpanString_helper(30 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+                                          "Yesterday", "Tomorrow");
+    test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+                                          "5 days ago", "in 5 days");
+    test_getRelativeTimeSpanString_helper(30 * WEEK_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+                                          "July 10, 2014", "September 3");
+    test_getRelativeTimeSpanString_helper(5 * 365 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+                                          "February 6, 2010", "February 4, 2020");
+
+    test_getRelativeTimeSpanString_helper(60 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+                                          "1 min. ago", "in 1 min.");
+    test_getRelativeTimeSpanString_helper(120 * SECOND_IN_MILLIS - 1, MINUTE_IN_MILLIS, flags,
+                                          "1 min. ago", "in 1 min.");
+    test_getRelativeTimeSpanString_helper(60 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, flags,
+                                          "1 hr. ago", "in 1 hr.");
+    test_getRelativeTimeSpanString_helper(120 * MINUTE_IN_MILLIS - 1, HOUR_IN_MILLIS, flags,
+                                          "1 hr. ago", "in 1 hr.");
+    test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, "Today",
+                                          "Today");
+    test_getRelativeTimeSpanString_helper(12 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags,
+                                          "Yesterday", "Today");
+    test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags,
+                                          "Yesterday", "Tomorrow");
+    test_getRelativeTimeSpanString_helper(48 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags,
+                                          "2 days ago", "in 2 days");
+    test_getRelativeTimeSpanString_helper(45 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags,
+                                          "2 days ago", "in 2 days");
+    test_getRelativeTimeSpanString_helper(7 * DAY_IN_MILLIS, WEEK_IN_MILLIS, flags,
+                                          "1 wk. ago", "in 1 wk.");
+    test_getRelativeTimeSpanString_helper(14 * DAY_IN_MILLIS - 1, WEEK_IN_MILLIS, flags,
+                                          "1 wk. ago", "in 1 wk.");
+
+    // duration < minResolution
+    test_getRelativeTimeSpanString_helper(59 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, flags,
+                                          "0 min. ago", "in 0 min.");
+    test_getRelativeTimeSpanString_helper(59 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, flags,
+                                          "0 hr. ago", "in 0 hr.");
+    test_getRelativeTimeSpanString_helper(HOUR_IN_MILLIS - 1, HOUR_IN_MILLIS, flags,
+                                          "0 hr. ago", "in 0 hr.");
+    test_getRelativeTimeSpanString_helper(DAY_IN_MILLIS - 1, DAY_IN_MILLIS, flags,
+                                          "Yesterday", "Tomorrow");
+    test_getRelativeTimeSpanString_helper(20 * SECOND_IN_MILLIS, WEEK_IN_MILLIS, flags,
+                                          "0 wk. ago", "in 0 wk.");
+    test_getRelativeTimeSpanString_helper(WEEK_IN_MILLIS - 1, WEEK_IN_MILLIS, flags,
+                                          "0 wk. ago", "in 0 wk.");
+
+  }
+
+  public void test_getRelativeTimeSpanStringGerman() throws Exception {
+    Locale de_DE = new Locale("de", "DE");
+    final long now = System.currentTimeMillis();
+    TimeZone tz = TimeZone.getDefault();
+
+    // 42 minutes ago
+    assertEquals("vor 42 Minuten",
+      getRelativeTimeSpanString(de_DE, tz, now - 42 * MINUTE_IN_MILLIS, now,
+                                MINUTE_IN_MILLIS, 0));
+    // in 42 minutes
+    assertEquals("in 42 Minuten",
+      getRelativeTimeSpanString(de_DE, tz, now + 42 * MINUTE_IN_MILLIS, now,
+                                MINUTE_IN_MILLIS, 0));
+    // yesterday
+    assertEquals("Gestern",
+                 getRelativeTimeSpanString(de_DE, tz, now - DAY_IN_MILLIS, now,
+                                           DAY_IN_MILLIS, 0));
+    // the day before yesterday
+    assertEquals("Vorgestern",
+                 getRelativeTimeSpanString(de_DE, tz, now - 2 * DAY_IN_MILLIS, now,
+                                           DAY_IN_MILLIS, 0));
+    // tomorrow
+    assertEquals("Morgen",
+                 getRelativeTimeSpanString(de_DE, tz, now + DAY_IN_MILLIS, now,
+                                           DAY_IN_MILLIS, 0));
+    // the day after tomorrow
+    assertEquals("Übermorgen",
+                 getRelativeTimeSpanString(de_DE, tz, now + 2 * DAY_IN_MILLIS, now,
+                                           DAY_IN_MILLIS, 0));
+  }
+
+  public void test_getRelativeTimeSpanStringFrench() throws Exception {
+    Locale fr_FR = new Locale("fr", "FR");
+    final long now = System.currentTimeMillis();
+    TimeZone tz = TimeZone.getDefault();
+
+    // 42 minutes ago
+    assertEquals("il y a 42 minutes",
+                 getRelativeTimeSpanString(fr_FR, tz, now - (42 * MINUTE_IN_MILLIS), now,
+                                           MINUTE_IN_MILLIS, 0));
+    // in 42 minutes
+    assertEquals("dans 42 minutes",
+                 getRelativeTimeSpanString(fr_FR, tz, now + (42 * MINUTE_IN_MILLIS), now,
+                                           MINUTE_IN_MILLIS, 0));
+    // yesterday
+    assertEquals("Hier",
+                 getRelativeTimeSpanString(fr_FR, tz, now - DAY_IN_MILLIS, now,
+                                           DAY_IN_MILLIS, 0));
+    // the day before yesterday
+    assertEquals("Avant-hier",
+                 getRelativeTimeSpanString(fr_FR, tz, now - 2 * DAY_IN_MILLIS, now,
+                                           DAY_IN_MILLIS, 0));
+    // tomorrow
+    assertEquals("Demain",
+                 getRelativeTimeSpanString(fr_FR, tz, now + DAY_IN_MILLIS, now,
+                                           DAY_IN_MILLIS, 0));
+    // the day after tomorrow
+    assertEquals("Après-demain",
+                 getRelativeTimeSpanString(fr_FR, tz, now + 2 * DAY_IN_MILLIS, now,
+                                           DAY_IN_MILLIS, 0));
+  }
+
+  // Tests adopted from CTS tests for DateUtils.getRelativeDateTimeString.
+  public void test_getRelativeDateTimeStringCTS() throws Exception {
+    Locale en_US = Locale.getDefault();
+    TimeZone tz = TimeZone.getDefault();
+    final long baseTime = System.currentTimeMillis();
+
+    final long DAY_DURATION = 5 * 24 * 60 * 60 * 1000;
+    assertNotNull(getRelativeDateTimeString(en_US, tz, baseTime - DAY_DURATION, baseTime,
+                                            MINUTE_IN_MILLIS, DAY_IN_MILLIS,
+                                            FORMAT_NUMERIC_DATE));
+  }
+
+  public void test_getRelativeDateTimeString() throws Exception {
+    Locale en_US = new Locale("en", "US");
+    TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
+    Calendar cal = Calendar.getInstance(tz, en_US);
+    // Feb 5, 2015 at 10:50 PST
+    cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0);
+    final long base = cal.getTimeInMillis();
+
+    assertEquals("5 seconds ago, 10:49 AM",
+                 getRelativeDateTimeString(en_US, tz, base - 5 * SECOND_IN_MILLIS, base, 0,
+                                           MINUTE_IN_MILLIS, 0));
+    assertEquals("5 min. ago, 10:45 AM",
+                 getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base, 0,
+                                           HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+    assertEquals("0 hr. ago, 10:45 AM",
+                 getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base,
+                                           HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+    assertEquals("5 hours ago, 5:50 AM",
+                 getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base,
+                                           HOUR_IN_MILLIS, DAY_IN_MILLIS, 0));
+    assertEquals("Yesterday, 7:50 PM",
+                 getRelativeDateTimeString(en_US, tz, base - 15 * HOUR_IN_MILLIS, base, 0,
+                                           WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+    assertEquals("5 days ago, 10:50 AM",
+                 getRelativeDateTimeString(en_US, tz, base - 5 * DAY_IN_MILLIS, base, 0,
+                                           WEEK_IN_MILLIS, 0));
+    assertEquals("Jan 29, 10:50 AM",
+                 getRelativeDateTimeString(en_US, tz, base - 7 * DAY_IN_MILLIS, base, 0,
+                                           WEEK_IN_MILLIS, 0));
+    assertEquals("11/27/2014, 10:50 AM",
+                 getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
+                                           WEEK_IN_MILLIS, 0));
+    assertEquals("11/27/2014, 10:50 AM",
+                 getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
+                                           YEAR_IN_MILLIS, 0));
+
+    // User-supplied flags should be ignored when formatting the date clause.
+    final int FORMAT_SHOW_WEEKDAY = 0x00002;
+    assertEquals("11/27/2014, 10:50 AM",
+                 getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
+                                           WEEK_IN_MILLIS,
+                                           FORMAT_ABBREV_ALL | FORMAT_SHOW_WEEKDAY));
+  }
+
+  public void test_getRelativeDateTimeStringDST() throws Exception {
+    Locale en_US = new Locale("en", "US");
+    TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
+    Calendar cal = Calendar.getInstance(tz, en_US);
+
+    // DST starts on Mar 9, 2014 at 2:00 AM.
+    // So 5 hours before 3:15 AM should be formatted as 'Yesterday, 9:15 PM'.
+    cal.set(2014, Calendar.MARCH, 9, 3, 15, 0);
+    long base = cal.getTimeInMillis();
+    assertEquals("Yesterday, 9:15 PM",
+                 getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base, 0,
+                                           WEEK_IN_MILLIS, 0));
+
+    // 1 hour after 2:00 AM should be formatted as 'in 1 hour, 4:00 AM'.
+    cal.set(2014, Calendar.MARCH, 9, 2, 0, 0);
+    base = cal.getTimeInMillis();
+    assertEquals("in 1 hour, 4:00 AM",
+                 getRelativeDateTimeString(en_US, tz, base + 1 * HOUR_IN_MILLIS, base, 0,
+                                           WEEK_IN_MILLIS, 0));
+
+    // DST ends on Nov 2, 2014 at 2:00 AM. Clocks are turned backward 1 hour to
+    // 1:00 AM. 8 hours before 5:20 AM should be 'Yesterday, 10:20 PM'.
+    cal.set(2014, Calendar.NOVEMBER, 2, 5, 20, 0);
+    base = cal.getTimeInMillis();
+    assertEquals("Yesterday, 10:20 PM",
+                 getRelativeDateTimeString(en_US, tz, base - 8 * HOUR_IN_MILLIS, base, 0,
+                                           WEEK_IN_MILLIS, 0));
+
+    cal.set(2014, Calendar.NOVEMBER, 2, 0, 45, 0);
+    base = cal.getTimeInMillis();
+    // 45 minutes after 0:45 AM should be 'in 45 minutes, 1:30 AM'.
+    assertEquals("in 45 minutes, 1:30 AM",
+                 getRelativeDateTimeString(en_US, tz, base + 45 * MINUTE_IN_MILLIS, base, 0,
+                                           WEEK_IN_MILLIS, 0));
+    // 45 minutes later, it should be 'in 45 minutes, 1:15 AM'.
+    assertEquals("in 45 minutes, 1:15 AM",
+                 getRelativeDateTimeString(en_US, tz, base + 90 * MINUTE_IN_MILLIS,
+                                           base + 45 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0));
+    // Another 45 minutes later, it should be 'in 45 minutes, 2:00 AM'.
+    assertEquals("in 45 minutes, 2:00 AM",
+                 getRelativeDateTimeString(en_US, tz, base + 135 * MINUTE_IN_MILLIS,
+                                           base + 90 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0));
+  }
+
+
+  public void test_getRelativeDateTimeStringItalian() throws Exception {
+    Locale it_IT = new Locale("it", "IT");
+    TimeZone tz = TimeZone.getTimeZone("Europe/Rome");
+    Calendar cal = Calendar.getInstance(tz, it_IT);
+    // 05 febbraio 2015 20:15
+    cal.set(2015, Calendar.FEBRUARY, 5, 20, 15, 0);
+    final long base = cal.getTimeInMillis();
+
+    assertEquals("5 secondi fa, 20:14",
+                 getRelativeDateTimeString(it_IT, tz, base - 5 * SECOND_IN_MILLIS, base, 0,
+                                           MINUTE_IN_MILLIS, 0));
+    assertEquals("5 min. fa, 20:10",
+                 getRelativeDateTimeString(it_IT, tz, base - 5 * MINUTE_IN_MILLIS, base, 0,
+                                           HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+    assertEquals("0 h. fa, 20:10",
+                 getRelativeDateTimeString(it_IT, tz, base - 5 * MINUTE_IN_MILLIS, base,
+                                           HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+    assertEquals("Ieri, 22:15",
+                 getRelativeDateTimeString(it_IT, tz, base - 22 * HOUR_IN_MILLIS, base, 0,
+                                           WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
+    assertEquals("5 giorni fa, 20:15",
+                 getRelativeDateTimeString(it_IT, tz, base - 5 * DAY_IN_MILLIS, base, 0,
+                                           WEEK_IN_MILLIS, 0));
+    assertEquals("27/11/2014, 20:15",
+                 getRelativeDateTimeString(it_IT, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
+                                           WEEK_IN_MILLIS, 0));
+  }
+
+  // http://b/5252772: detect the actual date difference
+  public void test5252772() throws Exception {
+    Locale en_US = new Locale("en", "US");
+    TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
+
+    // Now is Sep 2, 2011, 10:23 AM PDT.
+    Calendar nowCalendar = Calendar.getInstance(tz, en_US);
+    nowCalendar.set(2011, Calendar.SEPTEMBER, 2, 10, 23, 0);
+    final long now = nowCalendar.getTimeInMillis();
+
+    // Sep 1, 2011, 10:24 AM
+    Calendar yesterdayCalendar1 = Calendar.getInstance(tz, en_US);
+    yesterdayCalendar1.set(2011, Calendar.SEPTEMBER, 1, 10, 24, 0);
+    long yesterday1 = yesterdayCalendar1.getTimeInMillis();
+    assertEquals("Yesterday, 10:24 AM",
+                 getRelativeDateTimeString(en_US, tz, yesterday1, now, MINUTE_IN_MILLIS,
+                                           WEEK_IN_MILLIS, 0));
+
+    // Sep 1, 2011, 10:22 AM
+    Calendar yesterdayCalendar2 = Calendar.getInstance(tz, en_US);
+    yesterdayCalendar2.set(2011, Calendar.SEPTEMBER, 1, 10, 22, 0);
+    long yesterday2 = yesterdayCalendar2.getTimeInMillis();
+    assertEquals("Yesterday, 10:22 AM",
+                 getRelativeDateTimeString(en_US, tz, yesterday2, now, MINUTE_IN_MILLIS,
+                                           WEEK_IN_MILLIS, 0));
+
+    // Aug 31, 2011, 10:24 AM
+    Calendar twoDaysAgoCalendar1 = Calendar.getInstance(tz, en_US);
+    twoDaysAgoCalendar1.set(2011, Calendar.AUGUST, 31, 10, 24, 0);
+    long twoDaysAgo1 = twoDaysAgoCalendar1.getTimeInMillis();
+    assertEquals("2 days ago, 10:24 AM",
+                 getRelativeDateTimeString(en_US, tz, twoDaysAgo1, now, MINUTE_IN_MILLIS,
+                                           WEEK_IN_MILLIS, 0));
+
+    // Aug 31, 2011, 10:22 AM
+    Calendar twoDaysAgoCalendar2 = Calendar.getInstance(tz, en_US);
+    twoDaysAgoCalendar2.set(2011, Calendar.AUGUST, 31, 10, 22, 0);
+    long twoDaysAgo2 = twoDaysAgoCalendar2.getTimeInMillis();
+    assertEquals("2 days ago, 10:22 AM",
+                 getRelativeDateTimeString(en_US, tz, twoDaysAgo2, now, MINUTE_IN_MILLIS,
+                                           WEEK_IN_MILLIS, 0));
+
+    // Sep 3, 2011, 10:22 AM
+    Calendar tomorrowCalendar1 = Calendar.getInstance(tz, en_US);
+    tomorrowCalendar1.set(2011, Calendar.SEPTEMBER, 3, 10, 22, 0);
+    long tomorrow1 = tomorrowCalendar1.getTimeInMillis();
+    assertEquals("Tomorrow, 10:22 AM",
+                 getRelativeDateTimeString(en_US, tz, tomorrow1, now, MINUTE_IN_MILLIS,
+                                           WEEK_IN_MILLIS, 0));
+
+    // Sep 3, 2011, 10:24 AM
+    Calendar tomorrowCalendar2 = Calendar.getInstance(tz, en_US);
+    tomorrowCalendar2.set(2011, Calendar.SEPTEMBER, 3, 10, 24, 0);
+    long tomorrow2 = tomorrowCalendar2.getTimeInMillis();
+    assertEquals("Tomorrow, 10:24 AM",
+                 getRelativeDateTimeString(en_US, tz, tomorrow2, now, MINUTE_IN_MILLIS,
+                                           WEEK_IN_MILLIS, 0));
+
+    // Sep 4, 2011, 10:22 AM
+    Calendar twoDaysLaterCalendar1 = Calendar.getInstance(tz, en_US);
+    twoDaysLaterCalendar1.set(2011, Calendar.SEPTEMBER, 4, 10, 22, 0);
+    long twoDaysLater1 = twoDaysLaterCalendar1.getTimeInMillis();
+    assertEquals("in 2 days, 10:22 AM",
+                 getRelativeDateTimeString(en_US, tz, twoDaysLater1, now, MINUTE_IN_MILLIS,
+                                           WEEK_IN_MILLIS, 0));
+
+    // Sep 4, 2011, 10:24 AM
+    Calendar twoDaysLaterCalendar2 = Calendar.getInstance(tz, en_US);
+    twoDaysLaterCalendar2.set(2011, Calendar.SEPTEMBER, 4, 10, 24, 0);
+    long twoDaysLater2 = twoDaysLaterCalendar2.getTimeInMillis();
+    assertEquals("in 2 days, 10:24 AM",
+                 getRelativeDateTimeString(en_US, tz, twoDaysLater2, now, MINUTE_IN_MILLIS,
+                                           WEEK_IN_MILLIS, 0));
+  }
+}
diff --git a/luni/src/test/java/libcore/java/util/TimeZoneTest.java b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
index 4cda676..4f2e38c 100644
--- a/luni/src/test/java/libcore/java/util/TimeZoneTest.java
+++ b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
@@ -261,14 +261,6 @@
         assertEquals("", failures.toString());
     }
 
-    public void testSantiago() throws Exception {
-        TimeZone tz = TimeZone.getTimeZone("America/Santiago");
-        assertEquals("Chile Summer Time", tz.getDisplayName(true, TimeZone.LONG, Locale.US));
-        assertEquals("Chile Standard Time", tz.getDisplayName(false, TimeZone.LONG, Locale.US));
-        assertEquals("GMT-03:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.US));
-        assertEquals("GMT-04:00", tz.getDisplayName(false, TimeZone.SHORT, Locale.US));
-    }
-
     // http://b/7955614
     public void testApia() throws Exception {
         TimeZone tz = TimeZone.getTimeZone("Pacific/Apia");