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