1 /* 2 * Copyright (C) 2015 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_12HOUR; 20 import static android.text.format.DateUtils.FORMAT_24HOUR; 21 import static android.text.format.DateUtils.FORMAT_ABBREV_ALL; 22 import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; 23 import static android.text.format.DateUtils.FORMAT_ABBREV_TIME; 24 import static android.text.format.DateUtils.FORMAT_ABBREV_WEEKDAY; 25 import static android.text.format.DateUtils.FORMAT_NO_MONTH_DAY; 26 import static android.text.format.DateUtils.FORMAT_NO_YEAR; 27 import static android.text.format.DateUtils.FORMAT_NUMERIC_DATE; 28 import static android.text.format.DateUtils.FORMAT_SHOW_DATE; 29 import static android.text.format.DateUtils.FORMAT_SHOW_TIME; 30 import static android.text.format.DateUtils.FORMAT_SHOW_WEEKDAY; 31 import static android.text.format.DateUtils.FORMAT_SHOW_YEAR; 32 33 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 34 35 import android.icu.util.Calendar; 36 import android.icu.util.GregorianCalendar; 37 import android.icu.util.TimeZone; 38 import android.icu.util.ULocale; 39 40 import com.android.internal.annotations.VisibleForTesting; 41 42 /** 43 * Common methods and constants for the various ICU formatters used to support {@link 44 * android.text.format.DateUtils}. 45 * 46 * @hide 47 */ 48 @VisibleForTesting(visibility = PACKAGE) 49 @android.ravenwood.annotation.RavenwoodKeepWholeClass 50 public final class DateUtilsBridge { 51 52 /** 53 * Creates an immutable ICU timezone backed by the specified libcore timezone data. At the time 54 * of writing the libcore implementation is faster but restricted to 1902 - 2038. Callers must 55 * not modify the {@code tz} after calling this method. 56 */ icuTimeZone(java.util.TimeZone tz)57 public static TimeZone icuTimeZone(java.util.TimeZone tz) { 58 TimeZone icuTimeZone = TimeZone.getTimeZone(tz.getID()); 59 icuTimeZone.freeze(); // Optimization - allows the timezone to be copied cheaply. 60 return icuTimeZone; 61 } 62 63 /** 64 * Create a GregorianCalendar based on the arguments 65 */ createIcuCalendar(TimeZone icuTimeZone, ULocale icuLocale, long timeInMillis)66 public static Calendar createIcuCalendar(TimeZone icuTimeZone, ULocale icuLocale, 67 long timeInMillis) { 68 Calendar calendar = new GregorianCalendar(icuTimeZone, icuLocale); 69 calendar.setTimeInMillis(timeInMillis); 70 return calendar; 71 } 72 toSkeleton(Calendar calendar, int flags)73 public static String toSkeleton(Calendar calendar, int flags) { 74 return toSkeleton(calendar, calendar, flags); 75 } 76 toSkeleton(Calendar startCalendar, Calendar endCalendar, int flags)77 public static String toSkeleton(Calendar startCalendar, Calendar endCalendar, int flags) { 78 if ((flags & FORMAT_ABBREV_ALL) != 0) { 79 flags |= FORMAT_ABBREV_MONTH | FORMAT_ABBREV_TIME | FORMAT_ABBREV_WEEKDAY; 80 } 81 82 String monthPart = "MMMM"; 83 if ((flags & FORMAT_NUMERIC_DATE) != 0) { 84 monthPart = "M"; 85 } else if ((flags & FORMAT_ABBREV_MONTH) != 0) { 86 monthPart = "MMM"; 87 } 88 89 String weekPart = "EEEE"; 90 if ((flags & FORMAT_ABBREV_WEEKDAY) != 0) { 91 weekPart = "EEE"; 92 } 93 94 String timePart = "j"; // "j" means choose 12 or 24 hour based on current locale. 95 if ((flags & FORMAT_24HOUR) != 0) { 96 timePart = "H"; 97 } else if ((flags & FORMAT_12HOUR) != 0) { 98 timePart = "h"; 99 } 100 101 // If we've not been asked to abbreviate times, or we're using the 24-hour clock (where it 102 // never makes sense to leave out the minutes), include minutes. This gets us times like 103 // "4 PM" while avoiding times like "16" (for "16:00"). 104 if ((flags & FORMAT_ABBREV_TIME) == 0 || (flags & FORMAT_24HOUR) != 0) { 105 timePart += "m"; 106 } else { 107 // Otherwise, we're abbreviating a 12-hour time, and should only show the minutes 108 // if they're not both "00". 109 if (!(onTheHour(startCalendar) && onTheHour(endCalendar))) { 110 timePart = timePart + "m"; 111 } 112 } 113 114 if (fallOnDifferentDates(startCalendar, endCalendar)) { 115 flags |= FORMAT_SHOW_DATE; 116 } 117 118 if (fallInSameMonth(startCalendar, endCalendar) && (flags & FORMAT_NO_MONTH_DAY) != 0) { 119 flags &= (~FORMAT_SHOW_WEEKDAY); 120 flags &= (~FORMAT_SHOW_TIME); 121 } 122 123 if ((flags & (FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_SHOW_WEEKDAY)) == 0) { 124 flags |= FORMAT_SHOW_DATE; 125 } 126 127 // If we've been asked to show the date, work out whether we think we should show the year. 128 if ((flags & FORMAT_SHOW_DATE) != 0) { 129 if ((flags & FORMAT_SHOW_YEAR) != 0) { 130 // The caller explicitly wants us to show the year. 131 } else if ((flags & FORMAT_NO_YEAR) != 0) { 132 // The caller explicitly doesn't want us to show the year, even if we otherwise 133 // would. 134 } else if (!fallInSameYear(startCalendar, endCalendar) || !isThisYear(startCalendar)) { 135 flags |= FORMAT_SHOW_YEAR; 136 } 137 } 138 139 StringBuilder builder = new StringBuilder(); 140 if ((flags & (FORMAT_SHOW_DATE | FORMAT_NO_MONTH_DAY)) != 0) { 141 if ((flags & FORMAT_SHOW_YEAR) != 0) { 142 builder.append("y"); 143 } 144 builder.append(monthPart); 145 if ((flags & FORMAT_NO_MONTH_DAY) == 0) { 146 builder.append("d"); 147 } 148 } 149 if ((flags & FORMAT_SHOW_WEEKDAY) != 0) { 150 builder.append(weekPart); 151 } 152 if ((flags & FORMAT_SHOW_TIME) != 0) { 153 builder.append(timePart); 154 } 155 return builder.toString(); 156 } 157 dayDistance(Calendar c1, Calendar c2)158 public static int dayDistance(Calendar c1, Calendar c2) { 159 return c2.get(Calendar.JULIAN_DAY) - c1.get(Calendar.JULIAN_DAY); 160 } 161 162 /** 163 * Returns whether the argument will be displayed as if it were midnight, using any of the 164 * skeletons provided by {@link #toSkeleton}. 165 */ isDisplayMidnightUsingSkeleton(Calendar c)166 public static boolean isDisplayMidnightUsingSkeleton(Calendar c) { 167 // All the skeletons returned by toSkeleton have minute precision (they may abbreviate 168 // 4:00 PM to 4 PM but will still show the following minute as 4:01 PM). 169 return c.get(Calendar.HOUR_OF_DAY) == 0 && c.get(Calendar.MINUTE) == 0; 170 } 171 onTheHour(Calendar c)172 private static boolean onTheHour(Calendar c) { 173 return c.get(Calendar.MINUTE) == 0 && c.get(Calendar.SECOND) == 0; 174 } 175 fallOnDifferentDates(Calendar c1, Calendar c2)176 private static boolean fallOnDifferentDates(Calendar c1, Calendar c2) { 177 return c1.get(Calendar.YEAR) != c2.get(Calendar.YEAR) 178 || c1.get(Calendar.MONTH) != c2.get(Calendar.MONTH) 179 || c1.get(Calendar.DAY_OF_MONTH) != c2.get(Calendar.DAY_OF_MONTH); 180 } 181 fallInSameMonth(Calendar c1, Calendar c2)182 private static boolean fallInSameMonth(Calendar c1, Calendar c2) { 183 return c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH); 184 } 185 fallInSameYear(Calendar c1, Calendar c2)186 private static boolean fallInSameYear(Calendar c1, Calendar c2) { 187 return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR); 188 } 189 isThisYear(Calendar c)190 private static boolean isThisYear(Calendar c) { 191 Calendar now = (Calendar) c.clone(); 192 now.setTimeInMillis(System.currentTimeMillis()); 193 return c.get(Calendar.YEAR) == now.get(Calendar.YEAR); 194 } 195 } 196