DateFormat.java revision 9266c558bf1d21ff647525ff99f7dadbca417309
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.TimeZone; 31import java.text.SimpleDateFormat; 32 33/** 34 Utility class for producing strings with formatted date/time. 35 36 <p> 37 This class takes as inputs a format string and a representation of a date/time. 38 The format string controls how the output is generated. 39 </p> 40 <p> 41 Formatting characters may be repeated in order to get more detailed representations 42 of that field. For instance, the format character 'M' is used to 43 represent the month. Depending on how many times that character is repeated 44 you get a different representation. 45 </p> 46 <p> 47 For the month of September:<br/> 48 M -> 9<br/> 49 MM -> 09<br/> 50 MMM -> Sep<br/> 51 MMMM -> September 52 </p> 53 <p> 54 The effects of the duplication vary depending on the nature of the field. 55 See the notes on the individual field formatters for details. For purely numeric 56 fields such as <code>HOUR</code> adding more copies of the designator will 57 zero-pad the value to that number of characters. 58 </p> 59 <p> 60 For 7 minutes past the hour:<br/> 61 m -> 7<br/> 62 mm -> 07<br/> 63 mmm -> 007<br/> 64 mmmm -> 0007 65 </p> 66 <p> 67 Examples for April 6, 1970 at 3:23am:<br/> 68 "MM/dd/yy h:mmaa" -> "04/06/70 3:23am"<br/> 69 "MMM dd, yyyy h:mmaa" -> "Apr 6, 1970 3:23am"<br/> 70 "MMMM dd, yyyy h:mmaa" -> "April 6, 1970 3:23am"<br/> 71 "E, MMMM dd, yyyy h:mmaa" -> "Mon, April 6, 1970 3:23am&<br/> 72 "EEEE, MMMM dd, yyyy h:mmaa" -> "Monday, April 6, 1970 3:23am"<br/> 73 "'Noteworthy day: 'M/d/yy" -> "Noteworthy day: 4/6/70" 74 */ 75 76public class DateFormat { 77 /** 78 Text in the format string that should be copied verbatim rather that 79 interpreted as formatting codes must be surrounded by the <code>QUOTE</code> 80 character. If you need to embed a literal <code>QUOTE</code> character in 81 the output text then use two in a row. 82 */ 83 public static final char QUOTE = '\''; 84 85 /** 86 This designator indicates whether the <code>HOUR</code> field is before 87 or after noon. The output is lower-case. 88 89 Examples: 90 a -> a or p 91 aa -> am or pm 92 */ 93 public static final char AM_PM = 'a'; 94 95 /** 96 This designator indicates whether the <code>HOUR</code> field is before 97 or after noon. The output is capitalized. 98 99 Examples: 100 A -> A or P 101 AA -> AM or PM 102 */ 103 public static final char CAPITAL_AM_PM = 'A'; 104 105 /** 106 This designator indicates the day of the month. 107 108 Examples for the 9th of the month: 109 d -> 9 110 dd -> 09 111 */ 112 public static final char DATE = 'd'; 113 114 /** 115 This designator indicates the name of the day of the week. 116 117 Examples for Sunday: 118 E -> Sun 119 EEEE -> Sunday 120 */ 121 public static final char DAY = 'E'; 122 123 /** 124 This designator indicates the hour of the day in 12 hour format. 125 126 Examples for 3pm: 127 h -> 3 128 hh -> 03 129 */ 130 public static final char HOUR = 'h'; 131 132 /** 133 This designator indicates the hour of the day in 24 hour format. 134 135 Example for 3pm: 136 k -> 15 137 138 Examples for midnight: 139 k -> 0 140 kk -> 00 141 */ 142 public static final char HOUR_OF_DAY = 'k'; 143 144 /** 145 This designator indicates the minute of the hour. 146 147 Examples for 7 minutes past the hour: 148 m -> 7 149 mm -> 07 150 */ 151 public static final char MINUTE = 'm'; 152 153 /** 154 This designator indicates the month of the year 155 156 Examples for September: 157 M -> 9 158 MM -> 09 159 MMM -> Sep 160 MMMM -> September 161 */ 162 public static final char MONTH = 'M'; 163 164 /** 165 This designator indicates the seconds of the minute. 166 167 Examples for 7 seconds past the minute: 168 s -> 7 169 ss -> 07 170 */ 171 public static final char SECONDS = 's'; 172 173 /** 174 This designator indicates the offset of the timezone from GMT. 175 176 Example for US/Pacific timezone: 177 z -> -0800 178 zz -> PST 179 */ 180 public static final char TIME_ZONE = 'z'; 181 182 /** 183 This designator indicates the year. 184 185 Examples for 2006 186 y -> 06 187 yyyy -> 2006 188 */ 189 public static final char YEAR = 'y'; 190 191 /** 192 * Returns true if user preference is set to 24-hour format. 193 * @param context the context to use for the content resolver 194 * @return true if 24 hour time format is selected, false otherwise. 195 */ 196 public static boolean is24HourFormat(Context context) { 197 String value = Settings.System.getString(context.getContentResolver(), 198 Settings.System.TIME_12_24); 199 200 if (value == null) { 201 java.text.DateFormat natural = 202 java.text.DateFormat.getTimeInstance( 203 java.text.DateFormat.LONG, 204 context.getResources().getConfiguration().locale); 205 206 if (natural instanceof SimpleDateFormat) { 207 SimpleDateFormat sdf = (SimpleDateFormat) natural; 208 String pattern = sdf.toPattern(); 209 210 if (pattern.indexOf('H') >= 0) { 211 return true; 212 } else { 213 return false; 214 } 215 } 216 } 217 218 boolean b24 = !(value == null || value.equals("12")); 219 return b24; 220 } 221 222 /** 223 * Returns a {@link java.text.DateFormat} object that can format the time according 224 * to the current user preference. 225 * @param context the application context 226 * @return the {@link java.text.DateFormat} object that properly formats the time. 227 */ 228 public static final java.text.DateFormat getTimeFormat(Context context) { 229 boolean b24 = is24HourFormat(context); 230 int res; 231 232 if (b24) { 233 res = R.string.twenty_four_hour_time_format; 234 } else { 235 res = R.string.twelve_hour_time_format; 236 } 237 238 return new java.text.SimpleDateFormat(context.getString(res)); 239 } 240 241 /** 242 * Returns a {@link java.text.DateFormat} object that can format the date according 243 * to the current user preference. 244 * @param context the application context 245 * @return the {@link java.text.DateFormat} object that properly formats the date. 246 */ 247 public static final java.text.DateFormat getDateFormat(Context context) { 248 String value = getDateFormatString(context); 249 return new java.text.SimpleDateFormat(value); 250 } 251 252 /** 253 * Returns a {@link java.text.DateFormat} object that can format the date 254 * in long form (such as December 31, 1999) based on user preference. 255 * @param context the application context 256 * @return the {@link java.text.DateFormat} object that formats the date in long form. 257 */ 258 public static final java.text.DateFormat getLongDateFormat(Context context) { 259 String value = getDateFormatString(context); 260 if (value.indexOf('M') < value.indexOf('d')) { 261 value = context.getString(R.string.full_date_month_first); 262 } else { 263 value = context.getString(R.string.full_date_day_first); 264 } 265 return new java.text.SimpleDateFormat(value); 266 } 267 268 /** 269 * Returns a {@link java.text.DateFormat} object that can format the date 270 * in medium form (such as Dec. 31, 1999) based on user preference. 271 * @param context the application context 272 * @return the {@link java.text.DateFormat} object that formats the date in long form. 273 */ 274 public static final java.text.DateFormat getMediumDateFormat(Context context) { 275 String value = getDateFormatString(context); 276 if (value.indexOf('M') < value.indexOf('d')) { 277 value = context.getString(R.string.medium_date_month_first); 278 } else { 279 value = context.getString(R.string.medium_date_day_first); 280 } 281 return new java.text.SimpleDateFormat(value); 282 } 283 284 /** 285 * Gets the current date format stored as a char array. The array will contain 286 * 3 elements ({@link #DATE}, {@link #MONTH}, and {@link #YEAR}) in the order 287 * preferred by the user. 288 */ 289 public static final char[] getDateFormatOrder(Context context) { 290 char[] order = new char[] {DATE, MONTH, YEAR}; 291 String value = getDateFormatString(context); 292 int index = 0; 293 boolean foundDate = false; 294 boolean foundMonth = false; 295 boolean foundYear = false; 296 297 for (char c : value.toCharArray()) { 298 if (!foundDate && (c == DATE)) { 299 foundDate = true; 300 order[index] = DATE; 301 index++; 302 } 303 304 if (!foundMonth && (c == MONTH)) { 305 foundMonth = true; 306 order[index] = MONTH; 307 index++; 308 } 309 310 if (!foundYear && (c == YEAR)) { 311 foundYear = true; 312 order[index] = YEAR; 313 index++; 314 } 315 } 316 return order; 317 } 318 319 private static String getDateFormatString(Context context) { 320 String value = Settings.System.getString(context.getContentResolver(), 321 Settings.System.DATE_FORMAT); 322 if (value == null || value.length() < 6) { 323 /* 324 * No need to localize -- this is an emergency fallback in case 325 * the setting is missing, but it should always be set. 326 */ 327 value = "MM-dd-yyyy"; 328 } 329 return value; 330 } 331 332 /** 333 * Given a format string and a time in milliseconds since Jan 1, 1970 GMT, returns a 334 * CharSequence containing the requested date. 335 * @param inFormat the format string, as described in {@link android.text.format.DateFormat} 336 * @param inTimeInMillis in milliseconds since Jan 1, 1970 GMT 337 * @return a {@link CharSequence} containing the requested text 338 */ 339 public static final CharSequence format(CharSequence inFormat, long inTimeInMillis) { 340 return format(inFormat, new Date(inTimeInMillis)); 341 } 342 343 /** 344 * Given a format string and a {@link java.util.Date} object, returns a CharSequence containing 345 * the requested date. 346 * @param inFormat the format string, as described in {@link android.text.format.DateFormat} 347 * @param inDate the date to format 348 * @return a {@link CharSequence} containing the requested text 349 */ 350 public static final CharSequence format(CharSequence inFormat, Date inDate) { 351 Calendar c = new GregorianCalendar(); 352 353 c.setTime(inDate); 354 355 return format(inFormat, c); 356 } 357 358 /** 359 * Given a format string and a {@link java.util.Calendar} object, returns a CharSequence 360 * containing the requested date. 361 * @param inFormat the format string, as described in {@link android.text.format.DateFormat} 362 * @param inDate the date to format 363 * @return a {@link CharSequence} containing the requested text 364 */ 365 public static final CharSequence format(CharSequence inFormat, Calendar inDate) { 366 SpannableStringBuilder s = new SpannableStringBuilder(inFormat); 367 int c; 368 int count; 369 370 int len = inFormat.length(); 371 372 for (int i = 0; i < len; i += count) { 373 int temp; 374 375 count = 1; 376 c = s.charAt(i); 377 378 if (c == QUOTE) { 379 count = appendQuotedText(s, i, len); 380 len = s.length(); 381 continue; 382 } 383 384 while ((i + count < len) && (s.charAt(i + count) == c)) { 385 count++; 386 } 387 388 String replacement; 389 390 switch (c) { 391 case AM_PM: 392 replacement = DateUtils.getAMPMString(inDate.get(Calendar.AM_PM)); 393 break; 394 395 case CAPITAL_AM_PM: 396 //FIXME: this is the same as AM_PM? no capital? 397 replacement = DateUtils.getAMPMString(inDate.get(Calendar.AM_PM)); 398 break; 399 400 case DATE: 401 replacement = zeroPad(inDate.get(Calendar.DATE), count); 402 break; 403 404 case DAY: 405 temp = inDate.get(Calendar.DAY_OF_WEEK); 406 replacement = DateUtils.getDayOfWeekString(temp, 407 count < 4 ? 408 DateUtils.LENGTH_MEDIUM : 409 DateUtils.LENGTH_LONG); 410 break; 411 412 case HOUR: 413 temp = inDate.get(Calendar.HOUR); 414 415 if (0 == temp) 416 temp = 12; 417 418 replacement = zeroPad(temp, count); 419 break; 420 421 case HOUR_OF_DAY: 422 replacement = zeroPad(inDate.get(Calendar.HOUR_OF_DAY), count); 423 break; 424 425 case MINUTE: 426 replacement = zeroPad(inDate.get(Calendar.MINUTE), count); 427 break; 428 429 case MONTH: 430 replacement = getMonthString(inDate, count); 431 break; 432 433 case SECONDS: 434 replacement = zeroPad(inDate.get(Calendar.SECOND), count); 435 break; 436 437 case TIME_ZONE: 438 replacement = getTimeZoneString(inDate, count); 439 break; 440 441 case YEAR: 442 replacement = getYearString(inDate, count); 443 break; 444 445 default: 446 replacement = null; 447 break; 448 } 449 450 if (replacement != null) { 451 s.replace(i, i + count, replacement); 452 count = replacement.length(); // CARE: count is used in the for loop above 453 len = s.length(); 454 } 455 } 456 457 if (inFormat instanceof Spanned) 458 return new SpannedString(s); 459 else 460 return s.toString(); 461 } 462 463 private static final String getMonthString(Calendar inDate, int count) { 464 int month = inDate.get(Calendar.MONTH); 465 466 if (count >= 4) 467 return DateUtils.getMonthString(month, DateUtils.LENGTH_LONG); 468 else if (count == 3) 469 return DateUtils.getMonthString(month, DateUtils.LENGTH_MEDIUM); 470 else { 471 // Calendar.JANUARY == 0, so add 1 to month. 472 return zeroPad(month+1, count); 473 } 474 } 475 476 private static final String getTimeZoneString(Calendar inDate, int count) { 477 TimeZone tz = inDate.getTimeZone(); 478 479 if (count < 2) { // FIXME: shouldn't this be <= 2 ? 480 return formatZoneOffset(inDate.get(Calendar.DST_OFFSET) + 481 inDate.get(Calendar.ZONE_OFFSET), 482 count); 483 } else { 484 boolean dst = inDate.get(Calendar.DST_OFFSET) != 0; 485 return tz.getDisplayName(dst, TimeZone.SHORT); 486 } 487 } 488 489 private static final String formatZoneOffset(int offset, int count) { 490 offset /= 1000; // milliseconds to seconds 491 StringBuilder tb = new StringBuilder(); 492 493 if (offset < 0) { 494 tb.insert(0, "-"); 495 offset = -offset; 496 } else { 497 tb.insert(0, "+"); 498 } 499 500 int hours = offset / 3600; 501 int minutes = (offset % 3600) / 60; 502 503 tb.append(zeroPad(hours, 2)); 504 tb.append(zeroPad(minutes, 2)); 505 return tb.toString(); 506 } 507 508 private static final String getYearString(Calendar inDate, int count) { 509 int year = inDate.get(Calendar.YEAR); 510 return (count <= 2) ? zeroPad(year % 100, 2) : String.valueOf(year); 511 } 512 513 private static final int appendQuotedText(SpannableStringBuilder s, int i, int len) { 514 if (i + 1 < len && s.charAt(i + 1) == QUOTE) { 515 s.delete(i, i + 1); 516 return 1; 517 } 518 519 int count = 0; 520 521 // delete leading quote 522 s.delete(i, i + 1); 523 len--; 524 525 while (i < len) { 526 char c = s.charAt(i); 527 528 if (c == QUOTE) { 529 // QUOTEQUOTE -> QUOTE 530 if (i + 1 < len && s.charAt(i + 1) == QUOTE) { 531 532 s.delete(i, i + 1); 533 len--; 534 count++; 535 i++; 536 } else { 537 // Closing QUOTE ends quoted text copying 538 s.delete(i, i + 1); 539 break; 540 } 541 } else { 542 i++; 543 count++; 544 } 545 } 546 547 return count; 548 } 549 550 private static final String zeroPad(int inValue, int inMinDigits) { 551 String val = String.valueOf(inValue); 552 553 if (val.length() < inMinDigits) { 554 char[] buf = new char[inMinDigits]; 555 556 for (int i = 0; i < inMinDigits; i++) 557 buf[i] = '0'; 558 559 val.getChars(0, val.length(), buf, inMinDigits - val.length()); 560 val = new String(buf); 561 } 562 return val; 563 } 564} 565