Home | History | Annotate | Download | only in impl
      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