Home | History | Annotate | Download | only in format
      1 /*
      2  * Copyright (C) 2006 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 android.content.Context;
     20 import android.provider.Settings;
     21 import android.text.SpannableStringBuilder;
     22 import android.text.Spanned;
     23 import android.text.SpannedString;
     24 
     25 import com.android.internal.R;
     26 
     27 import java.util.Calendar;
     28 import java.util.Date;
     29 import java.util.GregorianCalendar;
     30 import java.util.Locale;
     31 import java.util.TimeZone;
     32 import java.text.SimpleDateFormat;
     33 
     34 /**
     35     Utility class for producing strings with formatted date/time.
     36 
     37     <p>
     38     This class takes as inputs a format string and a representation of a date/time.
     39     The format string controls how the output is generated.
     40     </p>
     41     <p>
     42     Formatting characters may be repeated in order to get more detailed representations
     43     of that field.  For instance, the format character &apos;M&apos; is used to
     44     represent the month.  Depending on how many times that character is repeated
     45     you get a different representation.
     46     </p>
     47     <p>
     48     For the month of September:<br/>
     49     M -&gt; 9<br/>
     50     MM -&gt; 09<br/>
     51     MMM -&gt; Sep<br/>
     52     MMMM -&gt; September
     53     </p>
     54     <p>
     55     The effects of the duplication vary depending on the nature of the field.
     56     See the notes on the individual field formatters for details.  For purely numeric
     57     fields such as <code>HOUR</code> adding more copies of the designator will
     58     zero-pad the value to that number of characters.
     59     </p>
     60     <p>
     61     For 7 minutes past the hour:<br/>
     62     m -&gt; 7<br/>
     63     mm -&gt; 07<br/>
     64     mmm -&gt; 007<br/>
     65     mmmm -&gt; 0007
     66     </p>
     67     <p>
     68     Examples for April 6, 1970 at 3:23am:<br/>
     69     &quot;MM/dd/yy h:mmaa&quot; -&gt; &quot;04/06/70 3:23am&quot<br/>
     70     &quot;MMM dd, yyyy h:mmaa&quot; -&gt; &quot;Apr 6, 1970 3:23am&quot<br/>
     71     &quot;MMMM dd, yyyy h:mmaa&quot; -&gt; &quot;April 6, 1970 3:23am&quot<br/>
     72     &quot;E, MMMM dd, yyyy h:mmaa&quot; -&gt; &quot;Mon, April 6, 1970 3:23am&<br/>
     73     &quot;EEEE, MMMM dd, yyyy h:mmaa&quot; -&gt; &quot;Monday, April 6, 1970 3:23am&quot;<br/>
     74     &quot;&apos;Noteworthy day: &apos;M/d/yy&quot; -&gt; &quot;Noteworthy day: 4/6/70&quot;
     75  */
     76 
     77 public class DateFormat {
     78     /**
     79         Text in the format string that should be copied verbatim rather that
     80         interpreted as formatting codes must be surrounded by the <code>QUOTE</code>
     81         character.  If you need to embed a literal <code>QUOTE</code> character in
     82         the output text then use two in a row.
     83      */
     84     public  static final char    QUOTE                  =    '\'';
     85 
     86     /**
     87         This designator indicates whether the <code>HOUR</code> field is before
     88         or after noon.  The output is lower-case.
     89 
     90         Examples:
     91         a -> a or p
     92         aa -> am or pm
     93      */
     94     public  static final char    AM_PM                  =    'a';
     95 
     96     /**
     97         This designator indicates whether the <code>HOUR</code> field is before
     98         or after noon.  The output is capitalized.
     99 
    100         Examples:
    101         A -> A or P
    102         AA -> AM or PM
    103      */
    104     public  static final char    CAPITAL_AM_PM          =    'A';
    105 
    106     /**
    107         This designator indicates the day of the month.
    108 
    109         Examples for the 9th of the month:
    110         d -> 9
    111         dd -> 09
    112      */
    113     public  static final char    DATE                   =    'd';
    114 
    115     /**
    116         This designator indicates the name of the day of the week.
    117 
    118         Examples for Sunday:
    119         E -> Sun
    120         EEEE -> Sunday
    121      */
    122     public  static final char    DAY                    =    'E';
    123 
    124     /**
    125         This designator indicates the hour of the day in 12 hour format.
    126 
    127         Examples for 3pm:
    128         h -> 3
    129         hh -> 03
    130      */
    131     public  static final char    HOUR                   =    'h';
    132 
    133     /**
    134         This designator indicates the hour of the day in 24 hour format.
    135 
    136         Example for 3pm:
    137         k -> 15
    138 
    139         Examples for midnight:
    140         k -> 0
    141         kk -> 00
    142      */
    143     public  static final char    HOUR_OF_DAY            =    'k';
    144 
    145     /**
    146         This designator indicates the minute of the hour.
    147 
    148         Examples for 7 minutes past the hour:
    149         m -> 7
    150         mm -> 07
    151      */
    152     public  static final char    MINUTE                 =    'm';
    153 
    154     /**
    155         This designator indicates the month of the year
    156 
    157         Examples for September:
    158         M -> 9
    159         MM -> 09
    160         MMM -> Sep
    161         MMMM -> September
    162      */
    163     public  static final char    MONTH                  =    'M';
    164 
    165     /**
    166         This designator indicates the seconds of the minute.
    167 
    168         Examples for 7 seconds past the minute:
    169         s -> 7
    170         ss -> 07
    171      */
    172     public  static final char    SECONDS                =    's';
    173 
    174     /**
    175         This designator indicates the offset of the timezone from GMT.
    176 
    177         Example for US/Pacific timezone:
    178         z -> -0800
    179         zz -> PST
    180      */
    181     public  static final char    TIME_ZONE              =    'z';
    182 
    183     /**
    184         This designator indicates the year.
    185 
    186         Examples for 2006
    187         y -> 06
    188         yyyy -> 2006
    189      */
    190     public  static final char    YEAR                   =    'y';
    191 
    192 
    193     private static final Object sLocaleLock = new Object();
    194     private static Locale sIs24HourLocale;
    195     private static boolean sIs24Hour;
    196 
    197 
    198     /**
    199      * Returns true if user preference is set to 24-hour format.
    200      * @param context the context to use for the content resolver
    201      * @return true if 24 hour time format is selected, false otherwise.
    202      */
    203     public static boolean is24HourFormat(Context context) {
    204         String value = Settings.System.getString(context.getContentResolver(),
    205                 Settings.System.TIME_12_24);
    206 
    207         if (value == null) {
    208             Locale locale = context.getResources().getConfiguration().locale;
    209 
    210             synchronized (sLocaleLock) {
    211                 if (sIs24HourLocale != null && sIs24HourLocale.equals(locale)) {
    212                     return sIs24Hour;
    213                 }
    214             }
    215 
    216             java.text.DateFormat natural =
    217                 java.text.DateFormat.getTimeInstance(
    218                     java.text.DateFormat.LONG, locale);
    219 
    220             if (natural instanceof SimpleDateFormat) {
    221                 SimpleDateFormat sdf = (SimpleDateFormat) natural;
    222                 String pattern = sdf.toPattern();
    223 
    224                 if (pattern.indexOf('H') >= 0) {
    225                     value = "24";
    226                 } else {
    227                     value = "12";
    228                 }
    229             } else {
    230                 value = "12";
    231             }
    232 
    233             synchronized (sLocaleLock) {
    234                 sIs24HourLocale = locale;
    235                 sIs24Hour = !value.equals("12");
    236             }
    237         }
    238 
    239         boolean b24 =  !(value == null || value.equals("12"));
    240         return b24;
    241     }
    242 
    243     /**
    244      * Returns a {@link java.text.DateFormat} object that can format the time according
    245      * to the current locale and the user's 12-/24-hour clock preference.
    246      * @param context the application context
    247      * @return the {@link java.text.DateFormat} object that properly formats the time.
    248      */
    249     public static final java.text.DateFormat getTimeFormat(Context context) {
    250         boolean b24 = is24HourFormat(context);
    251         int res;
    252 
    253         if (b24) {
    254             res = R.string.twenty_four_hour_time_format;
    255         } else {
    256             res = R.string.twelve_hour_time_format;
    257         }
    258 
    259         return new java.text.SimpleDateFormat(context.getString(res));
    260     }
    261 
    262     /**
    263      * Returns a {@link java.text.DateFormat} object that can format the date
    264      * in short form (such as 12/31/1999) according
    265      * to the current locale and the user's date-order preference.
    266      * @param context the application context
    267      * @return the {@link java.text.DateFormat} object that properly formats the date.
    268      */
    269     public static final java.text.DateFormat getDateFormat(Context context) {
    270         String value = Settings.System.getString(context.getContentResolver(),
    271                 Settings.System.DATE_FORMAT);
    272 
    273         return getDateFormatForSetting(context, value);
    274     }
    275 
    276     /**
    277      * Returns a {@link java.text.DateFormat} object to format the date
    278      * as if the date format setting were set to <code>value</code>,
    279      * including null to use the locale's default format.
    280      * @param context the application context
    281      * @param value the date format setting string to interpret for
    282      *              the current locale
    283      * @hide
    284      */
    285     public static java.text.DateFormat getDateFormatForSetting(Context context,
    286                                                                String value) {
    287         String format = getDateFormatStringForSetting(context, value);
    288 
    289         return new java.text.SimpleDateFormat(format);
    290     }
    291 
    292     private static String getDateFormatStringForSetting(Context context, String value) {
    293         if (value != null) {
    294             int month = value.indexOf('M');
    295             int day = value.indexOf('d');
    296             int year = value.indexOf('y');
    297 
    298             if (month >= 0 && day >= 0 && year >= 0) {
    299                 String template = context.getString(R.string.numeric_date_template);
    300                 if (year < month && year < day) {
    301                     if (month < day) {
    302                         value = String.format(template, "yyyy", "MM", "dd");
    303                     } else {
    304                         value = String.format(template, "yyyy", "dd", "MM");
    305                     }
    306                 } else if (month < day) {
    307                     if (day < year) {
    308                         value = String.format(template, "MM", "dd", "yyyy");
    309                     } else { // unlikely
    310                         value = String.format(template, "MM", "yyyy", "dd");
    311                     }
    312                 } else { // day < month
    313                     if (month < year) {
    314                         value = String.format(template, "dd", "MM", "yyyy");
    315                     } else { // unlikely
    316                         value = String.format(template, "dd", "yyyy", "MM");
    317                     }
    318                 }
    319 
    320                 return value;
    321             }
    322         }
    323 
    324         /*
    325          * The setting is not set; use the default.
    326          * We use a resource string here instead of just DateFormat.SHORT
    327          * so that we get a four-digit year instead a two-digit year.
    328          */
    329         value = context.getString(R.string.numeric_date_format);
    330         return value;
    331     }
    332 
    333     /**
    334      * Returns a {@link java.text.DateFormat} object that can format the date
    335      * in long form (such as December 31, 1999) for the current locale.
    336      * @param context the application context
    337      * @return the {@link java.text.DateFormat} object that formats the date in long form.
    338      */
    339     public static final java.text.DateFormat getLongDateFormat(Context context) {
    340         return java.text.DateFormat.getDateInstance(java.text.DateFormat.LONG);
    341     }
    342 
    343     /**
    344      * Returns a {@link java.text.DateFormat} object that can format the date
    345      * in medium form (such as Dec. 31, 1999) for the current locale.
    346      * @param context the application context
    347      * @return the {@link java.text.DateFormat} object that formats the date in long form.
    348      */
    349     public static final java.text.DateFormat getMediumDateFormat(Context context) {
    350         return java.text.DateFormat.getDateInstance(java.text.DateFormat.MEDIUM);
    351     }
    352 
    353     /**
    354      * Gets the current date format stored as a char array. The array will contain
    355      * 3 elements ({@link #DATE}, {@link #MONTH}, and {@link #YEAR}) in the order
    356      * specified by the user's format preference.  Note that this order is
    357      * only appropriate for all-numeric dates; spelled-out (MEDIUM and LONG)
    358      * dates will generally contain other punctuation, spaces, or words,
    359      * not just the day, month, and year, and not necessarily in the same
    360      * order returned here.
    361      */
    362     public static final char[] getDateFormatOrder(Context context) {
    363         char[] order = new char[] {DATE, MONTH, YEAR};
    364         String value = getDateFormatString(context);
    365         int index = 0;
    366         boolean foundDate = false;
    367         boolean foundMonth = false;
    368         boolean foundYear = false;
    369 
    370         for (char c : value.toCharArray()) {
    371             if (!foundDate && (c == DATE)) {
    372                 foundDate = true;
    373                 order[index] = DATE;
    374                 index++;
    375             }
    376 
    377             if (!foundMonth && (c == MONTH)) {
    378                 foundMonth = true;
    379                 order[index] = MONTH;
    380                 index++;
    381             }
    382 
    383             if (!foundYear && (c == YEAR)) {
    384                 foundYear = true;
    385                 order[index] = YEAR;
    386                 index++;
    387             }
    388         }
    389         return order;
    390     }
    391 
    392     private static String getDateFormatString(Context context) {
    393         String value = Settings.System.getString(context.getContentResolver(),
    394                 Settings.System.DATE_FORMAT);
    395 
    396         return getDateFormatStringForSetting(context, value);
    397     }
    398 
    399     /**
    400      * Given a format string and a time in milliseconds since Jan 1, 1970 GMT, returns a
    401      * CharSequence containing the requested date.
    402      * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
    403      * @param inTimeInMillis in milliseconds since Jan 1, 1970 GMT
    404      * @return a {@link CharSequence} containing the requested text
    405      */
    406     public static final CharSequence format(CharSequence inFormat, long inTimeInMillis) {
    407         return format(inFormat, new Date(inTimeInMillis));
    408     }
    409 
    410     /**
    411      * Given a format string and a {@link java.util.Date} object, returns a CharSequence containing
    412      * the requested date.
    413      * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
    414      * @param inDate the date to format
    415      * @return a {@link CharSequence} containing the requested text
    416      */
    417     public static final CharSequence format(CharSequence inFormat, Date inDate) {
    418         Calendar    c = new GregorianCalendar();
    419 
    420         c.setTime(inDate);
    421 
    422         return format(inFormat, c);
    423     }
    424 
    425     /**
    426      * Given a format string and a {@link java.util.Calendar} object, returns a CharSequence
    427      * containing the requested date.
    428      * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
    429      * @param inDate the date to format
    430      * @return a {@link CharSequence} containing the requested text
    431      */
    432     public static final CharSequence format(CharSequence inFormat, Calendar inDate) {
    433         SpannableStringBuilder      s = new SpannableStringBuilder(inFormat);
    434         int             c;
    435         int             count;
    436 
    437         int len = inFormat.length();
    438 
    439         for (int i = 0; i < len; i += count) {
    440             int temp;
    441 
    442             count = 1;
    443             c = s.charAt(i);
    444 
    445             if (c == QUOTE) {
    446                 count = appendQuotedText(s, i, len);
    447                 len = s.length();
    448                 continue;
    449             }
    450 
    451             while ((i + count < len) && (s.charAt(i + count) == c)) {
    452                 count++;
    453             }
    454 
    455             String replacement;
    456 
    457             switch (c) {
    458                 case AM_PM:
    459                     replacement = DateUtils.getAMPMString(inDate.get(Calendar.AM_PM));
    460                     break;
    461 
    462                 case CAPITAL_AM_PM:
    463                     //FIXME: this is the same as AM_PM? no capital?
    464                     replacement = DateUtils.getAMPMString(inDate.get(Calendar.AM_PM));
    465                     break;
    466 
    467                 case DATE:
    468                     replacement = zeroPad(inDate.get(Calendar.DATE), count);
    469                     break;
    470 
    471                 case DAY:
    472                     temp = inDate.get(Calendar.DAY_OF_WEEK);
    473                     replacement = DateUtils.getDayOfWeekString(temp,
    474                                                                count < 4 ?
    475                                                                DateUtils.LENGTH_MEDIUM :
    476                                                                DateUtils.LENGTH_LONG);
    477                     break;
    478 
    479                 case HOUR:
    480                     temp = inDate.get(Calendar.HOUR);
    481 
    482                     if (0 == temp)
    483                         temp = 12;
    484 
    485                     replacement = zeroPad(temp, count);
    486                     break;
    487 
    488                 case HOUR_OF_DAY:
    489                     replacement = zeroPad(inDate.get(Calendar.HOUR_OF_DAY), count);
    490                     break;
    491 
    492                 case MINUTE:
    493                     replacement = zeroPad(inDate.get(Calendar.MINUTE), count);
    494                     break;
    495 
    496                 case MONTH:
    497                     replacement = getMonthString(inDate, count);
    498                     break;
    499 
    500                 case SECONDS:
    501                     replacement = zeroPad(inDate.get(Calendar.SECOND), count);
    502                     break;
    503 
    504                 case TIME_ZONE:
    505                     replacement = getTimeZoneString(inDate, count);
    506                     break;
    507 
    508                 case YEAR:
    509                     replacement = getYearString(inDate, count);
    510                     break;
    511 
    512                 default:
    513                     replacement = null;
    514                     break;
    515             }
    516 
    517             if (replacement != null) {
    518                 s.replace(i, i + count, replacement);
    519                 count = replacement.length(); // CARE: count is used in the for loop above
    520                 len = s.length();
    521             }
    522         }
    523 
    524         if (inFormat instanceof Spanned)
    525             return new SpannedString(s);
    526         else
    527             return s.toString();
    528     }
    529 
    530     private static final String getMonthString(Calendar inDate, int count) {
    531         int month = inDate.get(Calendar.MONTH);
    532 
    533         if (count >= 4)
    534             return DateUtils.getMonthString(month, DateUtils.LENGTH_LONG);
    535         else if (count == 3)
    536             return DateUtils.getMonthString(month, DateUtils.LENGTH_MEDIUM);
    537         else {
    538             // Calendar.JANUARY == 0, so add 1 to month.
    539             return zeroPad(month+1, count);
    540         }
    541     }
    542 
    543     private static final String getTimeZoneString(Calendar inDate, int count) {
    544         TimeZone tz = inDate.getTimeZone();
    545 
    546         if (count < 2) { // FIXME: shouldn't this be <= 2 ?
    547             return formatZoneOffset(inDate.get(Calendar.DST_OFFSET) +
    548                                     inDate.get(Calendar.ZONE_OFFSET),
    549                                     count);
    550         } else {
    551             boolean dst = inDate.get(Calendar.DST_OFFSET) != 0;
    552             return tz.getDisplayName(dst, TimeZone.SHORT);
    553         }
    554     }
    555 
    556     private static final String formatZoneOffset(int offset, int count) {
    557         offset /= 1000; // milliseconds to seconds
    558         StringBuilder tb = new StringBuilder();
    559 
    560         if (offset < 0) {
    561             tb.insert(0, "-");
    562             offset = -offset;
    563         } else {
    564             tb.insert(0, "+");
    565         }
    566 
    567         int hours = offset / 3600;
    568         int minutes = (offset % 3600) / 60;
    569 
    570         tb.append(zeroPad(hours, 2));
    571         tb.append(zeroPad(minutes, 2));
    572         return tb.toString();
    573     }
    574 
    575     private static final String getYearString(Calendar inDate, int count) {
    576         int year = inDate.get(Calendar.YEAR);
    577         return (count <= 2) ? zeroPad(year % 100, 2) : String.valueOf(year);
    578     }
    579 
    580     private static final int appendQuotedText(SpannableStringBuilder s, int i, int len) {
    581         if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
    582             s.delete(i, i + 1);
    583             return 1;
    584         }
    585 
    586         int count = 0;
    587 
    588         // delete leading quote
    589         s.delete(i, i + 1);
    590         len--;
    591 
    592         while (i < len) {
    593             char c = s.charAt(i);
    594 
    595             if (c == QUOTE) {
    596                 //  QUOTEQUOTE -> QUOTE
    597                 if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
    598 
    599                     s.delete(i, i + 1);
    600                     len--;
    601                     count++;
    602                     i++;
    603                 } else {
    604                     //  Closing QUOTE ends quoted text copying
    605                     s.delete(i, i + 1);
    606                     break;
    607                 }
    608             } else {
    609                 i++;
    610                 count++;
    611             }
    612         }
    613 
    614         return count;
    615     }
    616 
    617     private static final String zeroPad(int inValue, int inMinDigits) {
    618         String val = String.valueOf(inValue);
    619 
    620         if (val.length() < inMinDigits) {
    621             char[] buf = new char[inMinDigits];
    622 
    623             for (int i = 0; i < inMinDigits; i++)
    624                 buf[i] = '0';
    625 
    626             val.getChars(0, val.length(), buf, inMinDigits - val.length());
    627             val = new String(buf);
    628         }
    629         return val;
    630     }
    631 }
    632