| /* |
| * Copyright (C) 2013 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 static android.text.format.DateUtils.FORMAT_SHOW_TIME; |
| import static android.text.format.DateUtils.FORMAT_UTC; |
| |
| import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; |
| |
| import android.icu.util.Calendar; |
| import android.icu.util.ULocale; |
| import android.util.LruCache; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.text.FieldPosition; |
| import java.util.TimeZone; |
| |
| /** |
| * A wrapper of {@link android.icu.text.DateIntervalFormat} used by {@link DateUtilsBridge}. |
| * |
| * @hide |
| */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public final class DateIntervalFormat { |
| |
| private static final LruCache<String, android.icu.text.DateIntervalFormat> CACHED_FORMATTERS = |
| new LruCache<>(8); |
| |
| private DateIntervalFormat() { |
| } |
| |
| /** |
| * Format a date range. |
| */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public static String formatDateRange(long startMs, long endMs, int flags, String olsonId) { |
| if ((flags & FORMAT_UTC) != 0) { |
| olsonId = "UTC"; |
| } |
| // We create a java.util.TimeZone here to use libcore's data and libcore's olson ID / |
| // pseudo-tz logic. |
| TimeZone tz = (olsonId != null) ? TimeZone.getTimeZone(olsonId) : TimeZone.getDefault(); |
| android.icu.util.TimeZone icuTimeZone = DateUtilsBridge.icuTimeZone(tz); |
| ULocale icuLocale = ULocale.getDefault(); |
| return formatDateRange(icuLocale, icuTimeZone, startMs, endMs, flags); |
| } |
| |
| /** |
| * Format a date range. This is our slightly more sensible internal API. |
| * A truly reasonable replacement would take a skeleton instead of int flags. |
| */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public static String formatDateRange(ULocale icuLocale, android.icu.util.TimeZone icuTimeZone, |
| long startMs, long endMs, int flags) { |
| Calendar startCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, startMs); |
| Calendar endCalendar; |
| if (startMs == endMs) { |
| endCalendar = startCalendar; |
| } else { |
| endCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, endMs); |
| } |
| |
| // Special handling when the range ends at midnight: |
| // - If we're not showing times, and the range is non-empty, we fudge the end date so we |
| // don't count the day that's about to start. |
| // - If we are showing times, and the range ends at exactly 00:00 of the day following |
| // its start (which can be thought of as 24:00 the same day), we fudge the end date so we |
| // don't show the dates --- unless the start is anything displayed as 00:00, in which case |
| // we include both dates to disambiguate. |
| // This is not the behavior of icu4j's DateIntervalFormat, but it's the required behavior |
| // of Android's DateUtils.formatDateRange. |
| if (isExactlyMidnight(endCalendar)) { |
| boolean showTime = (flags & FORMAT_SHOW_TIME) == FORMAT_SHOW_TIME; |
| boolean endsDayAfterStart = DateUtilsBridge.dayDistance(startCalendar, endCalendar) |
| == 1; |
| if ((!showTime && startMs != endMs) |
| || (endsDayAfterStart |
| && !DateUtilsBridge.isDisplayMidnightUsingSkeleton(startCalendar))) { |
| endCalendar.add(Calendar.DAY_OF_MONTH, -1); |
| } |
| } |
| |
| String skeleton = DateUtilsBridge.toSkeleton(startCalendar, endCalendar, flags); |
| synchronized (CACHED_FORMATTERS) { |
| android.icu.text.DateIntervalFormat formatter = |
| getFormatter(skeleton, icuLocale, icuTimeZone); |
| return formatter.format(startCalendar, endCalendar, new StringBuffer(), |
| new FieldPosition(0)).toString(); |
| } |
| } |
| |
| private static android.icu.text.DateIntervalFormat getFormatter(String skeleton, ULocale locale, |
| android.icu.util.TimeZone icuTimeZone) { |
| String key = skeleton + "\t" + locale + "\t" + icuTimeZone; |
| android.icu.text.DateIntervalFormat formatter = CACHED_FORMATTERS.get(key); |
| if (formatter != null) { |
| return formatter; |
| } |
| formatter = android.icu.text.DateIntervalFormat.getInstance(skeleton, locale); |
| formatter.setTimeZone(icuTimeZone); |
| CACHED_FORMATTERS.put(key, formatter); |
| return formatter; |
| } |
| |
| private static boolean isExactlyMidnight(Calendar c) { |
| return c.get(Calendar.HOUR_OF_DAY) == 0 |
| && c.get(Calendar.MINUTE) == 0 |
| && c.get(Calendar.SECOND) == 0 |
| && c.get(Calendar.MILLISECOND) == 0; |
| } |
| } |