1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.text.format; 18 19 import static android.text.format.DateUtils.FORMAT_SHOW_TIME; 20 import static android.text.format.DateUtils.FORMAT_UTC; 21 22 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 23 24 import android.icu.util.Calendar; 25 import android.icu.util.ULocale; 26 import android.util.LruCache; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.libcore.Flags; 30 31 import java.text.FieldPosition; 32 import java.util.TimeZone; 33 34 /** 35 * A wrapper of {@link android.icu.text.DateIntervalFormat} used by {@link DateUtilsBridge}. 36 * 37 * @hide 38 */ 39 @VisibleForTesting(visibility = PACKAGE) 40 @android.ravenwood.annotation.RavenwoodKeepWholeClass 41 public final class DateIntervalFormat { 42 43 private static final LruCache<String, android.icu.text.DateIntervalFormat> CACHED_FORMATTERS = 44 new LruCache<>(8); 45 DateIntervalFormat()46 private DateIntervalFormat() { 47 } 48 49 /** 50 * Format a date range. 51 */ 52 @VisibleForTesting(visibility = PACKAGE) formatDateRange(long startMs, long endMs, int flags, String olsonId)53 public static String formatDateRange(long startMs, long endMs, int flags, String olsonId) { 54 if ((flags & FORMAT_UTC) != 0) { 55 olsonId = "UTC"; 56 } 57 // We create a java.util.TimeZone here to use libcore's data and libcore's olson ID / 58 // pseudo-tz logic. 59 TimeZone tz = (olsonId != null) ? TimeZone.getTimeZone(olsonId) : TimeZone.getDefault(); 60 android.icu.util.TimeZone icuTimeZone = DateUtilsBridge.icuTimeZone(tz); 61 ULocale icuLocale = ULocale.getDefault(); 62 return formatDateRange(icuLocale, icuTimeZone, startMs, endMs, flags); 63 } 64 65 /** 66 * Format a date range. This is our slightly more sensible internal API. 67 * A truly reasonable replacement would take a skeleton instead of int flags. 68 */ 69 @VisibleForTesting(visibility = PACKAGE) formatDateRange(ULocale icuLocale, android.icu.util.TimeZone icuTimeZone, long startMs, long endMs, int flags)70 public static String formatDateRange(ULocale icuLocale, android.icu.util.TimeZone icuTimeZone, 71 long startMs, long endMs, int flags) { 72 Calendar startCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, startMs); 73 Calendar endCalendar; 74 if (startMs == endMs) { 75 endCalendar = startCalendar; 76 } else { 77 endCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, endMs); 78 } 79 80 // Special handling when the range ends at midnight: 81 // - If we're not showing times, and the range is non-empty, we fudge the end date so we 82 // don't count the day that's about to start. 83 // - If we are showing times, and the range ends at exactly 00:00 of the day following 84 // its start (which can be thought of as 24:00 the same day), we fudge the end date so we 85 // don't show the dates --- unless the start is anything displayed as 00:00, in which case 86 // we include both dates to disambiguate. 87 // This is not the behavior of icu4j's DateIntervalFormat, but it's the required behavior 88 // of Android's DateUtils.formatDateRange. 89 if (isExactlyMidnight(endCalendar)) { 90 boolean showTime = (flags & FORMAT_SHOW_TIME) == FORMAT_SHOW_TIME; 91 boolean endsDayAfterStart = DateUtilsBridge.dayDistance(startCalendar, endCalendar) 92 == 1; 93 if ((!showTime && startMs != endMs) 94 || (endsDayAfterStart 95 && !DateUtilsBridge.isDisplayMidnightUsingSkeleton(startCalendar))) { 96 endCalendar.add(Calendar.DAY_OF_MONTH, -1); 97 } 98 } 99 100 String skeleton = DateUtilsBridge.toSkeleton(startCalendar, endCalendar, flags); 101 synchronized (CACHED_FORMATTERS) { 102 android.icu.text.DateIntervalFormat formatter = 103 getFormatter(skeleton, icuLocale, icuTimeZone); 104 return formatter.format(startCalendar, endCalendar, new StringBuffer(), 105 new FieldPosition(0)).toString(); 106 } 107 } 108 getFormatter(String skeleton, ULocale locale, android.icu.util.TimeZone icuTimeZone)109 private static android.icu.text.DateIntervalFormat getFormatter(String skeleton, ULocale locale, 110 android.icu.util.TimeZone icuTimeZone) { 111 String key = skeleton + "\t" + locale + "\t" + icuTimeZone; 112 android.icu.text.DateIntervalFormat formatter = CACHED_FORMATTERS.get(key); 113 if (formatter != null) { 114 return formatter; 115 } 116 formatter = android.icu.text.DateIntervalFormat.getInstance(skeleton, locale); 117 formatter.setTimeZone(icuTimeZone); 118 CACHED_FORMATTERS.put(key, formatter); 119 return formatter; 120 } 121 isExactlyMidnight(Calendar c)122 private static boolean isExactlyMidnight(Calendar c) { 123 return c.get(Calendar.HOUR_OF_DAY) == 0 124 && c.get(Calendar.MINUTE) == 0 125 && c.get(Calendar.SECOND) == 0 126 && c.get(Calendar.MILLISECOND) == 0; 127 } 128 129 130 @VisibleForTesting(visibility = PACKAGE) isLibcoreVFlagEnabled()131 public static boolean isLibcoreVFlagEnabled() { 132 // Note that the Flags class is expected to be jarjar-ed in the build-time. 133 // See go/repackage_flags 134 // The full-qualified name should be like 135 // com.android.internal.hidden_from_bootclasspath.com.android.libcore.Flags 136 return Flags.vApis(); 137 } 138 } 139