1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // 2016 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html#License 4 /* 5 ******************************************************************************* 6 * Copyright (C) 2007-2016, International Business Machines Corporation and 7 * others. All Rights Reserved. 8 ******************************************************************************* 9 */ 10 package android.icu.impl; 11 12 import java.text.FieldPosition; 13 import java.text.ParsePosition; 14 import java.util.ArrayList; 15 import java.util.Date; 16 import java.util.List; 17 import java.util.MissingResourceException; 18 19 import android.icu.lang.UCharacter; 20 import android.icu.text.BreakIterator; 21 import android.icu.text.DateFormat; 22 import android.icu.text.DisplayContext; 23 import android.icu.text.MessageFormat; 24 import android.icu.text.SimpleDateFormat; 25 import android.icu.util.Calendar; 26 import android.icu.util.TimeZone; 27 import android.icu.util.ULocale; 28 import android.icu.util.UResourceBundle; 29 30 /** 31 * @author srl 32 * @hide Only a subset of ICU is exposed in Android 33 */ 34 public class RelativeDateFormat extends DateFormat { 35 36 /** 37 * @author srl 38 * 39 */ 40 public static class URelativeString { 41 URelativeString(int offset, String string) { 42 this.offset = offset; 43 this.string = string; 44 } 45 URelativeString(String offset, String string) { 46 this.offset = Integer.parseInt(offset); 47 this.string = string; 48 } 49 public int offset; 50 public String string; 51 } 52 53 // copy c'tor? 54 55 /** 56 * @param timeStyle The time style for the date and time. 57 * @param dateStyle The date style for the date and time. 58 * @param locale The locale for the date. 59 * @param cal The calendar to be used 60 */ 61 public RelativeDateFormat(int timeStyle, int dateStyle, ULocale locale, Calendar cal) { 62 calendar = cal; 63 64 fLocale = locale; 65 fTimeStyle = timeStyle; 66 fDateStyle = dateStyle; 67 68 if (fDateStyle != DateFormat.NONE) { 69 int newStyle = fDateStyle & ~DateFormat.RELATIVE; 70 DateFormat df = DateFormat.getDateInstance(newStyle, locale); 71 if (df instanceof SimpleDateFormat) { 72 fDateTimeFormat = (SimpleDateFormat)df; 73 } else { 74 throw new IllegalArgumentException("Can't create SimpleDateFormat for date style"); 75 } 76 fDatePattern = fDateTimeFormat.toPattern(); 77 if (fTimeStyle != DateFormat.NONE) { 78 newStyle = fTimeStyle & ~DateFormat.RELATIVE; 79 df = DateFormat.getTimeInstance(newStyle, locale); 80 if (df instanceof SimpleDateFormat) { 81 fTimePattern = ((SimpleDateFormat)df).toPattern(); 82 } 83 } 84 } else { 85 // does not matter whether timeStyle is UDAT_NONE, we need something for fDateTimeFormat 86 int newStyle = fTimeStyle & ~DateFormat.RELATIVE; 87 DateFormat df = DateFormat.getTimeInstance(newStyle, locale); 88 if (df instanceof SimpleDateFormat) { 89 fDateTimeFormat = (SimpleDateFormat)df; 90 } else { 91 throw new IllegalArgumentException("Can't create SimpleDateFormat for time style"); 92 } 93 fTimePattern = fDateTimeFormat.toPattern(); 94 } 95 96 initializeCalendar(null, fLocale); 97 loadDates(); 98 initializeCombinedFormat(calendar, fLocale); 99 } 100 101 /** 102 * serial version (generated) 103 */ 104 private static final long serialVersionUID = 1131984966440549435L; 105 106 /* (non-Javadoc) 107 * @see android.icu.text.DateFormat#format(android.icu.util.Calendar, java.lang.StringBuffer, java.text.FieldPosition) 108 */ 109 @Override 110 public StringBuffer format(Calendar cal, StringBuffer toAppendTo, 111 FieldPosition fieldPosition) { 112 113 String relativeDayString = null; 114 DisplayContext capitalizationContext = getContext(DisplayContext.Type.CAPITALIZATION); 115 116 if (fDateStyle != DateFormat.NONE) { 117 // calculate the difference, in days, between 'cal' and now. 118 int dayDiff = dayDifference(cal); 119 120 // look up string 121 relativeDayString = getStringForDay(dayDiff); 122 } 123 124 if (fDateTimeFormat != null) { 125 if (relativeDayString != null && fDatePattern != null && 126 (fTimePattern == null || fCombinedFormat == null || combinedFormatHasDateAtStart) ) { 127 // capitalize relativeDayString according to context for relative, set formatter no context 128 if ( relativeDayString.length() > 0 && UCharacter.isLowerCase(relativeDayString.codePointAt(0)) && 129 (capitalizationContext == DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE || 130 (capitalizationContext == DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU && capitalizationOfRelativeUnitsForListOrMenu) || 131 (capitalizationContext == DisplayContext.CAPITALIZATION_FOR_STANDALONE && capitalizationOfRelativeUnitsForStandAlone) )) { 132 if (capitalizationBrkIter == null) { 133 // should only happen when deserializing, etc. 134 capitalizationBrkIter = BreakIterator.getSentenceInstance(fLocale); 135 } 136 relativeDayString = UCharacter.toTitleCase(fLocale, relativeDayString, capitalizationBrkIter, 137 UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT); 138 } 139 fDateTimeFormat.setContext(DisplayContext.CAPITALIZATION_NONE); 140 } else { 141 // set our context for the formatter 142 fDateTimeFormat.setContext(capitalizationContext); 143 } 144 } 145 146 if (fDateTimeFormat != null && (fDatePattern != null || fTimePattern != null)) { 147 // The new way 148 if (fDatePattern == null) { 149 // must have fTimePattern 150 fDateTimeFormat.applyPattern(fTimePattern); 151 fDateTimeFormat.format(cal, toAppendTo, fieldPosition); 152 } else if (fTimePattern == null) { 153 // must have fDatePattern 154 if (relativeDayString != null) { 155 toAppendTo.append(relativeDayString); 156 } else { 157 fDateTimeFormat.applyPattern(fDatePattern); 158 fDateTimeFormat.format(cal, toAppendTo, fieldPosition); 159 } 160 } else { 161 String datePattern = fDatePattern; // default; 162 if (relativeDayString != null) { 163 // Need to quote the relativeDayString to make it a legal date pattern 164 datePattern = "'" + relativeDayString.replace("'", "''") + "'"; 165 } 166 StringBuffer combinedPattern = new StringBuffer(""); 167 fCombinedFormat.format(new Object[] {fTimePattern, datePattern}, combinedPattern, new FieldPosition(0)); 168 fDateTimeFormat.applyPattern(combinedPattern.toString()); 169 fDateTimeFormat.format(cal, toAppendTo, fieldPosition); 170 } 171 } else if (fDateFormat != null) { 172 // A subset of the old way, for serialization compatibility 173 // (just do the date part) 174 if (relativeDayString != null) { 175 toAppendTo.append(relativeDayString); 176 } else { 177 fDateFormat.format(cal, toAppendTo, fieldPosition); 178 } 179 } 180 181 return toAppendTo; 182 } 183 184 /* (non-Javadoc) 185 * @see android.icu.text.DateFormat#parse(java.lang.String, android.icu.util.Calendar, java.text.ParsePosition) 186 */ 187 @Override 188 public void parse(String text, Calendar cal, ParsePosition pos) { 189 throw new UnsupportedOperationException("Relative Date parse is not implemented yet"); 190 } 191 192 /* (non-Javadoc) 193 * @see android.icu.text.DateFormat#setContext(android.icu.text.DisplayContext) 194 * Here we override the DateFormat implementation in order to 195 * lazily initialize relevant items 196 */ 197 @Override 198 public void setContext(DisplayContext context) { 199 super.setContext(context); 200 if (!capitalizationInfoIsSet && 201 (context==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU || context==DisplayContext.CAPITALIZATION_FOR_STANDALONE)) { 202 initCapitalizationContextInfo(fLocale); 203 capitalizationInfoIsSet = true; 204 } 205 if (capitalizationBrkIter == null && (context==DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE || 206 (context==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU && capitalizationOfRelativeUnitsForListOrMenu) || 207 (context==DisplayContext.CAPITALIZATION_FOR_STANDALONE && capitalizationOfRelativeUnitsForStandAlone) )) { 208 capitalizationBrkIter = BreakIterator.getSentenceInstance(fLocale); 209 } 210 } 211 212 private DateFormat fDateFormat; // keep for serialization compatibility 213 @SuppressWarnings("unused") 214 private DateFormat fTimeFormat; // now unused, keep for serialization compatibility 215 private MessageFormat fCombinedFormat; // the {0} {1} format. 216 private SimpleDateFormat fDateTimeFormat = null; // the held date/time formatter 217 private String fDatePattern = null; 218 private String fTimePattern = null; 219 220 int fDateStyle; 221 int fTimeStyle; 222 ULocale fLocale; 223 224 private transient List<URelativeString> fDates = null; 225 226 private boolean combinedFormatHasDateAtStart = false; 227 private boolean capitalizationInfoIsSet = false; 228 private boolean capitalizationOfRelativeUnitsForListOrMenu = false; 229 private boolean capitalizationOfRelativeUnitsForStandAlone = false; 230 private transient BreakIterator capitalizationBrkIter = null; 231 232 /** 233 * Get the string at a specific offset. 234 * @param day day offset ( -1, 0, 1, etc.. ). Does not require sorting by offset. 235 * @return the string, or NULL if none at that location. 236 */ 237 private String getStringForDay(int day) { 238 if(fDates == null) { 239 loadDates(); 240 } 241 for(URelativeString dayItem : fDates) { 242 if(dayItem.offset == day) { 243 return dayItem.string; 244 } 245 } 246 return null; 247 } 248 249 // Sink to get "fields/day/relative". 250 private final class RelDateFmtDataSink extends UResource.Sink { 251 252 @Override 253 public void put(UResource.Key key, UResource.Value value, boolean noFallback) { 254 if (value.getType() == ICUResourceBundle.ALIAS) { 255 return; 256 } 257 258 UResource.Table table = value.getTable(); 259 for (int i = 0; table.getKeyAndValue(i, key, value); ++i) { 260 261 int keyOffset; 262 try { 263 keyOffset = Integer.parseInt(key.toString()); 264 } 265 catch (NumberFormatException nfe) { 266 // Flag the error? 267 return; 268 } 269 // Check if already set. 270 if (getStringForDay(keyOffset) == null) { 271 URelativeString newDayInfo = new URelativeString(keyOffset, value.getString()); 272 fDates.add(newDayInfo); 273 } 274 } 275 } 276 } 277 278 /** 279 * Load the Date string array 280 */ 281 private synchronized void loadDates() { 282 ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, fLocale); 283 284 // Use sink mechanism to traverse data structure. 285 fDates = new ArrayList<URelativeString>(); 286 RelDateFmtDataSink sink = new RelDateFmtDataSink(); 287 rb.getAllItemsWithFallback("fields/day/relative", sink); 288 } 289 290 /** 291 * Set capitalizationOfRelativeUnitsForListOrMenu, capitalizationOfRelativeUnitsForStandAlone 292 */ 293 private void initCapitalizationContextInfo(ULocale locale) { 294 ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale); 295 try { 296 ICUResourceBundle rdb = rb.getWithFallback("contextTransforms/relative"); 297 int[] intVector = rdb.getIntVector(); 298 if (intVector.length >= 2) { 299 capitalizationOfRelativeUnitsForListOrMenu = (intVector[0] != 0); 300 capitalizationOfRelativeUnitsForStandAlone = (intVector[1] != 0); 301 } 302 } catch (MissingResourceException e) { 303 // use default 304 } 305 } 306 307 /** 308 * @return the number of days in "until-now" 309 */ 310 private static int dayDifference(Calendar until) { 311 Calendar nowCal = (Calendar)until.clone(); 312 Date nowDate = new Date(System.currentTimeMillis()); 313 nowCal.clear(); 314 nowCal.setTime(nowDate); 315 int dayDiff = until.get(Calendar.JULIAN_DAY) - nowCal.get(Calendar.JULIAN_DAY); 316 return dayDiff; 317 } 318 319 /** 320 * initializes fCalendar from parameters. Returns fCalendar as a convenience. 321 * @param zone Zone to be adopted, or NULL for TimeZone::createDefault(). 322 * @param locale Locale of the calendar 323 * @param status Error code 324 * @return the newly constructed fCalendar 325 */ 326 private Calendar initializeCalendar(TimeZone zone, ULocale locale) { 327 if (calendar == null) { 328 if(zone == null) { 329 calendar = Calendar.getInstance(locale); 330 } else { 331 calendar = Calendar.getInstance(zone, locale); 332 } 333 } 334 return calendar; 335 } 336 337 private MessageFormat initializeCombinedFormat(Calendar cal, ULocale locale) { 338 String pattern; 339 ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance( 340 ICUData.ICU_BASE_NAME, locale); 341 String resourcePath = "calendar/" + cal.getType() + "/DateTimePatterns"; 342 ICUResourceBundle patternsRb= rb.findWithFallback(resourcePath); 343 if (patternsRb == null && !cal.getType().equals("gregorian")) { 344 // Try again with gregorian, if not already attempted. 345 patternsRb = rb.findWithFallback("calendar/gregorian/DateTimePatterns"); 346 } 347 348 if (patternsRb == null || patternsRb.getSize() < 9) { 349 // Undefined or too few elements. 350 pattern = "{1} {0}"; 351 } else { 352 int glueIndex = 8; 353 if (patternsRb.getSize() >= 13) { 354 if (fDateStyle >= DateFormat.FULL && fDateStyle <= DateFormat.SHORT) { 355 glueIndex += fDateStyle + 1; 356 } else 357 if (fDateStyle >= DateFormat.RELATIVE_FULL && 358 fDateStyle <= DateFormat.RELATIVE_SHORT) { 359 glueIndex += fDateStyle + 1 - DateFormat.RELATIVE; 360 } 361 } 362 int elementType = patternsRb.get(glueIndex).getType(); 363 if (elementType == UResourceBundle.ARRAY) { 364 pattern = patternsRb.get(glueIndex).getString(0); 365 } else { 366 pattern = patternsRb.getString(glueIndex); 367 } 368 } 369 combinedFormatHasDateAtStart = pattern.startsWith("{1}"); 370 fCombinedFormat = new MessageFormat(pattern, locale); 371 return fCombinedFormat; 372 } 373 } 374