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