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