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