• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
is24HourFormat(Context context)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      */
getTimeFormat(Context context)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      */
getDateFormat(Context context)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      */
getDateFormatForSetting(Context context, String value)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 
getDateFormatStringForSetting(Context context, String value)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      */
getLongDateFormat(Context context)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      */
getMediumDateFormat(Context context)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      */
getDateFormatOrder(Context context)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 
getDateFormatString(Context context)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      */
format(CharSequence inFormat, long inTimeInMillis)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      */
format(CharSequence inFormat, Date inDate)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      */
format(CharSequence inFormat, Calendar inDate)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 
getMonthString(Calendar inDate, int count)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 
getTimeZoneString(Calendar inDate, int count)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 
formatZoneOffset(int offset, int count)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 
getYearString(Calendar inDate, int count)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 
appendQuotedText(SpannableStringBuilder s, int i, int len)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 
zeroPad(int inValue, int inMinDigits)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