From cdf1423650017b9269069574247b89e3b74c4728 Mon Sep 17 00:00:00 2001 From: Victor Chang Date: Tue, 7 Jul 2020 17:41:20 +0100 Subject: [PATCH 1/2] Copy some libcore.icu classes to android.text.format Bug: 160606356 Test: N/A Change-Id: I408ed56e240d2379fb49a52a19f134d83a2b601f --- .../android/text/format/DateTimeFormat.java | 59 ++ .../android/text/format/DateUtilsBridge.java | 188 +++++ .../format/RelativeDateTimeFormatter.java | 362 +++++++++ .../format/RelativeDateTimeFormatterTest.java | 712 ++++++++++++++++++ 4 files changed, 1321 insertions(+) create mode 100644 core/java/android/text/format/DateTimeFormat.java create mode 100644 core/java/android/text/format/DateUtilsBridge.java create mode 100644 core/java/android/text/format/RelativeDateTimeFormatter.java create mode 100644 core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java diff --git a/core/java/android/text/format/DateTimeFormat.java b/core/java/android/text/format/DateTimeFormat.java new file mode 100644 index 0000000000000..20c4f4c6c4acb --- /dev/null +++ b/core/java/android/text/format/DateTimeFormat.java @@ -0,0 +1,59 @@ +/* + * 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 android.text.format; + +import android.icu.text.DateFormat; +import android.icu.text.DateTimePatternGenerator; +import android.icu.text.DisplayContext; +import android.icu.text.SimpleDateFormat; +import android.icu.util.Calendar; +import android.icu.util.ULocale; + +import libcore.util.BasicLruCache; + +/** + * A formatter that outputs a single date/time. + */ +public class DateTimeFormat { + private static final libcore.icu.DateTimeFormat.FormatterCache + CACHED_FORMATTERS = new libcore.icu.DateTimeFormat.FormatterCache(); + + static class FormatterCache extends BasicLruCache { + FormatterCache() { + super(8); + } + } + + private DateTimeFormat() { + } + + public static String format(ULocale icuLocale, Calendar time, int flags, + DisplayContext displayContext) { + String skeleton = DateUtilsBridge.toSkeleton(time, flags); + String key = skeleton + "\t" + icuLocale + "\t" + time.getTimeZone(); + synchronized(CACHED_FORMATTERS) { + DateFormat formatter = CACHED_FORMATTERS.get(key); + if (formatter == null) { + DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(icuLocale); + formatter = new SimpleDateFormat(generator.getBestPattern(skeleton), icuLocale); + CACHED_FORMATTERS.put(key, formatter); + } + formatter.setContext(displayContext); + return formatter.format(time); + } + } +} diff --git a/core/java/android/text/format/DateUtilsBridge.java b/core/java/android/text/format/DateUtilsBridge.java new file mode 100644 index 0000000000000..8701f5cb38e96 --- /dev/null +++ b/core/java/android/text/format/DateUtilsBridge.java @@ -0,0 +1,188 @@ +/* + * 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 android.text.format; + +import android.icu.util.Calendar; +import android.icu.util.GregorianCalendar; +import android.icu.util.TimeZone; +import android.icu.util.ULocale; + +/** + * Common methods and constants for the various ICU formatters used to support + * {@link android.text.format.DateUtils}. + * @hide + */ +final class DateUtilsBridge { + // These are all public API in DateUtils. There are others, but they're either for use with + // other methods (like FORMAT_ABBREV_RELATIVE), don't internationalize (like FORMAT_CAP_AMPM), + // or have never been implemented anyway. + public static final int FORMAT_SHOW_TIME = 0x00001; + public static final int FORMAT_SHOW_WEEKDAY = 0x00002; + public static final int FORMAT_SHOW_YEAR = 0x00004; + public static final int FORMAT_NO_YEAR = 0x00008; + public static final int FORMAT_SHOW_DATE = 0x00010; + public static final int FORMAT_NO_MONTH_DAY = 0x00020; + public static final int FORMAT_12HOUR = 0x00040; + public static final int FORMAT_24HOUR = 0x00080; + public static final int FORMAT_UTC = 0x02000; + public static final int FORMAT_ABBREV_TIME = 0x04000; + public static final int FORMAT_ABBREV_WEEKDAY = 0x08000; + 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; + + /** + * Creates an immutable ICU timezone backed by the specified libcore timezone data. At the time of + * writing the libcore implementation is faster but restricted to 1902 - 2038. + * Callers must not modify the {@code tz} after calling this method. + */ + public static android.icu.util.TimeZone icuTimeZone(java.util.TimeZone tz) { + android.icu.util.TimeZone icuTimeZone = TimeZone.getTimeZone(tz.getID()); + icuTimeZone.freeze(); // Optimization - allows the timezone to be copied cheaply. + return icuTimeZone; + } + + public static Calendar createIcuCalendar(android.icu.util.TimeZone icuTimeZone, ULocale icuLocale, + long timeInMillis) { + Calendar calendar = new GregorianCalendar(icuTimeZone, icuLocale); + calendar.setTimeInMillis(timeInMillis); + return calendar; + } + + public static String toSkeleton(Calendar calendar, int flags) { + return toSkeleton(calendar, calendar, flags); + } + + public static String toSkeleton(Calendar startCalendar, Calendar endCalendar, int flags) { + if ((flags & FORMAT_ABBREV_ALL) != 0) { + flags |= FORMAT_ABBREV_MONTH | FORMAT_ABBREV_TIME | FORMAT_ABBREV_WEEKDAY; + } + + String monthPart = "MMMM"; + if ((flags & FORMAT_NUMERIC_DATE) != 0) { + monthPart = "M"; + } else if ((flags & FORMAT_ABBREV_MONTH) != 0) { + monthPart = "MMM"; + } + + String weekPart = "EEEE"; + if ((flags & FORMAT_ABBREV_WEEKDAY) != 0) { + weekPart = "EEE"; + } + + String timePart = "j"; // "j" means choose 12 or 24 hour based on current locale. + if ((flags & FORMAT_24HOUR) != 0) { + timePart = "H"; + } else if ((flags & FORMAT_12HOUR) != 0) { + timePart = "h"; + } + + // If we've not been asked to abbreviate times, or we're using the 24-hour clock (where it + // never makes sense to leave out the minutes), include minutes. This gets us times like + // "4 PM" while avoiding times like "16" (for "16:00"). + if ((flags & FORMAT_ABBREV_TIME) == 0 || (flags & FORMAT_24HOUR) != 0) { + timePart += "m"; + } else { + // Otherwise, we're abbreviating a 12-hour time, and should only show the minutes + // if they're not both "00". + if (!(onTheHour(startCalendar) && onTheHour(endCalendar))) { + timePart = timePart + "m"; + } + } + + if (fallOnDifferentDates(startCalendar, endCalendar)) { + flags |= FORMAT_SHOW_DATE; + } + + if (fallInSameMonth(startCalendar, endCalendar) && (flags & FORMAT_NO_MONTH_DAY) != 0) { + flags &= (~FORMAT_SHOW_WEEKDAY); + flags &= (~FORMAT_SHOW_TIME); + } + + if ((flags & (FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_SHOW_WEEKDAY)) == 0) { + flags |= FORMAT_SHOW_DATE; + } + + // If we've been asked to show the date, work out whether we think we should show the year. + if ((flags & FORMAT_SHOW_DATE) != 0) { + if ((flags & FORMAT_SHOW_YEAR) != 0) { + // The caller explicitly wants us to show the year. + } else if ((flags & FORMAT_NO_YEAR) != 0) { + // The caller explicitly doesn't want us to show the year, even if we otherwise would. + } else if (!fallInSameYear(startCalendar, endCalendar) || !isThisYear(startCalendar)) { + flags |= FORMAT_SHOW_YEAR; + } + } + + StringBuilder builder = new StringBuilder(); + if ((flags & (FORMAT_SHOW_DATE | FORMAT_NO_MONTH_DAY)) != 0) { + if ((flags & FORMAT_SHOW_YEAR) != 0) { + builder.append("y"); + } + builder.append(monthPart); + if ((flags & FORMAT_NO_MONTH_DAY) == 0) { + builder.append("d"); + } + } + if ((flags & FORMAT_SHOW_WEEKDAY) != 0) { + builder.append(weekPart); + } + if ((flags & FORMAT_SHOW_TIME) != 0) { + builder.append(timePart); + } + return builder.toString(); + } + + public static int dayDistance(Calendar c1, Calendar c2) { + return c2.get(Calendar.JULIAN_DAY) - c1.get(Calendar.JULIAN_DAY); + } + + /** + * Returns whether the argument will be displayed as if it were midnight, using any of the + * skeletons provided by {@link #toSkeleton}. + */ + public static boolean isDisplayMidnightUsingSkeleton(Calendar c) { + // All the skeletons returned by toSkeleton have minute precision (they may abbreviate 4:00 PM + // to 4 PM but will still show the following minute as 4:01 PM). + return c.get(Calendar.HOUR_OF_DAY) == 0 && c.get(Calendar.MINUTE) == 0; + } + + private static boolean onTheHour(Calendar c) { + return c.get(Calendar.MINUTE) == 0 && c.get(Calendar.SECOND) == 0; + } + + private static boolean fallOnDifferentDates(Calendar c1, Calendar c2) { + return c1.get(Calendar.YEAR) != c2.get(Calendar.YEAR) || + c1.get(Calendar.MONTH) != c2.get(Calendar.MONTH) || + c1.get(Calendar.DAY_OF_MONTH) != c2.get(Calendar.DAY_OF_MONTH); + } + + private static boolean fallInSameMonth(Calendar c1, Calendar c2) { + return c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH); + } + + private static boolean fallInSameYear(Calendar c1, Calendar c2) { + return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR); + } + + private static boolean isThisYear(Calendar c) { + Calendar now = (Calendar) c.clone(); + now.setTimeInMillis(System.currentTimeMillis()); + return c.get(Calendar.YEAR) == now.get(Calendar.YEAR); + } +} diff --git a/core/java/android/text/format/RelativeDateTimeFormatter.java b/core/java/android/text/format/RelativeDateTimeFormatter.java new file mode 100644 index 0000000000000..9c52431128651 --- /dev/null +++ b/core/java/android/text/format/RelativeDateTimeFormatter.java @@ -0,0 +1,362 @@ +/* + * 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 android.text.format; + +import java.util.Locale; +import libcore.util.BasicLruCache; + +import android.icu.text.DisplayContext; +import android.icu.util.Calendar; +import android.icu.util.ULocale; + +import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_ALL; +import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_MONTH; +import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_RELATIVE; +import static android.text.format.DateUtilsBridge.FORMAT_NO_YEAR; +import static android.text.format.DateUtilsBridge.FORMAT_NUMERIC_DATE; +import static android.text.format.DateUtilsBridge.FORMAT_SHOW_DATE; +import static android.text.format.DateUtilsBridge.FORMAT_SHOW_TIME; +import static android.text.format.DateUtilsBridge.FORMAT_SHOW_YEAR; + +/** + * Exposes icu4j's RelativeDateTimeFormatter. + * + * @hide + */ +final class RelativeDateTimeFormatter { + + 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; + + private static final int DAY_IN_MS = 24 * 60 * 60 * 1000; + private static final int EPOCH_JULIAN_DAY = 2440588; + + private static final FormatterCache CACHED_FORMATTERS = new FormatterCache(); + + static class FormatterCache + extends BasicLruCache { + FormatterCache() { + super(8); + } + } + + 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 icu4j'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, java.util.TimeZone tz, long time, + long now, long minResolution, int flags) { + // Android has been inconsistent about capitalization in the past. e.g. bug http://b/20247811. + // Now we capitalize everything consistently. + final DisplayContext displayContext = DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE; + return getRelativeTimeSpanString(locale, tz, time, now, minResolution, flags, displayContext); + } + + public static String getRelativeTimeSpanString(Locale locale, java.util.TimeZone tz, long time, + long now, long minResolution, int flags, DisplayContext displayContext) { + if (locale == null) { + throw new NullPointerException("locale == null"); + } + if (tz == null) { + throw new NullPointerException("tz == null"); + } + ULocale icuLocale = ULocale.forLocale(locale); + android.icu.util.TimeZone icuTimeZone = DateUtilsBridge.icuTimeZone(tz); + return getRelativeTimeSpanString(icuLocale, icuTimeZone, time, now, minResolution, flags, + displayContext); + } + + private static String getRelativeTimeSpanString(ULocale icuLocale, + android.icu.util.TimeZone icuTimeZone, long time, long now, long minResolution, int flags, + DisplayContext displayContext) { + + long duration = Math.abs(now - time); + boolean past = (now >= time); + + android.icu.text.RelativeDateTimeFormatter.Style style; + if ((flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0) { + style = android.icu.text.RelativeDateTimeFormatter.Style.SHORT; + } else { + style = android.icu.text.RelativeDateTimeFormatter.Style.LONG; + } + + android.icu.text.RelativeDateTimeFormatter.Direction direction; + if (past) { + direction = android.icu.text.RelativeDateTimeFormatter.Direction.LAST; + } else { + direction = android.icu.text.RelativeDateTimeFormatter.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; + android.icu.text.RelativeDateTimeFormatter.RelativeUnit unit; + android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit aunit = null; + + if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) { + count = (int)(duration / SECOND_IN_MILLIS); + unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.SECONDS; + } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) { + count = (int)(duration / MINUTE_IN_MILLIS); + unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.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 = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.HOURS; + } else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) { + count = Math.abs(dayDistance(icuTimeZone, time, now)); + unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.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", + // icu4j returns an empty string instead of falling back to strings + // like "2 days ago". + String str; + if (past) { + synchronized (CACHED_FORMATTERS) { + str = getFormatter(icuLocale, style, displayContext) + .format( + android.icu.text.RelativeDateTimeFormatter.Direction.LAST_2, + android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY); + } + } else { + synchronized (CACHED_FORMATTERS) { + str = getFormatter(icuLocale, style, displayContext) + .format( + android.icu.text.RelativeDateTimeFormatter.Direction.NEXT_2, + android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY); + } + } + if (str != null && !str.isEmpty()) { + return str; + } + // Fall back to show something like "2 days ago". + } else if (count == 1) { + // Show "Yesterday / Tomorrow" instead of "1 day ago / In 1 day". + aunit = android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY; + relative = false; + } else if (count == 0) { + // Show "Today" if time and now are on the same day. + aunit = android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY; + direction = android.icu.text.RelativeDateTimeFormatter.Direction.THIS; + relative = false; + } + } else if (minResolution == WEEK_IN_MILLIS) { + count = (int)(duration / WEEK_IN_MILLIS); + unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.WEEKS; + } else { + Calendar timeCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, time); + // The duration is longer than a week and minResolution is not + // WEEK_IN_MILLIS. Return the absolute date instead of relative time. + + // Bug 19822016: + // If user doesn't supply the year display flag, we need to explicitly + // set that to show / hide the year based on time and now. Otherwise + // formatDateRange() would determine that based on the current system + // time and may give wrong results. + if ((flags & (FORMAT_NO_YEAR | FORMAT_SHOW_YEAR)) == 0) { + Calendar nowCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, now); + + if (timeCalendar.get(Calendar.YEAR) != nowCalendar.get(Calendar.YEAR)) { + flags |= FORMAT_SHOW_YEAR; + } else { + flags |= FORMAT_NO_YEAR; + } + } + return DateTimeFormat.format(icuLocale, timeCalendar, flags, displayContext); + } + + synchronized (CACHED_FORMATTERS) { + android.icu.text.RelativeDateTimeFormatter formatter = + getFormatter(icuLocale, style, displayContext); + if (relative) { + return formatter.format(count, direction, unit); + } else { + return formatter.format(direction, aunit); + } + } + } + + /** + * 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 icu4j'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, java.util.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"); + } + ULocale icuLocale = ULocale.forLocale(locale); + android.icu.util.TimeZone icuTimeZone = DateUtilsBridge.icuTimeZone(tz); + + 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; + } + android.icu.text.RelativeDateTimeFormatter.Style style; + if ((flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0) { + style = android.icu.text.RelativeDateTimeFormatter.Style.SHORT; + } else { + style = android.icu.text.RelativeDateTimeFormatter.Style.LONG; + } + + Calendar timeCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, time); + Calendar nowCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, now); + + int days = Math.abs(DateUtilsBridge.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(icuLocale, icuTimeZone, time, now, minResolution, + flags, DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); + } else { + // We always use fixed flags to format the date clause. User-supplied + // flags are ignored. + 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_NO_YEAR | FORMAT_ABBREV_MONTH; + } + + dateClause = DateTimeFormat.format(icuLocale, timeCalendar, flags, + DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); + } + + String timeClause = DateTimeFormat.format(icuLocale, timeCalendar, FORMAT_SHOW_TIME, + DisplayContext.CAPITALIZATION_NONE); + + // icu4j also has other options available to control the capitalization. We are currently using + // the _NONE option only. + DisplayContext capitalizationContext = DisplayContext.CAPITALIZATION_NONE; + + // Combine the two clauses, such as '5 days ago, 10:50 AM'. + synchronized (CACHED_FORMATTERS) { + return getFormatter(icuLocale, style, capitalizationContext) + .combineDateAndTime(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 android.icu.text.RelativeDateTimeFormatter getFormatter( + ULocale locale, android.icu.text.RelativeDateTimeFormatter.Style style, + DisplayContext displayContext) { + String key = locale + "\t" + style + "\t" + displayContext; + android.icu.text.RelativeDateTimeFormatter formatter = CACHED_FORMATTERS.get(key); + if (formatter == null) { + formatter = android.icu.text.RelativeDateTimeFormatter.getInstance( + locale, null, style, displayContext); + CACHED_FORMATTERS.put(key, formatter); + } + return formatter; + } + + // Return the date difference for the two times in a given timezone. + private static int dayDistance(android.icu.util.TimeZone icuTimeZone, long startTime, + long endTime) { + return julianDay(icuTimeZone, endTime) - julianDay(icuTimeZone, startTime); + } + + private static int julianDay(android.icu.util.TimeZone icuTimeZone, long time) { + long utcMs = time + icuTimeZone.getOffset(time); + return (int) (utcMs / DAY_IN_MS) + EPOCH_JULIAN_DAY; + } +} diff --git a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java new file mode 100644 index 0000000000000..61126c07e8f75 --- /dev/null +++ b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java @@ -0,0 +1,712 @@ +/* + * 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 android.text.format; + +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_ALL; +import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_RELATIVE; +import static android.text.format.DateUtilsBridge.FORMAT_NO_YEAR; +import static android.text.format.DateUtilsBridge.FORMAT_NUMERIC_DATE; +import static android.text.format.DateUtilsBridge.FORMAT_SHOW_YEAR; +import static android.text.format.RelativeDateTimeFormatter.DAY_IN_MILLIS; +import static android.text.format.RelativeDateTimeFormatter.HOUR_IN_MILLIS; +import static android.text.format.RelativeDateTimeFormatter.MINUTE_IN_MILLIS; +import static android.text.format.RelativeDateTimeFormatter.SECOND_IN_MILLIS; +import static android.text.format.RelativeDateTimeFormatter.WEEK_IN_MILLIS; +import static android.text.format.RelativeDateTimeFormatter.YEAR_IN_MILLIS; +import static android.text.format.RelativeDateTimeFormatter.getRelativeDateTimeString; +import static android.text.format.RelativeDateTimeFormatter.getRelativeTimeSpanString; + +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 { + // Bug: 19744876 + // We need to specify the timezone and the time explicitly. Otherwise it + // may not always give a correct answer of "tomorrow" by using + // (now + DAY_IN_MILLIS). + Locale de_DE = new Locale("de", "DE"); + TimeZone tz = TimeZone.getTimeZone("Europe/Berlin"); + Calendar cal = Calendar.getInstance(tz, de_DE); + // Feb 5, 2015 at 10:50 CET + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long now = cal.getTimeInMillis(); + + // 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"); + TimeZone tz = TimeZone.getTimeZone("Europe/Paris"); + Calendar cal = Calendar.getInstance(tz, fr_FR); + // Feb 5, 2015 at 10:50 CET + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long now = cal.getTimeInMillis(); + + // 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)); + } + + // b/19822016: show / hide the year based on the dates in the arguments. + public void test_bug19822016() 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, 2012 at 10:50 PST + cal.set(2012, Calendar.FEBRUARY, 5, 10, 50, 0); + long base = cal.getTimeInMillis(); + + assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz, + base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_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/2011, 10:50 AM", getRelativeDateTimeString(en_US, tz, + base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + assertEquals("January 6, 2012", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7, 2011", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("December 7, 2011", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + + // Feb 5, 2018 at 10:50 PST + cal.set(2018, Calendar.FEBRUARY, 5, 10, 50, 0); + base = cal.getTimeInMillis(); + assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz, + base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_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/2017, 10:50 AM", getRelativeDateTimeString(en_US, tz, + base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + assertEquals("January 6, 2018", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7, 2017", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("December 7, 2017", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + } + + // Check for missing ICU data. http://b/25821045 + public void test_bug25821045() { + final TimeZone tz = TimeZone.getDefault(); + final long now = System.currentTimeMillis(); + final long time = now + 1000; + final int minResolution = 1000 * 60; + final int transitionResolution = minResolution; + final int flags = FORMAT_ABBREV_RELATIVE; + // Exercise all available locales, forcing the ICU implementation to pre-cache the data. This + // highlights data issues. It can take a while. + for (Locale locale : Locale.getAvailableLocales()) { + // In (e.g.) ICU56 an exception is thrown on the first use for a locale if required data for + // the "other" plural is missing. It doesn't matter what is actually formatted. + try { + RelativeDateTimeFormatter.getRelativeDateTimeString( + locale, tz, time, now, minResolution, transitionResolution, flags); + } catch (IllegalStateException e) { + fail("Failed to format for " + locale); + } + } + } + + // Check for ICU data lookup fallback failure. http://b/25883157 + public void test_bug25883157() { + final Locale locale = new Locale("en", "GB"); + final TimeZone tz = TimeZone.getTimeZone("GMT"); + + final Calendar cal = Calendar.getInstance(tz, locale); + cal.set(2015, Calendar.JUNE, 19, 12, 0, 0); + + final long base = cal.getTimeInMillis(); + final long time = base + 2 * WEEK_IN_MILLIS; + + assertEquals("In 2 wk", getRelativeTimeSpanString( + locale, tz, time, base, WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + } + + // http://b/63745717 + public void test_combineDateAndTime_apostrophe() { + final Locale locale = new Locale("fr"); + android.icu.text.RelativeDateTimeFormatter icuFormatter = + android.icu.text.RelativeDateTimeFormatter.getInstance(locale); + assertEquals("D à T", icuFormatter.combineDateAndTime("D", "T")); + // Ensure single quote ' and curly braces {} are not interpreted in input values. + assertEquals("D'x' à T{0}", icuFormatter.combineDateAndTime("D'x'", "T{0}")); + } +} From 5e348b2a28f67ed835beedd4ef897de414d6f58b Mon Sep 17 00:00:00 2001 From: Victor Chang Date: Tue, 7 Jul 2020 18:32:13 +0100 Subject: [PATCH 2/2] Move/Copy some libcore.icu classes to android.text.format Bug: 160606356 Test: atest FrameworksCoreTests:android.text.format Change-Id: I5a3e954419e8bd282ec5dd057b241c3572be6831 --- .../android/text/format/DateTimeFormat.java | 19 +- core/java/android/text/format/DateUtils.java | 1 - .../android/text/format/DateUtilsBridge.java | 70 ++- .../format/RelativeDateTimeFormatter.java | 187 +++--- .../format/RelativeDateTimeFormatterTest.java | 550 ++++++++++-------- 5 files changed, 442 insertions(+), 385 deletions(-) diff --git a/core/java/android/text/format/DateTimeFormat.java b/core/java/android/text/format/DateTimeFormat.java index 20c4f4c6c4acb..064d7172c44f8 100644 --- a/core/java/android/text/format/DateTimeFormat.java +++ b/core/java/android/text/format/DateTimeFormat.java @@ -22,17 +22,17 @@ import android.icu.text.DisplayContext; import android.icu.text.SimpleDateFormat; import android.icu.util.Calendar; import android.icu.util.ULocale; - -import libcore.util.BasicLruCache; +import android.util.LruCache; /** * A formatter that outputs a single date/time. + * + * @hide */ -public class DateTimeFormat { - private static final libcore.icu.DateTimeFormat.FormatterCache - CACHED_FORMATTERS = new libcore.icu.DateTimeFormat.FormatterCache(); +class DateTimeFormat { + private static final FormatterCache CACHED_FORMATTERS = new FormatterCache(); - static class FormatterCache extends BasicLruCache { + static class FormatterCache extends LruCache { FormatterCache() { super(8); } @@ -42,13 +42,14 @@ public class DateTimeFormat { } public static String format(ULocale icuLocale, Calendar time, int flags, - DisplayContext displayContext) { + DisplayContext displayContext) { String skeleton = DateUtilsBridge.toSkeleton(time, flags); String key = skeleton + "\t" + icuLocale + "\t" + time.getTimeZone(); - synchronized(CACHED_FORMATTERS) { + synchronized (CACHED_FORMATTERS) { DateFormat formatter = CACHED_FORMATTERS.get(key); if (formatter == null) { - DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(icuLocale); + DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance( + icuLocale); formatter = new SimpleDateFormat(generator.getBestPattern(skeleton), icuLocale); CACHED_FORMATTERS.put(key, formatter); } diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index f236f19e973fd..b0253a0af7a7a 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -29,7 +29,6 @@ import com.android.internal.R; import libcore.icu.DateIntervalFormat; import libcore.icu.LocaleData; -import libcore.icu.RelativeDateTimeFormatter; import java.io.IOException; import java.util.Calendar; diff --git a/core/java/android/text/format/DateUtilsBridge.java b/core/java/android/text/format/DateUtilsBridge.java index 8701f5cb38e96..370d999abf3e7 100644 --- a/core/java/android/text/format/DateUtilsBridge.java +++ b/core/java/android/text/format/DateUtilsBridge.java @@ -16,49 +16,58 @@ package android.text.format; +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; + import android.icu.util.Calendar; import android.icu.util.GregorianCalendar; import android.icu.util.TimeZone; import android.icu.util.ULocale; +import com.android.internal.annotations.VisibleForTesting; + /** - * Common methods and constants for the various ICU formatters used to support - * {@link android.text.format.DateUtils}. + * Common methods and constants for the various ICU formatters used to support {@link + * android.text.format.DateUtils}. + * * @hide */ -final class DateUtilsBridge { +@VisibleForTesting(visibility = PACKAGE) +public final class DateUtilsBridge { // These are all public API in DateUtils. There are others, but they're either for use with // other methods (like FORMAT_ABBREV_RELATIVE), don't internationalize (like FORMAT_CAP_AMPM), // or have never been implemented anyway. - public static final int FORMAT_SHOW_TIME = 0x00001; - public static final int FORMAT_SHOW_WEEKDAY = 0x00002; - public static final int FORMAT_SHOW_YEAR = 0x00004; - public static final int FORMAT_NO_YEAR = 0x00008; - public static final int FORMAT_SHOW_DATE = 0x00010; - public static final int FORMAT_NO_MONTH_DAY = 0x00020; - public static final int FORMAT_12HOUR = 0x00040; - public static final int FORMAT_24HOUR = 0x00080; - public static final int FORMAT_UTC = 0x02000; - public static final int FORMAT_ABBREV_TIME = 0x04000; - public static final int FORMAT_ABBREV_WEEKDAY = 0x08000; - public static final int FORMAT_ABBREV_MONTH = 0x10000; - public static final int FORMAT_NUMERIC_DATE = 0x20000; + public static final int FORMAT_SHOW_TIME = 0x00001; + public static final int FORMAT_SHOW_WEEKDAY = 0x00002; + public static final int FORMAT_SHOW_YEAR = 0x00004; + public static final int FORMAT_NO_YEAR = 0x00008; + public static final int FORMAT_SHOW_DATE = 0x00010; + public static final int FORMAT_NO_MONTH_DAY = 0x00020; + public static final int FORMAT_12HOUR = 0x00040; + public static final int FORMAT_24HOUR = 0x00080; + public static final int FORMAT_UTC = 0x02000; + public static final int FORMAT_ABBREV_TIME = 0x04000; + public static final int FORMAT_ABBREV_WEEKDAY = 0x08000; + 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 int FORMAT_ABBREV_ALL = 0x80000; /** - * Creates an immutable ICU timezone backed by the specified libcore timezone data. At the time of - * writing the libcore implementation is faster but restricted to 1902 - 2038. - * Callers must not modify the {@code tz} after calling this method. + * Creates an immutable ICU timezone backed by the specified libcore timezone data. At the time + * of writing the libcore implementation is faster but restricted to 1902 - 2038. Callers must + * not modify the {@code tz} after calling this method. */ - public static android.icu.util.TimeZone icuTimeZone(java.util.TimeZone tz) { - android.icu.util.TimeZone icuTimeZone = TimeZone.getTimeZone(tz.getID()); + public static TimeZone icuTimeZone(java.util.TimeZone tz) { + TimeZone icuTimeZone = TimeZone.getTimeZone(tz.getID()); icuTimeZone.freeze(); // Optimization - allows the timezone to be copied cheaply. return icuTimeZone; } - public static Calendar createIcuCalendar(android.icu.util.TimeZone icuTimeZone, ULocale icuLocale, - long timeInMillis) { + /** + * Create a GregorianCalendar based on the arguments + */ + public static Calendar createIcuCalendar(TimeZone icuTimeZone, ULocale icuLocale, + long timeInMillis) { Calendar calendar = new GregorianCalendar(icuTimeZone, icuLocale); calendar.setTimeInMillis(timeInMillis); return calendar; @@ -123,7 +132,8 @@ final class DateUtilsBridge { if ((flags & FORMAT_SHOW_YEAR) != 0) { // The caller explicitly wants us to show the year. } else if ((flags & FORMAT_NO_YEAR) != 0) { - // The caller explicitly doesn't want us to show the year, even if we otherwise would. + // The caller explicitly doesn't want us to show the year, even if we otherwise + // would. } else if (!fallInSameYear(startCalendar, endCalendar) || !isThisYear(startCalendar)) { flags |= FORMAT_SHOW_YEAR; } @@ -157,8 +167,8 @@ final class DateUtilsBridge { * skeletons provided by {@link #toSkeleton}. */ public static boolean isDisplayMidnightUsingSkeleton(Calendar c) { - // All the skeletons returned by toSkeleton have minute precision (they may abbreviate 4:00 PM - // to 4 PM but will still show the following minute as 4:01 PM). + // All the skeletons returned by toSkeleton have minute precision (they may abbreviate + // 4:00 PM to 4 PM but will still show the following minute as 4:01 PM). return c.get(Calendar.HOUR_OF_DAY) == 0 && c.get(Calendar.MINUTE) == 0; } @@ -167,9 +177,9 @@ final class DateUtilsBridge { } private static boolean fallOnDifferentDates(Calendar c1, Calendar c2) { - return c1.get(Calendar.YEAR) != c2.get(Calendar.YEAR) || - c1.get(Calendar.MONTH) != c2.get(Calendar.MONTH) || - c1.get(Calendar.DAY_OF_MONTH) != c2.get(Calendar.DAY_OF_MONTH); + return c1.get(Calendar.YEAR) != c2.get(Calendar.YEAR) + || c1.get(Calendar.MONTH) != c2.get(Calendar.MONTH) + || c1.get(Calendar.DAY_OF_MONTH) != c2.get(Calendar.DAY_OF_MONTH); } private static boolean fallInSameMonth(Calendar c1, Calendar c2) { diff --git a/core/java/android/text/format/RelativeDateTimeFormatter.java b/core/java/android/text/format/RelativeDateTimeFormatter.java index 9c52431128651..c5bca172873ad 100644 --- a/core/java/android/text/format/RelativeDateTimeFormatter.java +++ b/core/java/android/text/format/RelativeDateTimeFormatter.java @@ -16,13 +16,6 @@ package android.text.format; -import java.util.Locale; -import libcore.util.BasicLruCache; - -import android.icu.text.DisplayContext; -import android.icu.util.Calendar; -import android.icu.util.ULocale; - import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_ALL; import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_MONTH; import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_RELATIVE; @@ -32,12 +25,24 @@ import static android.text.format.DateUtilsBridge.FORMAT_SHOW_DATE; import static android.text.format.DateUtilsBridge.FORMAT_SHOW_TIME; import static android.text.format.DateUtilsBridge.FORMAT_SHOW_YEAR; +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; + +import android.icu.text.DisplayContext; +import android.icu.util.Calendar; +import android.icu.util.ULocale; +import android.util.LruCache; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Locale; + /** * Exposes icu4j's RelativeDateTimeFormatter. * * @hide */ -final class RelativeDateTimeFormatter { +@VisibleForTesting(visibility = PACKAGE) +public final class RelativeDateTimeFormatter { public static final long SECOND_IN_MILLIS = 1000; public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60; @@ -54,7 +59,7 @@ final class RelativeDateTimeFormatter { private static final FormatterCache CACHED_FORMATTERS = new FormatterCache(); static class FormatterCache - extends BasicLruCache { + extends LruCache { FormatterCache() { super(8); } @@ -64,42 +69,40 @@ final class 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 icu4j'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. + * 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 icu4j'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, java.util.TimeZone tz, long time, - long now, long minResolution, int flags) { - // Android has been inconsistent about capitalization in the past. e.g. bug http://b/20247811. + long now, long minResolution, int flags) { + // Android has been inconsistent about capitalization in the past. e.g. bug + // http://b/20247811. // Now we capitalize everything consistently. - final DisplayContext displayContext = DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE; - return getRelativeTimeSpanString(locale, tz, time, now, minResolution, flags, displayContext); + final DisplayContext displayContext = + DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE; + return getRelativeTimeSpanString(locale, tz, time, now, minResolution, flags, + displayContext); } public static String getRelativeTimeSpanString(Locale locale, java.util.TimeZone tz, long time, - long now, long minResolution, int flags, DisplayContext displayContext) { + long now, long minResolution, int flags, DisplayContext displayContext) { if (locale == null) { throw new NullPointerException("locale == null"); } @@ -109,12 +112,13 @@ final class RelativeDateTimeFormatter { ULocale icuLocale = ULocale.forLocale(locale); android.icu.util.TimeZone icuTimeZone = DateUtilsBridge.icuTimeZone(tz); return getRelativeTimeSpanString(icuLocale, icuTimeZone, time, now, minResolution, flags, - displayContext); + displayContext); } private static String getRelativeTimeSpanString(ULocale icuLocale, - android.icu.util.TimeZone icuTimeZone, long time, long now, long minResolution, int flags, - DisplayContext displayContext) { + android.icu.util.TimeZone icuTimeZone, long time, long now, long minResolution, + int flags, + DisplayContext displayContext) { long duration = Math.abs(now - time); boolean past = (now >= time); @@ -142,16 +146,16 @@ final class RelativeDateTimeFormatter { android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit aunit = null; if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) { - count = (int)(duration / SECOND_IN_MILLIS); + count = (int) (duration / SECOND_IN_MILLIS); unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.SECONDS; } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) { - count = (int)(duration / MINUTE_IN_MILLIS); + count = (int) (duration / MINUTE_IN_MILLIS); unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.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); + count = (int) (duration / HOUR_IN_MILLIS); unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.HOURS; } else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) { count = Math.abs(dayDistance(icuTimeZone, time, now)); @@ -167,15 +171,13 @@ final class RelativeDateTimeFormatter { String str; if (past) { synchronized (CACHED_FORMATTERS) { - str = getFormatter(icuLocale, style, displayContext) - .format( + str = getFormatter(icuLocale, style, displayContext).format( android.icu.text.RelativeDateTimeFormatter.Direction.LAST_2, android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY); } } else { synchronized (CACHED_FORMATTERS) { - str = getFormatter(icuLocale, style, displayContext) - .format( + str = getFormatter(icuLocale, style, displayContext).format( android.icu.text.RelativeDateTimeFormatter.Direction.NEXT_2, android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY); } @@ -195,7 +197,7 @@ final class RelativeDateTimeFormatter { relative = false; } } else if (minResolution == WEEK_IN_MILLIS) { - count = (int)(duration / WEEK_IN_MILLIS); + count = (int) (duration / WEEK_IN_MILLIS); unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.WEEKS; } else { Calendar timeCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, time); @@ -208,7 +210,8 @@ final class RelativeDateTimeFormatter { // formatDateRange() would determine that based on the current system // time and may give wrong results. if ((flags & (FORMAT_NO_YEAR | FORMAT_SHOW_YEAR)) == 0) { - Calendar nowCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, now); + Calendar nowCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, + now); if (timeCalendar.get(Calendar.YEAR) != nowCalendar.get(Calendar.YEAR)) { flags |= FORMAT_SHOW_YEAR; @@ -221,7 +224,7 @@ final class RelativeDateTimeFormatter { synchronized (CACHED_FORMATTERS) { android.icu.text.RelativeDateTimeFormatter formatter = - getFormatter(icuLocale, style, displayContext); + getFormatter(icuLocale, style, displayContext); if (relative) { return formatter.format(count, direction, unit); } else { @@ -231,36 +234,31 @@ final class RelativeDateTimeFormatter { } /** - * 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 icu4j'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. + * 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 icu4j'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, java.util.TimeZone tz, long time, - long now, long minResolution, long transitionResolution, int flags) { + long now, long minResolution, long transitionResolution, int flags) { if (locale == null) { throw new NullPointerException("locale == null"); @@ -298,7 +296,7 @@ final class RelativeDateTimeFormatter { minResolution = DAY_IN_MILLIS; } dateClause = getRelativeTimeSpanString(icuLocale, icuTimeZone, time, now, minResolution, - flags, DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); + flags, DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); } else { // We always use fixed flags to format the date clause. User-supplied // flags are ignored. @@ -311,39 +309,38 @@ final class RelativeDateTimeFormatter { } dateClause = DateTimeFormat.format(icuLocale, timeCalendar, flags, - DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); + DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); } String timeClause = DateTimeFormat.format(icuLocale, timeCalendar, FORMAT_SHOW_TIME, - DisplayContext.CAPITALIZATION_NONE); + DisplayContext.CAPITALIZATION_NONE); - // icu4j also has other options available to control the capitalization. We are currently using + // icu4j also has other options available to control the capitalization. We are currently + // using // the _NONE option only. DisplayContext capitalizationContext = DisplayContext.CAPITALIZATION_NONE; // Combine the two clauses, such as '5 days ago, 10:50 AM'. synchronized (CACHED_FORMATTERS) { return getFormatter(icuLocale, style, capitalizationContext) - .combineDateAndTime(dateClause, timeClause); + .combineDateAndTime(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(). + * 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 android.icu.text.RelativeDateTimeFormatter getFormatter( - ULocale locale, android.icu.text.RelativeDateTimeFormatter.Style style, - DisplayContext displayContext) { + ULocale locale, android.icu.text.RelativeDateTimeFormatter.Style style, + DisplayContext displayContext) { String key = locale + "\t" + style + "\t" + displayContext; android.icu.text.RelativeDateTimeFormatter formatter = CACHED_FORMATTERS.get(key); if (formatter == null) { formatter = android.icu.text.RelativeDateTimeFormatter.getInstance( - locale, null, style, displayContext); + locale, null, style, displayContext); CACHED_FORMATTERS.put(key, formatter); } return formatter; @@ -351,7 +348,7 @@ final class RelativeDateTimeFormatter { // Return the date difference for the two times in a given timezone. private static int dayDistance(android.icu.util.TimeZone icuTimeZone, long startTime, - long endTime) { + long endTime) { return julianDay(icuTimeZone, endTime) - julianDay(icuTimeZone, startTime); } diff --git a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java index 61126c07e8f75..d9ba8fb81d3c9 100644 --- a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java +++ b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java @@ -16,10 +16,6 @@ package android.text.format; -import java.util.Calendar; -import java.util.Locale; -import java.util.TimeZone; - import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_ALL; import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_RELATIVE; import static android.text.format.DateUtilsBridge.FORMAT_NO_YEAR; @@ -34,9 +30,29 @@ import static android.text.format.RelativeDateTimeFormatter.YEAR_IN_MILLIS; import static android.text.format.RelativeDateTimeFormatter.getRelativeDateTimeString; import static android.text.format.RelativeDateTimeFormatter.getRelativeTimeSpanString; -public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RelativeDateTimeFormatterTest { // Tests adopted from CTS tests for DateUtils.getRelativeTimeSpanString. + @Test public void test_getRelativeTimeSpanStringCTS() throws Exception { Locale en_US = new Locale("en", "US"); TimeZone tz = TimeZone.getTimeZone("GMT"); @@ -46,54 +62,54 @@ public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { final long baseTime = cal.getTimeInMillis(); assertEquals("0 minutes ago", - getRelativeTimeSpanString(en_US, tz, baseTime - SECOND_IN_MILLIS, baseTime, - MINUTE_IN_MILLIS, 0)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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 { + 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); @@ -102,250 +118,273 @@ public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { final long base = cal.getTimeInMillis(); assertEquals(expectedInPast, - getRelativeTimeSpanString(en_US, tz, base - delta, base, minResolution, flags)); + getRelativeTimeSpanString(en_US, tz, base - delta, base, minResolution, flags)); assertEquals(expectedInFuture, - getRelativeTimeSpanString(en_US, tz, base + delta, base, minResolution, flags)); + 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); + String expectedInPast, + String expectedInFuture) throws Exception { + test_getRelativeTimeSpanString_helper(delta, minResolution, 0, expectedInPast, + expectedInFuture); } + @Test 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(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 * 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(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"); + "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"); + "0 hours ago"); test_getRelativeTimeSpanString_helper(1 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "1 hour ago", - "In 1 hour"); + "In 1 hour"); test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "2 hours ago", - "In 2 hours"); + "In 2 hours"); test_getRelativeTimeSpanString_helper(5 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "5 hours ago", - "In 5 hours"); + "In 5 hours"); test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "20 hours ago", - "In 20 hours"); + "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"); + "Tomorrow"); test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday", - "Tomorrow"); + "Tomorrow"); test_getRelativeTimeSpanString_helper(2 * DAY_IN_MILLIS, DAY_IN_MILLIS, "2 days ago", - "In 2 days"); + "In 2 days"); test_getRelativeTimeSpanString_helper(25 * DAY_IN_MILLIS, DAY_IN_MILLIS, "January 11", - "March 2"); + "March 2"); test_getRelativeTimeSpanString_helper(0 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "0 weeks ago", - "0 weeks ago"); + "0 weeks ago"); test_getRelativeTimeSpanString_helper(1 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "1 week ago", - "In 1 week"); + "In 1 week"); test_getRelativeTimeSpanString_helper(2 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "2 weeks ago", - "In 2 weeks"); + "In 2 weeks"); test_getRelativeTimeSpanString_helper(25 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "25 weeks ago", - "In 25 weeks"); + "In 25 weeks"); // duration >= minResolution test_getRelativeTimeSpanString_helper(30 * SECOND_IN_MILLIS, 0, "30 seconds ago", - "In 30 seconds"); + "In 30 seconds"); test_getRelativeTimeSpanString_helper(30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, - "30 minutes ago", "In 30 minutes"); + "30 minutes ago", "In 30 minutes"); test_getRelativeTimeSpanString_helper(30 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, "Yesterday", - "Tomorrow"); + "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"); + "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"); + "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(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"); + "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"); + "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"); + "Today"); test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday", - "Tomorrow"); + "Tomorrow"); test_getRelativeTimeSpanString_helper(48 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "2 days ago", - "In 2 days"); + "In 2 days"); test_getRelativeTimeSpanString_helper(45 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "2 days ago", - "In 2 days"); + "In 2 days"); test_getRelativeTimeSpanString_helper(7 * DAY_IN_MILLIS, WEEK_IN_MILLIS, "1 week ago", - "In 1 week"); + "In 1 week"); test_getRelativeTimeSpanString_helper(14 * DAY_IN_MILLIS - 1, WEEK_IN_MILLIS, "1 week ago", - "In 1 week"); + "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 * 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"); + "In 0 hours"); test_getRelativeTimeSpanString_helper(HOUR_IN_MILLIS - 1, HOUR_IN_MILLIS, "0 hours ago", - "In 0 hours"); + "In 0 hours"); test_getRelativeTimeSpanString_helper(DAY_IN_MILLIS - 1, DAY_IN_MILLIS, "Yesterday", - "Tomorrow"); + "Tomorrow"); test_getRelativeTimeSpanString_helper(20 * SECOND_IN_MILLIS, WEEK_IN_MILLIS, "0 weeks ago", - "In 0 weeks"); + "In 0 weeks"); test_getRelativeTimeSpanString_helper(WEEK_IN_MILLIS - 1, WEEK_IN_MILLIS, "0 weeks ago", - "In 0 weeks"); + "In 0 weeks"); } + @Test 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"); + "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"); + "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"); + "0 sec. ago", "0 sec. ago"); test_getRelativeTimeSpanString_helper(1 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, - "1 sec. ago", "In 1 sec."); + "1 sec. ago", "In 1 sec."); test_getRelativeTimeSpanString_helper(2 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, - "2 sec. ago", "In 2 sec."); + "2 sec. ago", "In 2 sec."); test_getRelativeTimeSpanString_helper(25 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, - "25 sec. ago", "In 25 sec."); + "25 sec. ago", "In 25 sec."); test_getRelativeTimeSpanString_helper(75 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, - "1 min. ago", "In 1 min."); + "1 min. ago", "In 1 min."); test_getRelativeTimeSpanString_helper(5000 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, - "1 hr. ago", "In 1 hr."); + "1 hr. ago", "In 1 hr."); test_getRelativeTimeSpanString_helper(0 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, - "0 min. ago", "0 min. ago"); + "0 min. ago", "0 min. ago"); test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, - "1 min. ago", "In 1 min."); + "1 min. ago", "In 1 min."); test_getRelativeTimeSpanString_helper(2 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, - "2 min. ago", "In 2 min."); + "2 min. ago", "In 2 min."); test_getRelativeTimeSpanString_helper(25 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, - "25 min. ago", "In 25 min."); + "25 min. ago", "In 25 min."); test_getRelativeTimeSpanString_helper(75 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, - "1 hr. ago", "In 1 hr."); + "1 hr. ago", "In 1 hr."); test_getRelativeTimeSpanString_helper(720 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, - "12 hr. ago", "In 12 hr."); + "12 hr. ago", "In 12 hr."); test_getRelativeTimeSpanString_helper(0 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, - "0 hr. ago", "0 hr. ago"); + "0 hr. ago", "0 hr. ago"); test_getRelativeTimeSpanString_helper(1 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, - "1 hr. ago", "In 1 hr."); + "1 hr. ago", "In 1 hr."); test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, - "2 hr. ago", "In 2 hr."); + "2 hr. ago", "In 2 hr."); test_getRelativeTimeSpanString_helper(5 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, - "5 hr. ago", "In 5 hr."); + "5 hr. ago", "In 5 hr."); test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, - "20 hr. ago", "In 20 hr."); + "20 hr. ago", "In 20 hr."); test_getRelativeTimeSpanString_helper(0 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags, "Today", - "Today"); + "Today"); test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, - "Yesterday", "Tomorrow"); + "Yesterday", "Tomorrow"); test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, - "Yesterday", "Tomorrow"); + "Yesterday", "Tomorrow"); test_getRelativeTimeSpanString_helper(2 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags, - "2 days ago", "In 2 days"); + "2 days ago", "In 2 days"); test_getRelativeTimeSpanString_helper(25 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags, - "January 11", "March 2"); + "January 11", "March 2"); test_getRelativeTimeSpanString_helper(0 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, - "0 wk. ago", "0 wk. ago"); + "0 wk. ago", "0 wk. ago"); test_getRelativeTimeSpanString_helper(1 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, - "1 wk. ago", "In 1 wk."); + "1 wk. ago", "In 1 wk."); test_getRelativeTimeSpanString_helper(2 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, - "2 wk. ago", "In 2 wk."); + "2 wk. ago", "In 2 wk."); test_getRelativeTimeSpanString_helper(25 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, - "25 wk. ago", "In 25 wk."); + "25 wk. ago", "In 25 wk."); // duration >= minResolution test_getRelativeTimeSpanString_helper(30 * SECOND_IN_MILLIS, 0, flags, "30 sec. ago", - "In 30 sec."); + "In 30 sec."); test_getRelativeTimeSpanString_helper(30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, - "30 min. ago", "In 30 min."); + "30 min. ago", "In 30 min."); test_getRelativeTimeSpanString_helper(30 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, flags, - "Yesterday", "Tomorrow"); + "Yesterday", "Tomorrow"); test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, flags, - "5 days ago", "In 5 days"); + "5 days ago", "In 5 days"); test_getRelativeTimeSpanString_helper(30 * WEEK_IN_MILLIS, MINUTE_IN_MILLIS, flags, - "July 10, 2014", "September 3"); + "July 10, 2014", "September 3"); test_getRelativeTimeSpanString_helper(5 * 365 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, flags, - "February 6, 2010", "February 4, 2020"); + "February 6, 2010", "February 4, 2020"); test_getRelativeTimeSpanString_helper(60 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, flags, - "1 min. ago", "In 1 min."); + "1 min. ago", "In 1 min."); test_getRelativeTimeSpanString_helper(120 * SECOND_IN_MILLIS - 1, MINUTE_IN_MILLIS, flags, - "1 min. ago", "In 1 min."); + "1 min. ago", "In 1 min."); test_getRelativeTimeSpanString_helper(60 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, flags, - "1 hr. ago", "In 1 hr."); + "1 hr. ago", "In 1 hr."); test_getRelativeTimeSpanString_helper(120 * MINUTE_IN_MILLIS - 1, HOUR_IN_MILLIS, flags, - "1 hr. ago", "In 1 hr."); + "1 hr. ago", "In 1 hr."); test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, "Today", - "Today"); + "Today"); test_getRelativeTimeSpanString_helper(12 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, - "Yesterday", "Today"); + "Yesterday", "Today"); test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, - "Yesterday", "Tomorrow"); + "Yesterday", "Tomorrow"); test_getRelativeTimeSpanString_helper(48 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, - "2 days ago", "In 2 days"); + "2 days ago", "In 2 days"); test_getRelativeTimeSpanString_helper(45 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, - "2 days ago", "In 2 days"); + "2 days ago", "In 2 days"); test_getRelativeTimeSpanString_helper(7 * DAY_IN_MILLIS, WEEK_IN_MILLIS, flags, - "1 wk. ago", "In 1 wk."); + "1 wk. ago", "In 1 wk."); test_getRelativeTimeSpanString_helper(14 * DAY_IN_MILLIS - 1, WEEK_IN_MILLIS, flags, - "1 wk. ago", "In 1 wk."); + "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."); + "0 min. ago", "In 0 min."); test_getRelativeTimeSpanString_helper(59 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, flags, - "0 hr. ago", "In 0 hr."); + "0 hr. ago", "In 0 hr."); test_getRelativeTimeSpanString_helper(HOUR_IN_MILLIS - 1, HOUR_IN_MILLIS, flags, - "0 hr. ago", "In 0 hr."); + "0 hr. ago", "In 0 hr."); test_getRelativeTimeSpanString_helper(DAY_IN_MILLIS - 1, DAY_IN_MILLIS, flags, - "Yesterday", "Tomorrow"); + "Yesterday", "Tomorrow"); test_getRelativeTimeSpanString_helper(20 * SECOND_IN_MILLIS, WEEK_IN_MILLIS, flags, - "0 wk. ago", "In 0 wk."); + "0 wk. ago", "In 0 wk."); test_getRelativeTimeSpanString_helper(WEEK_IN_MILLIS - 1, WEEK_IN_MILLIS, flags, - "0 wk. ago", "In 0 wk."); + "0 wk. ago", "In 0 wk."); } + @Test public void test_getRelativeTimeSpanStringGerman() throws Exception { // Bug: 19744876 // We need to specify the timezone and the time explicitly. Otherwise it @@ -360,24 +399,25 @@ public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { // 42 minutes ago assertEquals("Vor 42 Minuten", getRelativeTimeSpanString(de_DE, tz, - now - 42 * MINUTE_IN_MILLIS, now, MINUTE_IN_MILLIS, 0)); + 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)); + 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)); + 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)); + 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)); + 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)); + now + 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); } + @Test public void test_getRelativeTimeSpanStringFrench() throws Exception { Locale fr_FR = new Locale("fr", "FR"); TimeZone tz = TimeZone.getTimeZone("Europe/Paris"); @@ -388,25 +428,26 @@ public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { // 42 minutes ago assertEquals("Il y a 42 minutes", getRelativeTimeSpanString(fr_FR, tz, - now - (42 * MINUTE_IN_MILLIS), now, MINUTE_IN_MILLIS, 0)); + 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)); + 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)); + 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)); + 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)); + 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)); + now + 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); } // Tests adopted from CTS tests for DateUtils.getRelativeDateTimeString. + @Test public void test_getRelativeDateTimeStringCTS() throws Exception { Locale en_US = Locale.getDefault(); TimeZone tz = TimeZone.getDefault(); @@ -414,10 +455,11 @@ public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { 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)); + MINUTE_IN_MILLIS, DAY_IN_MILLIS, + FORMAT_NUMERIC_DATE)); } + @Test public void test_getRelativeDateTimeString() throws Exception { Locale en_US = new Locale("en", "US"); TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); @@ -427,41 +469,42 @@ public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, + FORMAT_ABBREV_ALL | FORMAT_SHOW_WEEKDAY)); } + @Test public void test_getRelativeDateTimeStringDST() throws Exception { Locale en_US = new Locale("en", "US"); TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); @@ -472,41 +515,41 @@ public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + getRelativeDateTimeString(en_US, tz, base + 135 * MINUTE_IN_MILLIS, + base + 90 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0)); } - + @Test public void test_getRelativeDateTimeStringItalian() throws Exception { Locale it_IT = new Locale("it", "IT"); TimeZone tz = TimeZone.getTimeZone("Europe/Rome"); @@ -516,26 +559,27 @@ public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + getRelativeDateTimeString(it_IT, tz, base - 10 * WEEK_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); } // http://b/5252772: detect the actual date difference + @Test public void test5252772() throws Exception { Locale en_US = new Locale("en", "US"); TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); @@ -550,67 +594,68 @@ public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + getRelativeDateTimeString(en_US, tz, twoDaysLater2, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); } // b/19822016: show / hide the year based on the dates in the arguments. + @Test public void test_bug19822016() throws Exception { Locale en_US = new Locale("en", "US"); TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); @@ -620,50 +665,51 @@ public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { long base = cal.getTimeInMillis(); assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz, - base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0)); + base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0)); assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz, - base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); assertEquals("11/27/2011, 10:50 AM", getRelativeDateTimeString(en_US, tz, - base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, - base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, - base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); assertEquals("January 6, 2012", getRelativeTimeSpanString(en_US, tz, - base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); assertEquals("December 7, 2011", getRelativeTimeSpanString(en_US, tz, - base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); assertEquals("December 7, 2011", getRelativeTimeSpanString(en_US, tz, - base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); assertEquals("December 7", getRelativeTimeSpanString(en_US, tz, - base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); // Feb 5, 2018 at 10:50 PST cal.set(2018, Calendar.FEBRUARY, 5, 10, 50, 0); base = cal.getTimeInMillis(); assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz, - base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0)); + base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0)); assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz, - base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); assertEquals("11/27/2017, 10:50 AM", getRelativeDateTimeString(en_US, tz, - base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, - base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, - base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); assertEquals("January 6, 2018", getRelativeTimeSpanString(en_US, tz, - base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); assertEquals("December 7, 2017", getRelativeTimeSpanString(en_US, tz, - base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); assertEquals("December 7, 2017", getRelativeTimeSpanString(en_US, tz, - base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); assertEquals("December 7", getRelativeTimeSpanString(en_US, tz, - base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); } // Check for missing ICU data. http://b/25821045 + @Test public void test_bug25821045() { final TimeZone tz = TimeZone.getDefault(); final long now = System.currentTimeMillis(); @@ -671,14 +717,16 @@ public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { final int minResolution = 1000 * 60; final int transitionResolution = minResolution; final int flags = FORMAT_ABBREV_RELATIVE; - // Exercise all available locales, forcing the ICU implementation to pre-cache the data. This + // Exercise all available locales, forcing the ICU implementation to pre-cache the data. + // This // highlights data issues. It can take a while. for (Locale locale : Locale.getAvailableLocales()) { - // In (e.g.) ICU56 an exception is thrown on the first use for a locale if required data for + // In (e.g.) ICU56 an exception is thrown on the first use for a locale if required + // data for // the "other" plural is missing. It doesn't matter what is actually formatted. try { RelativeDateTimeFormatter.getRelativeDateTimeString( - locale, tz, time, now, minResolution, transitionResolution, flags); + locale, tz, time, now, minResolution, transitionResolution, flags); } catch (IllegalStateException e) { fail("Failed to format for " + locale); } @@ -686,6 +734,7 @@ public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { } // Check for ICU data lookup fallback failure. http://b/25883157 + @Test public void test_bug25883157() { final Locale locale = new Locale("en", "GB"); final TimeZone tz = TimeZone.getTimeZone("GMT"); @@ -697,14 +746,15 @@ public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { final long time = base + 2 * WEEK_IN_MILLIS; assertEquals("In 2 wk", getRelativeTimeSpanString( - locale, tz, time, base, WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + locale, tz, time, base, WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); } // http://b/63745717 + @Test public void test_combineDateAndTime_apostrophe() { final Locale locale = new Locale("fr"); android.icu.text.RelativeDateTimeFormatter icuFormatter = - android.icu.text.RelativeDateTimeFormatter.getInstance(locale); + android.icu.text.RelativeDateTimeFormatter.getInstance(locale); assertEquals("D à T", icuFormatter.combineDateAndTime("D", "T")); // Ensure single quote ' and curly braces {} are not interpreted in input values. assertEquals("D'x' à T{0}", icuFormatter.combineDateAndTime("D'x'", "T{0}"));