summaryrefslogtreecommitdiffstats
path: root/luni
diff options
context:
space:
mode:
authorTao Bao <tbao@google.com>2015-02-06 15:03:01 -0800
committerTao Bao <tbao@google.com>2015-02-11 10:22:32 -0800
commit7bcff480531c1aa18de118c6f36dd397d5e1ad86 (patch)
tree669efcffdeaf3df1873c9672f2f6fa62ffc625c1 /luni
parenta9c24ad83f3cd18738268559169ba901b5f70232 (diff)
downloadlibcore-7bcff480531c1aa18de118c6f36dd397d5e1ad86.zip
libcore-7bcff480531c1aa18de118c6f36dd397d5e1ad86.tar.gz
libcore-7bcff480531c1aa18de118c6f36dd397d5e1ad86.tar.bz2
Use ICU for relative time formatting
Rewrite the DateUtils' relative time formatting APIs (getRelativeTimeSpanString, getRelativeDateTimeString) to use ICU ones. Two APIs that take withPreposition parameter are not changed. Because (a) ICU doesn't provide functionality to format preposition; (b) They are not really computing relative time but instead calling formatDateRange() to get the absolute time/date string. Benchmark results on aosp_hammerhead-userdebug: before: benchmark us linear runtime DateUtils_getRelativeDateTimeString 127.1 ========================== DateUtils_getRelativeDateTimeString_ABBREV 145.0 ============================== DateUtils_getRelativeTimeSpanString 28.0 ===== DateUtils_getRelativeTimeSpanString_ABBREV 27.9 ===== now: benchmark us linear runtime RelativeDateTimeFormatter_getRelativeDateTimeString 119.2 ========================== RelativeDateTimeFormatter_getRelativeDateTimeString_ABBREV 133.8 ============================== RelativeDateTimeFormatter_getRelativeTimeSpanString 24.6 ===== RelativeDateTimeFormatter_getRelativeTimeSpanString_ABBREV 24.7 ===== Bug: 19146457 Bug: 5252772 Change-Id: Ief74608354964a17e42191d7b1a58964f3a9acfd
Diffstat (limited to 'luni')
-rw-r--r--luni/src/main/java/libcore/icu/DateIntervalFormat.java12
-rw-r--r--luni/src/main/java/libcore/icu/RelativeDateTimeFormatter.java370
-rw-r--r--luni/src/main/native/Register.cpp1
-rw-r--r--luni/src/main/native/libcore_icu_RelativeDateTimeFormatter.cpp109
-rw-r--r--luni/src/main/native/sub.mk1
-rw-r--r--luni/src/test/java/libcore/icu/RelativeDateTimeFormatterTest.java611
6 files changed, 1103 insertions, 1 deletions
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 @@ public final class DateIntervalFormat {
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 @@ jint JNI_OnLoad(JavaVM* vm, void*) {
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 @@ LOCAL_SRC_FILES := \
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));
+ }
+}