summaryrefslogtreecommitdiffstats
path: root/luni/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'luni/src/main')
-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
5 files changed, 492 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 \