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