DateFormat.java revision 328769582328192f8f361dcb56f2ad67ad00ae2c
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.Locale;
31import java.util.TimeZone;
32import 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
77public 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        if (value != null) {
288            int month = value.indexOf('M');
289            int day = value.indexOf('d');
290            int year = value.indexOf('y');
291
292            if (month >= 0 && day >= 0 && year >= 0) {
293                String template = context.getString(R.string.numeric_date_template);
294                if (year < month) {
295                    if (month < day) {
296                        value = String.format(template, "yyyy", "MM", "dd");
297                    } else {
298                        value = String.format(template, "yyyy", "dd", "MM");
299                    }
300                } else if (month < day) {
301                    if (day < year) {
302                        value = String.format(template, "MM", "dd", "yyyy");
303                    } else { // unlikely
304                        value = String.format(template, "MM", "yyyy", "dd");
305                    }
306                } else { // day < month
307                    if (month < year) {
308                        value = String.format(template, "dd", "MM", "yyyy");
309                    } else { // unlikely
310                        value = String.format(template, "dd", "yyyy", "MM");
311                    }
312                }
313
314                return new java.text.SimpleDateFormat(value);
315            }
316        }
317
318        /*
319         * The setting is not set; use the default.
320         * We use a resource string here instead of just DateFormat.SHORT
321         * so that we get a four-digit year instead a two-digit year.
322         */
323        value = context.getString(R.string.numeric_date_format);
324        return new java.text.SimpleDateFormat(value);
325    }
326
327    /**
328     * Returns a {@link java.text.DateFormat} object that can format the date
329     * in long form (such as December 31, 1999) for the current locale.
330     * @param context the application context
331     * @return the {@link java.text.DateFormat} object that formats the date in long form.
332     */
333    public static final java.text.DateFormat getLongDateFormat(Context context) {
334        return java.text.DateFormat.getDateInstance(java.text.DateFormat.LONG);
335    }
336
337    /**
338     * Returns a {@link java.text.DateFormat} object that can format the date
339     * in medium form (such as Dec. 31, 1999) for the current locale.
340     * @param context the application context
341     * @return the {@link java.text.DateFormat} object that formats the date in long form.
342     */
343    public static final java.text.DateFormat getMediumDateFormat(Context context) {
344        return java.text.DateFormat.getDateInstance(java.text.DateFormat.MEDIUM);
345    }
346
347    /**
348     * Gets the current date format stored as a char array. The array will contain
349     * 3 elements ({@link #DATE}, {@link #MONTH}, and {@link #YEAR}) in the order
350     * preferred by the user.
351     */
352    public static final char[] getDateFormatOrder(Context context) {
353        char[] order = new char[] {DATE, MONTH, YEAR};
354        String value = getDateFormatString(context);
355        int index = 0;
356        boolean foundDate = false;
357        boolean foundMonth = false;
358        boolean foundYear = false;
359
360        for (char c : value.toCharArray()) {
361            if (!foundDate && (c == DATE)) {
362                foundDate = true;
363                order[index] = DATE;
364                index++;
365            }
366
367            if (!foundMonth && (c == MONTH)) {
368                foundMonth = true;
369                order[index] = MONTH;
370                index++;
371            }
372
373            if (!foundYear && (c == YEAR)) {
374                foundYear = true;
375                order[index] = YEAR;
376                index++;
377            }
378        }
379        return order;
380    }
381
382    private static String getDateFormatString(Context context) {
383        java.text.DateFormat df;
384        df = java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT);
385        if (df instanceof SimpleDateFormat) {
386            return ((SimpleDateFormat) df).toPattern();
387        }
388
389        String value = Settings.System.getString(context.getContentResolver(),
390                Settings.System.DATE_FORMAT);
391        if (value == null || value.length() < 6) {
392            /*
393             * No need to localize -- this is an emergency fallback in case
394             * the setting is missing, but it should always be set.
395             */
396            value = "MM-dd-yyyy";
397        }
398        return value;
399    }
400
401    /**
402     * Given a format string and a time in milliseconds since Jan 1, 1970 GMT, returns a
403     * CharSequence containing the requested date.
404     * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
405     * @param inTimeInMillis in milliseconds since Jan 1, 1970 GMT
406     * @return a {@link CharSequence} containing the requested text
407     */
408    public static final CharSequence format(CharSequence inFormat, long inTimeInMillis) {
409        return format(inFormat, new Date(inTimeInMillis));
410    }
411
412    /**
413     * Given a format string and a {@link java.util.Date} object, returns a CharSequence containing
414     * the requested date.
415     * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
416     * @param inDate the date to format
417     * @return a {@link CharSequence} containing the requested text
418     */
419    public static final CharSequence format(CharSequence inFormat, Date inDate) {
420        Calendar    c = new GregorianCalendar();
421
422        c.setTime(inDate);
423
424        return format(inFormat, c);
425    }
426
427    /**
428     * Given a format string and a {@link java.util.Calendar} object, returns a CharSequence
429     * containing the requested date.
430     * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
431     * @param inDate the date to format
432     * @return a {@link CharSequence} containing the requested text
433     */
434    public static final CharSequence format(CharSequence inFormat, Calendar inDate) {
435        SpannableStringBuilder      s = new SpannableStringBuilder(inFormat);
436        int             c;
437        int             count;
438
439        int len = inFormat.length();
440
441        for (int i = 0; i < len; i += count) {
442            int temp;
443
444            count = 1;
445            c = s.charAt(i);
446
447            if (c == QUOTE) {
448                count = appendQuotedText(s, i, len);
449                len = s.length();
450                continue;
451            }
452
453            while ((i + count < len) && (s.charAt(i + count) == c)) {
454                count++;
455            }
456
457            String replacement;
458
459            switch (c) {
460                case AM_PM:
461                    replacement = DateUtils.getAMPMString(inDate.get(Calendar.AM_PM));
462                    break;
463
464                case CAPITAL_AM_PM:
465                    //FIXME: this is the same as AM_PM? no capital?
466                    replacement = DateUtils.getAMPMString(inDate.get(Calendar.AM_PM));
467                    break;
468
469                case DATE:
470                    replacement = zeroPad(inDate.get(Calendar.DATE), count);
471                    break;
472
473                case DAY:
474                    temp = inDate.get(Calendar.DAY_OF_WEEK);
475                    replacement = DateUtils.getDayOfWeekString(temp,
476                                                               count < 4 ?
477                                                               DateUtils.LENGTH_MEDIUM :
478                                                               DateUtils.LENGTH_LONG);
479                    break;
480
481                case HOUR:
482                    temp = inDate.get(Calendar.HOUR);
483
484                    if (0 == temp)
485                        temp = 12;
486
487                    replacement = zeroPad(temp, count);
488                    break;
489
490                case HOUR_OF_DAY:
491                    replacement = zeroPad(inDate.get(Calendar.HOUR_OF_DAY), count);
492                    break;
493
494                case MINUTE:
495                    replacement = zeroPad(inDate.get(Calendar.MINUTE), count);
496                    break;
497
498                case MONTH:
499                    replacement = getMonthString(inDate, count);
500                    break;
501
502                case SECONDS:
503                    replacement = zeroPad(inDate.get(Calendar.SECOND), count);
504                    break;
505
506                case TIME_ZONE:
507                    replacement = getTimeZoneString(inDate, count);
508                    break;
509
510                case YEAR:
511                    replacement = getYearString(inDate, count);
512                    break;
513
514                default:
515                    replacement = null;
516                    break;
517            }
518
519            if (replacement != null) {
520                s.replace(i, i + count, replacement);
521                count = replacement.length(); // CARE: count is used in the for loop above
522                len = s.length();
523            }
524        }
525
526        if (inFormat instanceof Spanned)
527            return new SpannedString(s);
528        else
529            return s.toString();
530    }
531
532    private static final String getMonthString(Calendar inDate, int count) {
533        int month = inDate.get(Calendar.MONTH);
534
535        if (count >= 4)
536            return DateUtils.getMonthString(month, DateUtils.LENGTH_LONG);
537        else if (count == 3)
538            return DateUtils.getMonthString(month, DateUtils.LENGTH_MEDIUM);
539        else {
540            // Calendar.JANUARY == 0, so add 1 to month.
541            return zeroPad(month+1, count);
542        }
543    }
544
545    private static final String getTimeZoneString(Calendar inDate, int count) {
546        TimeZone tz = inDate.getTimeZone();
547
548        if (count < 2) { // FIXME: shouldn't this be <= 2 ?
549            return formatZoneOffset(inDate.get(Calendar.DST_OFFSET) +
550                                    inDate.get(Calendar.ZONE_OFFSET),
551                                    count);
552        } else {
553            boolean dst = inDate.get(Calendar.DST_OFFSET) != 0;
554            return tz.getDisplayName(dst, TimeZone.SHORT);
555        }
556    }
557
558    private static final String formatZoneOffset(int offset, int count) {
559        offset /= 1000; // milliseconds to seconds
560        StringBuilder tb = new StringBuilder();
561
562        if (offset < 0) {
563            tb.insert(0, "-");
564            offset = -offset;
565        } else {
566            tb.insert(0, "+");
567        }
568
569        int hours = offset / 3600;
570        int minutes = (offset % 3600) / 60;
571
572        tb.append(zeroPad(hours, 2));
573        tb.append(zeroPad(minutes, 2));
574        return tb.toString();
575    }
576
577    private static final String getYearString(Calendar inDate, int count) {
578        int year = inDate.get(Calendar.YEAR);
579        return (count <= 2) ? zeroPad(year % 100, 2) : String.valueOf(year);
580    }
581
582    private static final int appendQuotedText(SpannableStringBuilder s, int i, int len) {
583        if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
584            s.delete(i, i + 1);
585            return 1;
586        }
587
588        int count = 0;
589
590        // delete leading quote
591        s.delete(i, i + 1);
592        len--;
593
594        while (i < len) {
595            char c = s.charAt(i);
596
597            if (c == QUOTE) {
598                //  QUOTEQUOTE -> QUOTE
599                if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
600
601                    s.delete(i, i + 1);
602                    len--;
603                    count++;
604                    i++;
605                } else {
606                    //  Closing QUOTE ends quoted text copying
607                    s.delete(i, i + 1);
608                    break;
609                }
610            } else {
611                i++;
612                count++;
613            }
614        }
615
616        return count;
617    }
618
619    private static final String zeroPad(int inValue, int inMinDigits) {
620        String val = String.valueOf(inValue);
621
622        if (val.length() < inMinDigits) {
623            char[] buf = new char[inMinDigits];
624
625            for (int i = 0; i < inMinDigits; i++)
626                buf[i] = '0';
627
628            val.getChars(0, val.length(), buf, inMinDigits - val.length());
629            val = new String(buf);
630        }
631        return val;
632    }
633}
634