• Home
  • History
  • Annotate
  • 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.annotation.NonNull;
20  import android.app.compat.CompatChanges;
21  import android.compat.annotation.ChangeId;
22  import android.compat.annotation.EnabledSince;
23  import android.compat.annotation.UnsupportedAppUsage;
24  import android.content.Context;
25  import android.icu.text.DateFormatSymbols;
26  import android.icu.text.DateTimePatternGenerator;
27  import android.icu.util.ULocale;
28  import android.os.Build;
29  import android.provider.Settings;
30  import android.text.SpannableStringBuilder;
31  import android.text.Spanned;
32  import android.text.SpannedString;
33  
34  import java.text.SimpleDateFormat;
35  import java.util.Calendar;
36  import java.util.Date;
37  import java.util.GregorianCalendar;
38  import java.util.Locale;
39  import java.util.TimeZone;
40  
41  /**
42   * Utility class for producing strings with formatted date/time.
43   *
44   * <p>Most callers should avoid supplying their own format strings to this
45   * class' {@code format} methods and rely on the correctly localized ones
46   * supplied by the system. This class' factory methods return
47   * appropriately-localized {@link java.text.DateFormat} instances, suitable
48   * for both formatting and parsing dates. For the canonical documentation
49   * of format strings, see {@link java.text.SimpleDateFormat}.
50   *
51   * <p>In cases where the system does not provide a suitable pattern,
52   * this class offers the {@link #getBestDateTimePattern} method.
53   *
54   * <p>The {@code format} methods in this class implement a subset of Unicode
55   * <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a> patterns.
56   * The subset currently supported by this class includes the following format characters:
57   * {@code acdEHhLKkLMmsyz}. Up to API level 17, only {@code adEhkMmszy} were supported.
58   * Note that this class incorrectly implements {@code k} as if it were {@code H} for backwards
59   * compatibility.
60   *
61   * <p>See {@link java.text.SimpleDateFormat} for more documentation
62   * about patterns, or if you need a more complete or correct implementation.
63   * Note that the non-{@code format} methods in this class are implemented by
64   * {@code SimpleDateFormat}.
65   */
66  public class DateFormat {
67      /**
68       * @deprecated Use a literal {@code '} instead.
69       * @removed
70       */
71      @Deprecated
72      public  static final char    QUOTE                  =    '\'';
73  
74      /**
75       * @deprecated Use a literal {@code 'a'} instead.
76       * @removed
77       */
78      @Deprecated
79      public  static final char    AM_PM                  =    'a';
80  
81      /**
82       * @deprecated Use a literal {@code 'a'} instead; 'A' was always equivalent to 'a'.
83       * @removed
84       */
85      @Deprecated
86      public  static final char    CAPITAL_AM_PM          =    'A';
87  
88      /**
89       * @deprecated Use a literal {@code 'd'} instead.
90       * @removed
91       */
92      @Deprecated
93      public  static final char    DATE                   =    'd';
94  
95      /**
96       * @deprecated Use a literal {@code 'E'} instead.
97       * @removed
98       */
99      @Deprecated
100      public  static final char    DAY                    =    'E';
101  
102      /**
103       * @deprecated Use a literal {@code 'h'} instead.
104       * @removed
105       */
106      @Deprecated
107      public  static final char    HOUR                   =    'h';
108  
109      /**
110       * @deprecated Use a literal {@code 'H'} (for compatibility with {@link SimpleDateFormat}
111       * and Unicode) or {@code 'k'} (for compatibility with Android releases up to and including
112       * Jelly Bean MR-1) instead. Note that the two are incompatible.
113       *
114       * @removed
115       */
116      @Deprecated
117      public  static final char    HOUR_OF_DAY            =    'k';
118  
119      /**
120       * @deprecated Use a literal {@code 'm'} instead.
121       * @removed
122       */
123      @Deprecated
124      public  static final char    MINUTE                 =    'm';
125  
126      /**
127       * @deprecated Use a literal {@code 'M'} instead.
128       * @removed
129       */
130      @Deprecated
131      public  static final char    MONTH                  =    'M';
132  
133      /**
134       * @deprecated Use a literal {@code 'L'} instead.
135       * @removed
136       */
137      @Deprecated
138      public  static final char    STANDALONE_MONTH       =    'L';
139  
140      /**
141       * @deprecated Use a literal {@code 's'} instead.
142       * @removed
143       */
144      @Deprecated
145      public  static final char    SECONDS                =    's';
146  
147      /**
148       * @deprecated Use a literal {@code 'z'} instead.
149       * @removed
150       */
151      @Deprecated
152      public  static final char    TIME_ZONE              =    'z';
153  
154      /**
155       * @deprecated Use a literal {@code 'y'} instead.
156       * @removed
157       */
158      @Deprecated
159      public  static final char    YEAR                   =    'y';
160  
161  
162      private static final Object sLocaleLock = new Object();
163      private static Locale sIs24HourLocale;
164      private static boolean sIs24Hour;
165  
166      /**
167       * {@link #getBestDateTimePattern(Locale, String)} does not allow non-consecutive repeated
168       * symbol in the skeleton. For example, please use a skeleton of {@code "jmm"} or
169       * {@code "hmma"} instead of {@code "ahmma"} or {@code "jmma"}, because the field 'j' could
170       * mean using 12-hour in some locales and, in this case, is duplicated as the 'a' field.
171       */
172      @ChangeId
173      @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
174      static final long DISALLOW_DUPLICATE_FIELD_IN_SKELETON = 170233598L;
175  
176      /**
177       * Returns true if times should be formatted as 24 hour times, false if times should be
178       * formatted as 12 hour (AM/PM) times. Based on the user's chosen locale and other preferences.
179       * @param context the context to use for the content resolver
180       * @return true if 24 hour time format is selected, false otherwise.
181       */
is24HourFormat(Context context)182      public static boolean is24HourFormat(Context context) {
183          return is24HourFormat(context, context.getUserId());
184      }
185  
186      /**
187       * Returns true if times should be formatted as 24 hour times, false if times should be
188       * formatted as 12 hour (AM/PM) times. Based on the user's chosen locale and other preferences.
189       * @param context the context to use for the content resolver
190       * @param userHandle the user handle of the user to query.
191       * @return true if 24 hour time format is selected, false otherwise.
192       *
193       * @hide
194       */
195      @UnsupportedAppUsage
is24HourFormat(Context context, int userHandle)196      public static boolean is24HourFormat(Context context, int userHandle) {
197          final String value = Settings.System.getStringForUser(context.getContentResolver(),
198                  Settings.System.TIME_12_24, userHandle);
199          if (value != null) {
200              return value.equals("24");
201          }
202  
203          return is24HourLocale(context.getResources().getConfiguration().locale);
204      }
205  
206      /**
207       * Returns true if the specified locale uses a 24-hour time format by default, ignoring user
208       * settings.
209       * @param locale the locale to check
210       * @return true if the locale uses a 24 hour time format by default, false otherwise
211       * @hide
212       */
is24HourLocale(@onNull Locale locale)213      public static boolean is24HourLocale(@NonNull Locale locale) {
214          synchronized (sLocaleLock) {
215              if (sIs24HourLocale != null && sIs24HourLocale.equals(locale)) {
216                  return sIs24Hour;
217              }
218          }
219  
220          final java.text.DateFormat natural =
221                  java.text.DateFormat.getTimeInstance(java.text.DateFormat.LONG, locale);
222  
223          final boolean is24Hour;
224          if (natural instanceof SimpleDateFormat) {
225              final SimpleDateFormat sdf = (SimpleDateFormat) natural;
226              final String pattern = sdf.toPattern();
227              is24Hour = hasDesignator(pattern, 'H');
228          } else {
229              is24Hour = false;
230          }
231  
232          synchronized (sLocaleLock) {
233              sIs24HourLocale = locale;
234              sIs24Hour = is24Hour;
235          }
236  
237          return is24Hour;
238      }
239  
240      /**
241       * Returns the best possible localized form of the given skeleton for the given
242       * locale. A skeleton is similar to, and uses the same format characters as, a Unicode
243       * <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a>
244       * pattern.
245       *
246       * <p>One difference is that order is irrelevant. For example, "MMMMd" will return
247       * "MMMM d" in the {@code en_US} locale, but "d. MMMM" in the {@code de_CH} locale.
248       *
249       * <p>Note also in that second example that the necessary punctuation for German was
250       * added. For the same input in {@code es_ES}, we'd have even more extra text:
251       * "d 'de' MMMM".
252       *
253       * <p>This method will automatically correct for grammatical necessity. Given the
254       * same "MMMMd" input, this method will return "d LLLL" in the {@code fa_IR} locale,
255       * where stand-alone months are necessary. Lengths are preserved where meaningful,
256       * so "Md" would give a different result to "MMMd", say, except in a locale such as
257       * {@code ja_JP} where there is only one length of month.
258       *
259       * <p>This method will only return patterns that are in CLDR, and is useful whenever
260       * you know what elements you want in your format string but don't want to make your
261       * code specific to any one locale.
262       *
263       * @param locale the locale into which the skeleton should be localized
264       * @param skeleton a skeleton as described above
265       * @return a string pattern suitable for use with {@link java.text.SimpleDateFormat}.
266       */
getBestDateTimePattern(Locale locale, String skeleton)267      public static String getBestDateTimePattern(Locale locale, String skeleton) {
268          ULocale uLocale = ULocale.forLocale(locale);
269          DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(uLocale);
270          boolean allowDuplicateFields = !CompatChanges.isChangeEnabled(
271                  DISALLOW_DUPLICATE_FIELD_IN_SKELETON);
272          String pattern = dtpg.getBestPattern(skeleton, DateTimePatternGenerator.MATCH_NO_OPTIONS,
273                  allowDuplicateFields);
274          return getCompatibleEnglishPattern(uLocale, pattern);
275      }
276  
277      /**
278       * Returns a {@link java.text.DateFormat} object that can format the time according
279       * to the context's locale and the user's 12-/24-hour clock preference.
280       * @param context the application context
281       * @return the {@link java.text.DateFormat} object that properly formats the time.
282       */
getTimeFormat(Context context)283      public static java.text.DateFormat getTimeFormat(Context context) {
284          final Locale locale = context.getResources().getConfiguration().locale;
285          return new java.text.SimpleDateFormat(getTimeFormatString(context), locale);
286      }
287  
288      /**
289       * Returns a String pattern that can be used to format the time according
290       * to the context's locale and the user's 12-/24-hour clock preference.
291       * @param context the application context
292       * @hide
293       */
294      @UnsupportedAppUsage
getTimeFormatString(Context context)295      public static String getTimeFormatString(Context context) {
296          return getTimeFormatString(context, context.getUserId());
297      }
298  
299      /**
300       * Returns a String pattern that can be used to format the time according
301       * to the context's locale and the user's 12-/24-hour clock preference.
302       * @param context the application context
303       * @param userHandle the user handle of the user to query the format for
304       * @hide
305       */
306      @UnsupportedAppUsage
getTimeFormatString(Context context, int userHandle)307      public static String getTimeFormatString(Context context, int userHandle) {
308          ULocale uLocale = ULocale.forLocale(context.getResources().getConfiguration().locale);
309          DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(uLocale);
310          String pattern = is24HourFormat(context, userHandle) ? dtpg.getBestPattern("Hm")
311              : dtpg.getBestPattern("hm");
312          return getCompatibleEnglishPattern(uLocale, pattern);
313      }
314  
315      /**
316       * Returns a {@link java.text.DateFormat} object that can format the date
317       * in short form according to the context's locale.
318       *
319       * @param context the application context
320       * @return the {@link java.text.DateFormat} object that properly formats the date.
321       */
getDateFormat(Context context)322      public static java.text.DateFormat getDateFormat(Context context) {
323          final Locale locale = context.getResources().getConfiguration().locale;
324          return java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT, locale);
325      }
326  
327      /**
328       * Returns a {@link java.text.DateFormat} object that can format the date
329       * in long form (such as {@code Monday, January 3, 2000}) for the context's locale.
330       * @param context the application context
331       * @return the {@link java.text.DateFormat} object that formats the date in long form.
332       */
getLongDateFormat(Context context)333      public static java.text.DateFormat getLongDateFormat(Context context) {
334          final Locale locale = context.getResources().getConfiguration().locale;
335          return java.text.DateFormat.getDateInstance(java.text.DateFormat.LONG, locale);
336      }
337  
338      /**
339       * Returns a {@link java.text.DateFormat} object that can format the date
340       * in medium form (such as {@code Jan 3, 2000}) for the context's locale.
341       * @param context the application context
342       * @return the {@link java.text.DateFormat} object that formats the date in long form.
343       */
getMediumDateFormat(Context context)344      public static java.text.DateFormat getMediumDateFormat(Context context) {
345          final Locale locale = context.getResources().getConfiguration().locale;
346          return java.text.DateFormat.getDateInstance(java.text.DateFormat.MEDIUM, locale);
347      }
348  
349      /**
350       * Gets the current date format stored as a char array. Returns a 3 element
351       * array containing the day ({@code 'd'}), month ({@code 'M'}), and year ({@code 'y'}))
352       * in the order specified by the user's format preference.  Note that this order is
353       * <i>only</i> appropriate for all-numeric dates; spelled-out (MEDIUM and LONG)
354       * dates will generally contain other punctuation, spaces, or words,
355       * not just the day, month, and year, and not necessarily in the same
356       * order returned here.
357       */
getDateFormatOrder(Context context)358      public static char[] getDateFormatOrder(Context context) {
359          return getDateFormatOrder(getDateFormatString(context));
360      }
361  
362      /**
363       * @hide Used by internal framework class {@link android.widget.DatePickerSpinnerDelegate}.
364       */
getDateFormatOrder(String pattern)365      public static char[] getDateFormatOrder(String pattern) {
366          char[] result = new char[3];
367          int resultIndex = 0;
368          boolean sawDay = false;
369          boolean sawMonth = false;
370          boolean sawYear = false;
371  
372          for (int i = 0; i < pattern.length(); ++i) {
373              char ch = pattern.charAt(i);
374              if (ch == 'd' || ch == 'L' || ch == 'M' || ch == 'y') {
375                  if (ch == 'd' && !sawDay) {
376                      result[resultIndex++] = 'd';
377                      sawDay = true;
378                  } else if ((ch == 'L' || ch == 'M') && !sawMonth) {
379                      result[resultIndex++] = 'M';
380                      sawMonth = true;
381                  } else if ((ch == 'y') && !sawYear) {
382                      result[resultIndex++] = 'y';
383                      sawYear = true;
384                  }
385              } else if (ch == 'G') {
386                  // Ignore the era specifier, if present.
387              } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
388                  throw new IllegalArgumentException("Bad pattern character '" + ch + "' in "
389                      + pattern);
390              } else if (ch == '\'') {
391                  if (i < pattern.length() - 1 && pattern.charAt(i + 1) == '\'') {
392                      ++i;
393                  } else {
394                      i = pattern.indexOf('\'', i + 1);
395                      if (i == -1) {
396                          throw new IllegalArgumentException("Bad quoting in " + pattern);
397                      }
398                      ++i;
399                  }
400              } else {
401                  // Ignore spaces and punctuation.
402              }
403          }
404          return result;
405      }
406  
getDateFormatString(Context context)407      private static String getDateFormatString(Context context) {
408          final Locale locale = context.getResources().getConfiguration().locale;
409          java.text.DateFormat df = java.text.DateFormat.getDateInstance(
410                  java.text.DateFormat.SHORT, locale);
411          if (df instanceof SimpleDateFormat) {
412              return ((SimpleDateFormat) df).toPattern();
413          }
414  
415          throw new AssertionError("!(df instanceof SimpleDateFormat)");
416      }
417  
418      /**
419       * Given a format string and a time in milliseconds since Jan 1, 1970 GMT, returns a
420       * CharSequence containing the requested date.
421       * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
422       * @param inTimeInMillis in milliseconds since Jan 1, 1970 GMT
423       * @return a {@link CharSequence} containing the requested text
424       */
format(CharSequence inFormat, long inTimeInMillis)425      public static CharSequence format(CharSequence inFormat, long inTimeInMillis) {
426          return format(inFormat, new Date(inTimeInMillis));
427      }
428  
429      /**
430       * Given a format string and a {@link java.util.Date} object, returns a CharSequence containing
431       * the requested date.
432       * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
433       * @param inDate the date to format
434       * @return a {@link CharSequence} containing the requested text
435       */
format(CharSequence inFormat, Date inDate)436      public static CharSequence format(CharSequence inFormat, Date inDate) {
437          Calendar c = new GregorianCalendar();
438          c.setTime(inDate);
439          return format(inFormat, c);
440      }
441  
442      /**
443       * Indicates whether the specified format string contains seconds.
444       *
445       * Always returns false if the input format is null.
446       *
447       * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
448       *
449       * @return true if the format string contains {@link #SECONDS}, false otherwise
450       *
451       * @hide
452       */
453      @UnsupportedAppUsage
hasSeconds(CharSequence inFormat)454      public static boolean hasSeconds(CharSequence inFormat) {
455          return hasDesignator(inFormat, SECONDS);
456      }
457  
458      /**
459       * Test if a format string contains the given designator. Always returns
460       * {@code false} if the input format is {@code null}.
461       *
462       * Note that this is intended for searching for designators, not arbitrary
463       * characters. So searching for a literal single quote would not work correctly.
464       *
465       * @hide
466       */
467      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
hasDesignator(CharSequence inFormat, char designator)468      public static boolean hasDesignator(CharSequence inFormat, char designator) {
469          if (inFormat == null) return false;
470  
471          final int length = inFormat.length();
472  
473          boolean insideQuote = false;
474          for (int i = 0; i < length; i++) {
475              final char c = inFormat.charAt(i);
476              if (c == QUOTE) {
477                  insideQuote = !insideQuote;
478              } else if (!insideQuote) {
479                  if (c == designator) {
480                      return true;
481                  }
482              }
483          }
484  
485          return false;
486      }
487  
488      /**
489       * Given a format string and a {@link java.util.Calendar} object, returns a CharSequence
490       * containing the requested date.
491       * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
492       * @param inDate the date to format
493       * @return a {@link CharSequence} containing the requested text
494       */
format(CharSequence inFormat, Calendar inDate)495      public static CharSequence format(CharSequence inFormat, Calendar inDate) {
496          SpannableStringBuilder s = new SpannableStringBuilder(inFormat);
497          int count;
498  
499          DateFormatSymbols dfs = getIcuDateFormatSymbols(Locale.getDefault());
500          String[] amPm = dfs.getAmPmStrings();
501  
502          int len = inFormat.length();
503  
504          for (int i = 0; i < len; i += count) {
505              count = 1;
506              int c = s.charAt(i);
507  
508              if (c == QUOTE) {
509                  count = appendQuotedText(s, i);
510                  len = s.length();
511                  continue;
512              }
513  
514              while ((i + count < len) && (s.charAt(i + count) == c)) {
515                  count++;
516              }
517  
518              String replacement;
519              switch (c) {
520                  case 'A':
521                  case 'a':
522                      replacement = amPm[inDate.get(Calendar.AM_PM) - Calendar.AM];
523                      break;
524                  case 'd':
525                      replacement = zeroPad(inDate.get(Calendar.DATE), count);
526                      break;
527                  case 'c':
528                  case 'E':
529                      replacement = getDayOfWeekString(dfs,
530                                                       inDate.get(Calendar.DAY_OF_WEEK), count, c);
531                      break;
532                  case 'K': // hour in am/pm (0-11)
533                  case 'h': // hour in am/pm (1-12)
534                      {
535                          int hour = inDate.get(Calendar.HOUR);
536                          if (c == 'h' && hour == 0) {
537                              hour = 12;
538                          }
539                          replacement = zeroPad(hour, count);
540                      }
541                      break;
542                  case 'H': // hour in day (0-23)
543                  case 'k': // hour in day (1-24) [but see note below]
544                      {
545                          int hour = inDate.get(Calendar.HOUR_OF_DAY);
546                          // Historically on Android 'k' was interpreted as 'H', which wasn't
547                          // implemented, so pretty much all callers that want to format 24-hour
548                          // times are abusing 'k'. http://b/8359981.
549                          if (false && c == 'k' && hour == 0) {
550                              hour = 24;
551                          }
552                          replacement = zeroPad(hour, count);
553                      }
554                      break;
555                  case 'L':
556                  case 'M':
557                      replacement = getMonthString(dfs, inDate.get(Calendar.MONTH), count, c);
558                      break;
559                  case 'm':
560                      replacement = zeroPad(inDate.get(Calendar.MINUTE), count);
561                      break;
562                  case 's':
563                      replacement = zeroPad(inDate.get(Calendar.SECOND), count);
564                      break;
565                  case 'y':
566                      replacement = getYearString(inDate.get(Calendar.YEAR), count);
567                      break;
568                  case 'z':
569                      replacement = getTimeZoneString(inDate, count);
570                      break;
571                  default:
572                      replacement = null;
573                      break;
574              }
575  
576              if (replacement != null) {
577                  s.replace(i, i + count, replacement);
578                  count = replacement.length(); // CARE: count is used in the for loop above
579                  len = s.length();
580              }
581          }
582  
583          if (inFormat instanceof Spanned) {
584              return new SpannedString(s);
585          } else {
586              return s.toString();
587          }
588      }
589  
getDayOfWeekString(DateFormatSymbols dfs, int day, int count, int kind)590      private static String getDayOfWeekString(DateFormatSymbols dfs, int day, int count, int kind) {
591          boolean standalone = (kind == 'c');
592          int context = standalone ? DateFormatSymbols.STANDALONE : DateFormatSymbols.FORMAT;
593          final int width;
594          if (count == 5) {
595              width = DateFormatSymbols.NARROW;
596          } else if (count == 4) {
597              width = DateFormatSymbols.WIDE;
598          } else {
599              width = DateFormatSymbols.ABBREVIATED;
600          }
601          return dfs.getWeekdays(context, width)[day];
602      }
603  
getMonthString(DateFormatSymbols dfs, int month, int count, int kind)604      private static String getMonthString(DateFormatSymbols dfs, int month, int count, int kind) {
605          boolean standalone = (kind == 'L');
606          int monthContext = standalone ? DateFormatSymbols.STANDALONE : DateFormatSymbols.FORMAT;
607          if (count == 5) {
608              return dfs.getMonths(monthContext, DateFormatSymbols.NARROW)[month];
609          } else if (count == 4) {
610              return dfs.getMonths(monthContext, DateFormatSymbols.WIDE)[month];
611          } else if (count == 3) {
612              return dfs.getMonths(monthContext, DateFormatSymbols.ABBREVIATED)[month];
613          } else {
614              // Calendar.JANUARY == 0, so add 1 to month.
615              return zeroPad(month+1, count);
616          }
617      }
618  
getTimeZoneString(Calendar inDate, int count)619      private static String getTimeZoneString(Calendar inDate, int count) {
620          TimeZone tz = inDate.getTimeZone();
621          if (count < 2) { // FIXME: shouldn't this be <= 2 ?
622              return formatZoneOffset(inDate.get(Calendar.DST_OFFSET) +
623                                      inDate.get(Calendar.ZONE_OFFSET),
624                                      count);
625          } else {
626              boolean dst = inDate.get(Calendar.DST_OFFSET) != 0;
627              return tz.getDisplayName(dst, TimeZone.SHORT);
628          }
629      }
630  
formatZoneOffset(int offset, int count)631      private static String formatZoneOffset(int offset, int count) {
632          offset /= 1000; // milliseconds to seconds
633          StringBuilder tb = new StringBuilder();
634  
635          if (offset < 0) {
636              tb.insert(0, "-");
637              offset = -offset;
638          } else {
639              tb.insert(0, "+");
640          }
641  
642          int hours = offset / 3600;
643          int minutes = (offset % 3600) / 60;
644  
645          tb.append(zeroPad(hours, 2));
646          tb.append(zeroPad(minutes, 2));
647          return tb.toString();
648      }
649  
getYearString(int year, int count)650      private static String getYearString(int year, int count) {
651          return (count <= 2) ? zeroPad(year % 100, 2)
652                              : String.format(Locale.getDefault(), "%d", year);
653      }
654  
655  
656      /**
657       * Strips quotation marks from the {@code formatString} and appends the result back to the
658       * {@code formatString}.
659       *
660       * @param formatString the format string, as described in
661       *                     {@link android.text.format.DateFormat}, to be modified
662       * @param index        index of the first quote
663       * @return the length of the quoted text that was appended.
664       * @hide
665       */
appendQuotedText(SpannableStringBuilder formatString, int index)666      public static int appendQuotedText(SpannableStringBuilder formatString, int index) {
667          int length = formatString.length();
668          if (index + 1 < length && formatString.charAt(index + 1) == QUOTE) {
669              formatString.delete(index, index + 1);
670              return 1;
671          }
672  
673          int count = 0;
674  
675          // delete leading quote
676          formatString.delete(index, index + 1);
677          length--;
678  
679          while (index < length) {
680              char c = formatString.charAt(index);
681  
682              if (c == QUOTE) {
683                  //  QUOTEQUOTE -> QUOTE
684                  if (index + 1 < length && formatString.charAt(index + 1) == QUOTE) {
685  
686                      formatString.delete(index, index + 1);
687                      length--;
688                      count++;
689                      index++;
690                  } else {
691                      //  Closing QUOTE ends quoted text copying
692                      formatString.delete(index, index + 1);
693                      break;
694                  }
695              } else {
696                  index++;
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      /**
709       * We use Gregorian calendar for date formats in android.text.format and various UI widget
710       * historically. It's a utility method to get an {@link DateFormatSymbols} instance. Note that
711       * {@link DateFormatSymbols} has cache, and external cache is not needed unless same instance is
712       * requested repeatedly in the performance critical code.
713       *
714       * @hide
715       */
getIcuDateFormatSymbols(Locale locale)716      public static DateFormatSymbols getIcuDateFormatSymbols(Locale locale) {
717          return new DateFormatSymbols(android.icu.util.GregorianCalendar.class, locale);
718      }
719  
720      /**
721       * See http://b/266731719. It mirrors the implementation in
722       * {@link libcore.icu.SimpleDateFormatData.DateTimeFormatStringGenerator#postProcessPattern}
723       */
getCompatibleEnglishPattern(ULocale locale, String pattern)724      private static String getCompatibleEnglishPattern(ULocale locale, String pattern) {
725          if (pattern == null || locale == null || !"en".equals(locale.getLanguage())) {
726              return pattern;
727          }
728  
729          String region = locale.getCountry();
730          if (region != null && !region.isEmpty() && !"US".equals(region)) {
731              return pattern;
732          }
733  
734          return pattern.replace('\u202f', ' ');
735      }
736  }
737