DateFormat.java revision 328769582328192f8f361dcb56f2ad67ad00ae2c
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 17package android.text.format; 18 19import android.content.Context; 20import android.provider.Settings; 21import android.text.SpannableStringBuilder; 22import android.text.Spanned; 23import android.text.SpannedString; 24 25import com.android.internal.R; 26 27import java.util.Calendar; 28import java.util.Date; 29import java.util.GregorianCalendar; 30import java.util.Locale; 31import java.util.TimeZone; 32import 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 77public 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 */ 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 */ 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 */ 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 */ 285 public static java.text.DateFormat getDateFormatForSetting(Context context, 286 String value) { 287 if (value != null) { 288 int month = value.indexOf('M'); 289 int day = value.indexOf('d'); 290 int year = value.indexOf('y'); 291 292 if (month >= 0 && day >= 0 && year >= 0) { 293 String template = context.getString(R.string.numeric_date_template); 294 if (year < month) { 295 if (month < day) { 296 value = String.format(template, "yyyy", "MM", "dd"); 297 } else { 298 value = String.format(template, "yyyy", "dd", "MM"); 299 } 300 } else if (month < day) { 301 if (day < year) { 302 value = String.format(template, "MM", "dd", "yyyy"); 303 } else { // unlikely 304 value = String.format(template, "MM", "yyyy", "dd"); 305 } 306 } else { // day < month 307 if (month < year) { 308 value = String.format(template, "dd", "MM", "yyyy"); 309 } else { // unlikely 310 value = String.format(template, "dd", "yyyy", "MM"); 311 } 312 } 313 314 return new java.text.SimpleDateFormat(value); 315 } 316 } 317 318 /* 319 * The setting is not set; use the default. 320 * We use a resource string here instead of just DateFormat.SHORT 321 * so that we get a four-digit year instead a two-digit year. 322 */ 323 value = context.getString(R.string.numeric_date_format); 324 return new java.text.SimpleDateFormat(value); 325 } 326 327 /** 328 * Returns a {@link java.text.DateFormat} object that can format the date 329 * in long form (such as December 31, 1999) for the current locale. 330 * @param context the application context 331 * @return the {@link java.text.DateFormat} object that formats the date in long form. 332 */ 333 public static final java.text.DateFormat getLongDateFormat(Context context) { 334 return java.text.DateFormat.getDateInstance(java.text.DateFormat.LONG); 335 } 336 337 /** 338 * Returns a {@link java.text.DateFormat} object that can format the date 339 * in medium form (such as Dec. 31, 1999) for the current locale. 340 * @param context the application context 341 * @return the {@link java.text.DateFormat} object that formats the date in long form. 342 */ 343 public static final java.text.DateFormat getMediumDateFormat(Context context) { 344 return java.text.DateFormat.getDateInstance(java.text.DateFormat.MEDIUM); 345 } 346 347 /** 348 * Gets the current date format stored as a char array. The array will contain 349 * 3 elements ({@link #DATE}, {@link #MONTH}, and {@link #YEAR}) in the order 350 * preferred by the user. 351 */ 352 public static final char[] getDateFormatOrder(Context context) { 353 char[] order = new char[] {DATE, MONTH, YEAR}; 354 String value = getDateFormatString(context); 355 int index = 0; 356 boolean foundDate = false; 357 boolean foundMonth = false; 358 boolean foundYear = false; 359 360 for (char c : value.toCharArray()) { 361 if (!foundDate && (c == DATE)) { 362 foundDate = true; 363 order[index] = DATE; 364 index++; 365 } 366 367 if (!foundMonth && (c == MONTH)) { 368 foundMonth = true; 369 order[index] = MONTH; 370 index++; 371 } 372 373 if (!foundYear && (c == YEAR)) { 374 foundYear = true; 375 order[index] = YEAR; 376 index++; 377 } 378 } 379 return order; 380 } 381 382 private static String getDateFormatString(Context context) { 383 java.text.DateFormat df; 384 df = java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT); 385 if (df instanceof SimpleDateFormat) { 386 return ((SimpleDateFormat) df).toPattern(); 387 } 388 389 String value = Settings.System.getString(context.getContentResolver(), 390 Settings.System.DATE_FORMAT); 391 if (value == null || value.length() < 6) { 392 /* 393 * No need to localize -- this is an emergency fallback in case 394 * the setting is missing, but it should always be set. 395 */ 396 value = "MM-dd-yyyy"; 397 } 398 return value; 399 } 400 401 /** 402 * Given a format string and a time in milliseconds since Jan 1, 1970 GMT, returns a 403 * CharSequence containing the requested date. 404 * @param inFormat the format string, as described in {@link android.text.format.DateFormat} 405 * @param inTimeInMillis in milliseconds since Jan 1, 1970 GMT 406 * @return a {@link CharSequence} containing the requested text 407 */ 408 public static final CharSequence format(CharSequence inFormat, long inTimeInMillis) { 409 return format(inFormat, new Date(inTimeInMillis)); 410 } 411 412 /** 413 * Given a format string and a {@link java.util.Date} object, returns a CharSequence containing 414 * the requested date. 415 * @param inFormat the format string, as described in {@link android.text.format.DateFormat} 416 * @param inDate the date to format 417 * @return a {@link CharSequence} containing the requested text 418 */ 419 public static final CharSequence format(CharSequence inFormat, Date inDate) { 420 Calendar c = new GregorianCalendar(); 421 422 c.setTime(inDate); 423 424 return format(inFormat, c); 425 } 426 427 /** 428 * Given a format string and a {@link java.util.Calendar} object, returns a CharSequence 429 * containing the requested date. 430 * @param inFormat the format string, as described in {@link android.text.format.DateFormat} 431 * @param inDate the date to format 432 * @return a {@link CharSequence} containing the requested text 433 */ 434 public static final CharSequence format(CharSequence inFormat, Calendar inDate) { 435 SpannableStringBuilder s = new SpannableStringBuilder(inFormat); 436 int c; 437 int count; 438 439 int len = inFormat.length(); 440 441 for (int i = 0; i < len; i += count) { 442 int temp; 443 444 count = 1; 445 c = s.charAt(i); 446 447 if (c == QUOTE) { 448 count = appendQuotedText(s, i, len); 449 len = s.length(); 450 continue; 451 } 452 453 while ((i + count < len) && (s.charAt(i + count) == c)) { 454 count++; 455 } 456 457 String replacement; 458 459 switch (c) { 460 case AM_PM: 461 replacement = DateUtils.getAMPMString(inDate.get(Calendar.AM_PM)); 462 break; 463 464 case CAPITAL_AM_PM: 465 //FIXME: this is the same as AM_PM? no capital? 466 replacement = DateUtils.getAMPMString(inDate.get(Calendar.AM_PM)); 467 break; 468 469 case DATE: 470 replacement = zeroPad(inDate.get(Calendar.DATE), count); 471 break; 472 473 case DAY: 474 temp = inDate.get(Calendar.DAY_OF_WEEK); 475 replacement = DateUtils.getDayOfWeekString(temp, 476 count < 4 ? 477 DateUtils.LENGTH_MEDIUM : 478 DateUtils.LENGTH_LONG); 479 break; 480 481 case HOUR: 482 temp = inDate.get(Calendar.HOUR); 483 484 if (0 == temp) 485 temp = 12; 486 487 replacement = zeroPad(temp, count); 488 break; 489 490 case HOUR_OF_DAY: 491 replacement = zeroPad(inDate.get(Calendar.HOUR_OF_DAY), count); 492 break; 493 494 case MINUTE: 495 replacement = zeroPad(inDate.get(Calendar.MINUTE), count); 496 break; 497 498 case MONTH: 499 replacement = getMonthString(inDate, count); 500 break; 501 502 case SECONDS: 503 replacement = zeroPad(inDate.get(Calendar.SECOND), count); 504 break; 505 506 case TIME_ZONE: 507 replacement = getTimeZoneString(inDate, count); 508 break; 509 510 case YEAR: 511 replacement = getYearString(inDate, count); 512 break; 513 514 default: 515 replacement = null; 516 break; 517 } 518 519 if (replacement != null) { 520 s.replace(i, i + count, replacement); 521 count = replacement.length(); // CARE: count is used in the for loop above 522 len = s.length(); 523 } 524 } 525 526 if (inFormat instanceof Spanned) 527 return new SpannedString(s); 528 else 529 return s.toString(); 530 } 531 532 private static final String getMonthString(Calendar inDate, int count) { 533 int month = inDate.get(Calendar.MONTH); 534 535 if (count >= 4) 536 return DateUtils.getMonthString(month, DateUtils.LENGTH_LONG); 537 else if (count == 3) 538 return DateUtils.getMonthString(month, DateUtils.LENGTH_MEDIUM); 539 else { 540 // Calendar.JANUARY == 0, so add 1 to month. 541 return zeroPad(month+1, count); 542 } 543 } 544 545 private static final String getTimeZoneString(Calendar inDate, int count) { 546 TimeZone tz = inDate.getTimeZone(); 547 548 if (count < 2) { // FIXME: shouldn't this be <= 2 ? 549 return formatZoneOffset(inDate.get(Calendar.DST_OFFSET) + 550 inDate.get(Calendar.ZONE_OFFSET), 551 count); 552 } else { 553 boolean dst = inDate.get(Calendar.DST_OFFSET) != 0; 554 return tz.getDisplayName(dst, TimeZone.SHORT); 555 } 556 } 557 558 private static final String formatZoneOffset(int offset, int count) { 559 offset /= 1000; // milliseconds to seconds 560 StringBuilder tb = new StringBuilder(); 561 562 if (offset < 0) { 563 tb.insert(0, "-"); 564 offset = -offset; 565 } else { 566 tb.insert(0, "+"); 567 } 568 569 int hours = offset / 3600; 570 int minutes = (offset % 3600) / 60; 571 572 tb.append(zeroPad(hours, 2)); 573 tb.append(zeroPad(minutes, 2)); 574 return tb.toString(); 575 } 576 577 private static final String getYearString(Calendar inDate, int count) { 578 int year = inDate.get(Calendar.YEAR); 579 return (count <= 2) ? zeroPad(year % 100, 2) : String.valueOf(year); 580 } 581 582 private static final int appendQuotedText(SpannableStringBuilder s, int i, int len) { 583 if (i + 1 < len && s.charAt(i + 1) == QUOTE) { 584 s.delete(i, i + 1); 585 return 1; 586 } 587 588 int count = 0; 589 590 // delete leading quote 591 s.delete(i, i + 1); 592 len--; 593 594 while (i < len) { 595 char c = s.charAt(i); 596 597 if (c == QUOTE) { 598 // QUOTEQUOTE -> QUOTE 599 if (i + 1 < len && s.charAt(i + 1) == QUOTE) { 600 601 s.delete(i, i + 1); 602 len--; 603 count++; 604 i++; 605 } else { 606 // Closing QUOTE ends quoted text copying 607 s.delete(i, i + 1); 608 break; 609 } 610 } else { 611 i++; 612 count++; 613 } 614 } 615 616 return count; 617 } 618 619 private static final String zeroPad(int inValue, int inMinDigits) { 620 String val = String.valueOf(inValue); 621 622 if (val.length() < inMinDigits) { 623 char[] buf = new char[inMinDigits]; 624 625 for (int i = 0; i < inMinDigits; i++) 626 buf[i] = '0'; 627 628 val.getChars(0, val.length(), buf, inMinDigits - val.length()); 629 val = new String(buf); 630 } 631 return val; 632 } 633} 634