summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--benchmarks/src/benchmarks/regression/RelativeDateTimeFormatterBenchmark.java68
-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
7 files changed, 1171 insertions, 1 deletions
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/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));
+ }
+}