1 /* 2 * Copyright (C) 2009 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 libcore.icu; 18 19 import com.android.icu.util.ExtendedCalendar; 20 import java.text.DateFormat; 21 import java.text.SimpleDateFormat; 22 import java.util.Locale; 23 import java.util.Objects; 24 import java.util.concurrent.ConcurrentHashMap; 25 26 /** 27 * Pattern cache for {@link SimpleDateFormat} 28 * 29 * @hide 30 */ 31 public class SimpleDateFormatData { 32 33 // TODO(http://b/217881004): Replace this with a LRU cache. 34 private static final ConcurrentHashMap<String, SimpleDateFormatData> CACHE = 35 new ConcurrentHashMap<>(/* initialCapacity */ 3); 36 37 private final Locale locale; 38 39 private final String fullTimeFormat; 40 private final String longTimeFormat; 41 private final String mediumTimeFormat; 42 private final String shortTimeFormat; 43 44 private final String fullDateFormat; 45 private final String longDateFormat; 46 private final String mediumDateFormat; 47 private final String shortDateFormat; 48 SimpleDateFormatData(Locale locale)49 private SimpleDateFormatData(Locale locale) { 50 this.locale = locale; 51 52 // libcore's java.text supports Gregorian calendar only. 53 ExtendedCalendar extendedCalendar = ICU.getExtendedCalendar(locale, "gregorian"); 54 55 String tmpFullTimeFormat = getDateTimeFormatString(extendedCalendar, 56 android.icu.text.DateFormat.NONE, android.icu.text.DateFormat.FULL); 57 58 // Fix up a couple of patterns. 59 if (tmpFullTimeFormat != null) { 60 // There are some full time format patterns in ICU that use the pattern character 'v'. 61 // Java doesn't accept this, so we replace it with 'z' which has about the same result 62 // as 'v', the timezone name. 63 // 'v' -> "PT", 'z' -> "PST", v is the generic timezone and z the standard tz 64 // "vvvv" -> "Pacific Time", "zzzz" -> "Pacific Standard Time" 65 tmpFullTimeFormat = tmpFullTimeFormat.replace('v', 'z'); 66 } 67 fullTimeFormat = tmpFullTimeFormat; 68 69 longTimeFormat = getDateTimeFormatString(extendedCalendar, 70 android.icu.text.DateFormat.NONE, android.icu.text.DateFormat.LONG); 71 mediumTimeFormat = getDateTimeFormatString(extendedCalendar, 72 android.icu.text.DateFormat.NONE, android.icu.text.DateFormat.MEDIUM); 73 shortTimeFormat = getDateTimeFormatString(extendedCalendar, 74 android.icu.text.DateFormat.NONE, android.icu.text.DateFormat.SHORT); 75 fullDateFormat = getDateTimeFormatString(extendedCalendar, 76 android.icu.text.DateFormat.FULL, android.icu.text.DateFormat.NONE); 77 longDateFormat = getDateTimeFormatString(extendedCalendar, 78 android.icu.text.DateFormat.LONG, android.icu.text.DateFormat.NONE); 79 mediumDateFormat = getDateTimeFormatString(extendedCalendar, 80 android.icu.text.DateFormat.MEDIUM, android.icu.text.DateFormat.NONE); 81 shortDateFormat = getDateTimeFormatString(extendedCalendar, 82 android.icu.text.DateFormat.SHORT, android.icu.text.DateFormat.NONE); 83 } 84 85 /** 86 * Returns an instance. 87 * 88 * @param locale can't be null 89 * @throws NullPointerException if {@code locale} is null 90 * @return a {@link SimpleDateFormatData} instance 91 */ getInstance(Locale locale)92 public static SimpleDateFormatData getInstance(Locale locale) { 93 Objects.requireNonNull(locale, "locale can't be null"); 94 95 locale = LocaleData.getCompatibleLocaleForBug159514442(locale); 96 97 final String languageTag = locale.toLanguageTag(); 98 99 SimpleDateFormatData data = CACHE.get(languageTag); 100 if (data != null) { 101 return data; 102 } 103 104 data = new SimpleDateFormatData(locale); 105 SimpleDateFormatData prev = CACHE.putIfAbsent(languageTag, data); 106 if (prev != null) { 107 return prev; 108 } 109 return data; 110 } 111 112 /** 113 * Ensure that we pull in the locale data for the root locale, en_US, and the user's default 114 * locale. All devices must support the root locale and en_US, and they're used for various 115 * system things. Pre-populating the cache is especially useful on Android because 116 * we'll share this via the Zygote. 117 */ initializeCacheInZygote()118 public static void initializeCacheInZygote() { 119 getInstance(Locale.ROOT); 120 getInstance(Locale.US); 121 getInstance(Locale.getDefault()); 122 } 123 124 /** 125 * @throws AssertionError if style is not one of the 4 styles specified in {@link DateFormat} 126 * @return a date pattern string 127 */ getDateFormat(int style)128 public String getDateFormat(int style) { 129 switch (style) { 130 case DateFormat.SHORT: 131 return shortDateFormat; 132 case DateFormat.MEDIUM: 133 return mediumDateFormat; 134 case DateFormat.LONG: 135 return longDateFormat; 136 case DateFormat.FULL: 137 return fullDateFormat; 138 } 139 // TODO: fix this legacy behavior of throwing AssertionError introduced in 140 // the commit 6ca85c4. 141 throw new AssertionError(); 142 } 143 144 /** 145 * @throws AssertionError if style is not one of the 4 styles specified in {@link DateFormat} 146 * @return a time pattern string 147 */ getTimeFormat(int style)148 public String getTimeFormat(int style) { 149 // Do not cache ICU.getTimePattern() return value in the LocaleData instance 150 // because most users do not enable this setting, hurts performance in critical path, 151 // e.g. b/161846393, and ICU.getBestDateTimePattern will cache it in ICU.CACHED_PATTERNS 152 // on demand. 153 switch (style) { 154 case DateFormat.SHORT: 155 if (DateFormat.is24Hour == null) { 156 return shortTimeFormat; 157 } else { 158 return ICU.getTimePattern(locale, DateFormat.is24Hour, false); 159 } 160 case DateFormat.MEDIUM: 161 if (DateFormat.is24Hour == null) { 162 return mediumTimeFormat; 163 } else { 164 return ICU.getTimePattern(locale, DateFormat.is24Hour, true); 165 } 166 case DateFormat.LONG: 167 // CLDR doesn't really have anything we can use to obey the 12-/24-hour preference. 168 return longTimeFormat; 169 case DateFormat.FULL: 170 // CLDR doesn't really have anything we can use to obey the 12-/24-hour preference. 171 return fullTimeFormat; 172 } 173 // TODO: fix this legacy behavior of throwing AssertionError introduced in 174 // the commit 6ca85c4. 175 throw new AssertionError(); 176 } 177 getDateTimeFormatString(ExtendedCalendar extendedCalendar, int dateStyle, int timeStyle)178 private static String getDateTimeFormatString(ExtendedCalendar extendedCalendar, 179 int dateStyle, int timeStyle) { 180 return ICU.transformIcuDateTimePattern_forJavaText( 181 extendedCalendar.getDateTimePattern(dateStyle, timeStyle)); 182 } 183 184 } 185