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