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 'M' 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 -> 9<br/> 50 MM -> 09<br/> 51 MMM -> Sep<br/> 52 MMMM -> 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 -> 7<br/> 63 mm -> 07<br/> 64 mmm -> 007<br/> 65 mmmm -> 0007 66 </p> 67 <p> 68 Examples for April 6, 1970 at 3:23am:<br/> 69 "MM/dd/yy h:mmaa" -> "04/06/70 3:23am"<br/> 70 "MMM dd, yyyy h:mmaa" -> "Apr 6, 1970 3:23am"<br/> 71 "MMMM dd, yyyy h:mmaa" -> "April 6, 1970 3:23am"<br/> 72 "E, MMMM dd, yyyy h:mmaa" -> "Mon, April 6, 1970 3:23am&<br/> 73 "EEEE, MMMM dd, yyyy h:mmaa" -> "Monday, April 6, 1970 3:23am"<br/> 74 "'Noteworthy day: 'M/d/yy" -> "Noteworthy day: 4/6/70" 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