• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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